Merge remote-tracking branch 'upstream/next' into feature/glitchtip-service

# Conflicts:
#	apps/api/prisma/schema.prisma
#	apps/api/src/lib/common.ts
#	apps/api/src/lib/serviceFields.ts
#	apps/api/src/routes/api/v1/services/handlers.ts
This commit is contained in:
Guillaume Bonnet
2022-08-15 21:30:53 +02:00
21 changed files with 933 additions and 38 deletions

View File

@@ -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");

View File

@@ -323,6 +323,7 @@ model Service {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
fider Fider?
ghost Ghost?
glitchTip GlitchTip?
@@ -336,7 +337,9 @@ model Service {
umami Umami?
vscodeserver Vscodeserver?
wordpress Wordpress?
teams Team[]
appwrite Appwrite?
teams Team[]
}
model PlausibleAnalytics {
@@ -493,6 +496,25 @@ model Moodle {
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])
}
model GlitchTip {
id String @id @default(cuid())
postgresqlUser String

View File

@@ -2,14 +2,14 @@ import { executeDockerCmd, prisma } from "../common"
import { saveBuildLog } from "./common";
export default async function (data: any): Promise<void> {
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<void> {
}
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;
}
}

View File

@@ -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.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,
glitchTip: true,
};
@@ -97,17 +99,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) => {
@@ -270,6 +278,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',
@@ -585,6 +604,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}`
);
@@ -596,6 +620,11 @@ export async function startTraefikProxy(id: string): Promise<void> {
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
@@ -879,6 +908,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') {
@@ -915,7 +949,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
}
@@ -1238,7 +1272,6 @@ export async function startTraefikTCPProxy(
}
traefikUrl = `${ip}/webhooks/traefik/other.json`
}
console.log(traefikUrl)
const tcpProxy = {
version: '3.8',
services: {
@@ -1303,6 +1336,7 @@ export async function getServiceFromDB({ id, teamId }: { id: string; teamId: str
return s;
});
}
body[type] = { ...body[type], ...getUpdateableFields(type, body[type]) }
return { ...body, settings };
}
@@ -1529,6 +1563,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 if (type === 'glitchTip') {
const defaultUsername = cuid();
const defaultEmail = `${defaultUsername}@example.com`;
@@ -1566,6 +1629,7 @@ export async function configureServiceType({
}
export async function removeService({ id }: { id: string }): Promise<void> {
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 } });
@@ -1577,8 +1641,8 @@ export async function removeService({ id }: { id: string }): Promise<void> {
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
await prisma.glitchTip.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 } });
}
@@ -1643,9 +1707,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 }) {
@@ -1717,7 +1781,7 @@ export function convertTolOldVolumeNames(type) {
// export async function getAvailableServices(): Promise<any> {
// const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
// return data
//
//
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
// Cleanup old coolify images
try {
@@ -1780,4 +1844,4 @@ export function persistentVolumes(id, persistentStorage, config) {
...composeVolumes
) || {}
return { volumes, volumeMounts }
}
}

View File

@@ -16,6 +16,7 @@ export function formatLabelsOnDocker(data) {
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<boolean> {
let containerFound = false;
try {
console.log('checking ', container)
const { stdout } = await executeDockerCmd({
dockerId,
command:
@@ -71,7 +72,7 @@ export async function removeContainer({
}): Promise<void> {
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}` })

View File

@@ -326,7 +326,7 @@ export const fider = [{
isBoolean: false,
isEncrypted: true
}, {
name: 'postgreslUser',
name: 'postgresqlUser',
isEditable: false,
isLowerCase: false,
isNumber: false,
@@ -477,6 +477,88 @@ export const moodle = [{
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,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
}]
export const glitchTip = [{
name: 'postgresqlUser',
isEditable: false,
@@ -588,4 +670,4 @@ export const glitchTip = [{
isNumber: false,
isBoolean: true,
isEncrypted: false
}]
}]

View File

@@ -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'
}
}
}
}

View File

@@ -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<OnlyId>) {
// try {
@@ -29,7 +30,7 @@ import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServ
// serviceSecret.forEach((secret) => {
// environmentVariables[secret.name] = secret.value;
// });
// }
// }
// config.newVolumes = {}
// for (const service of Object.entries(config.services)) {
// const name = service[0]
@@ -97,7 +98,7 @@ import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServ
// }
// console.log(config.services)
// console.log(config.volumes)
// console.log(config.volumes)
// // config.services[id] = JSON.parse(JSON.stringify(config.services[type]))
// // config.services[id].container_name = id
@@ -197,13 +198,11 @@ export async function getService(request: FastifyRequest<OnlyId>) {
const teamId = request.user.teamId;
const { id } = request.params;
const service = await getServiceFromDB({ id, teamId });
const settings = await listSettings()
if (!service) {
throw { status: 404, message: 'Service not found.' }
}
return {
service,
settings
service
}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -590,6 +589,9 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
if (type === 'moodle') {
return await startMoodleService(request)
}
if (type === 'appwrite') {
return await startAppWriteService(request)
}
if (type === 'glitchTip') {
return await startGlitchTipService(request)
}
@@ -643,6 +645,9 @@ export async function stopService(request: FastifyRequest<ServiceStartStop>) {
if (type === 'fider') {
return await stopFiderService(request)
}
if (type === 'appwrite') {
return await stopAppWriteService(request)
}
if (type === 'moodle') {
return await stopMoodleService(request)
}
@@ -2480,7 +2485,511 @@ async function stopFiderService(request: FastifyRequest<ServiceStartStop>) {
return errorHandler({ status, message })
}
}
async function startAppWriteService(request: FastifyRequest<ServiceStartStop>) {
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<ServiceStartStop>) {
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<ServiceStartStop>) {
try {
const { id } = request.params;