feat: Appwrite service

This commit is contained in:
Andras Bacsai 2022-08-15 14:58:10 +00:00
parent 3dd2a059bf
commit 8b5c7c94cd
14 changed files with 887 additions and 29 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

@ -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])
}

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.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<void> {
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<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 } });
@ -1559,8 +1602,8 @@ export async function removeService({ id }: { id: string }): Promise<void> {
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 }) {

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

@ -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,

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 {
@ -588,6 +589,9 @@ export async function startService(request: FastifyRequest<ServiceStartStop>) {
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<ServiceStartStop>) {
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<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;

View File

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

View File

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

View File

@ -55,9 +55,12 @@
<a href="https://fider.io" target="_blank">
<Icons.Fider />
</a>
{:else if service.type === 'appwrote'}
<a href="https://appwrite.io" target="_blank">
<Icons.Appwrite/>
</a>
{:else if service.type === 'moodle'}
<a href="https://moodle.org" target="_blank">
<Icons.Moodle />
</a>
{/if}

View File

@ -0,0 +1,126 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
import Select from 'svelte-select';
export let service: any;
export let readOnly: any;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Appwrite</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="opensslKeyV1">Encryption Key</label>
<CopyPasswordField
name="opensslKeyV1"
id="opensslKeyV1"
isPasswordField
value={service.appwrite.opensslKeyV1}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="executorSecret">Executor Secret</label>
<CopyPasswordField
name="executorSecret"
id="executorSecret"
isPasswordField
value={service.appwrite.executorSecret}
readonly
disabled
/>
</div>
<!-- <div class="flex space-x-1 py-5 font-bold">
<div class="title">Redis</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="redisPassword">Password</label>
<CopyPasswordField
name="redisPassword"
id="redisPassword"
isPasswordField
value={service.appwrite.redisPassword}
readonly
disabled
/>
</div> -->
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MariaDB</div>
</div>
<!-- <div class="grid grid-cols-2 items-center px-10">
<label for="mariadbHost">MariaDB Host</label>
<CopyPasswordField
name="mariadbHost"
id="mariadbHost"
value={service.appwrite.mariadbHost}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPort">MariaDB Port</label>
<CopyPasswordField
name="mariadbPort"
id="mariadbPort"
value={service.appwrite.mariadbPort}
readonly
disabled
/>
</div> -->
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbUser">{$t('forms.username')}</label>
<CopyPasswordField
name="mariadbUser"
id="mariadbUser"
value={service.appwrite.mariadbUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="mariadbPassword"
isPasswordField
readonly
disabled
name="mariadbPassword"
value={service.appwrite.mariadbPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUser">Root User</label>
<CopyPasswordField
name="mariadbRootUser"
id="mariadbRootUser"
value={service.appwrite.mariadbRootUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUserPassword">Root Password</label>
<CopyPasswordField
id="mariadbRootUserPassword"
isPasswordField
readonly
disabled
name="mariadbRootUserPassword"
value={service.appwrite.mariadbRootUserPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbDatabase">{$t('index.database')}</label>
<CopyPasswordField
name="mariadbDatabase"
id="mariadbDatabase"
value={service.appwrite.mariadbDatabase}
readonly
disabled
/>
</div>

View File

@ -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 @@
<Hasura bind:service />
{:else if service.type === 'fider'}
<Fider bind:service {readOnly} />
{:else if service.type === 'appwrite'}
<Appwrite bind:service {readOnly} />
{:else if service.type === 'moodle'}
<Moodle bind:service {readOnly} />
{/if}

View File

@ -35,7 +35,6 @@
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
let recommendedVersion = supportedServiceTypesAndVersions.find(
({ name }) => name === type
)?.recommendedVersion;

View File

@ -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": {