wip: trpc

This commit is contained in:
Andras Bacsai
2023-01-13 14:54:21 +01:00
parent 568ab24fd9
commit 97313e4180
18 changed files with 1951 additions and 86 deletions

View File

@@ -10,6 +10,8 @@ import { env } from '../env';
import { day } from './dayjs';
import { executeCommand } from './executeCommand';
import { saveBuildLog } from './logging';
import { checkContainer } from './docker';
import yaml from 'js-yaml';
const customConfig: Config = {
dictionaries: [adjectives, colors, animals],
@@ -22,6 +24,37 @@ export const isDev = env.NODE_ENV === 'development';
export const version = '3.13.0';
export const sentryDSN =
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
export const defaultTraefikImage = `traefik:v2.8`;
export function getAPIUrl() {
if (process.env.GITPOD_WORKSPACE_URL) {
const { href } = new URL(process.env.GITPOD_WORKSPACE_URL);
const newURL = href.replace('https://', 'https://3001-').replace(/\/$/, '');
return newURL;
}
if (process.env.CODESANDBOX_HOST) {
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3001')}`;
}
return isDev ? 'http://host.docker.internal:3001' : 'http://localhost:3000';
}
export function getUIUrl() {
if (process.env.GITPOD_WORKSPACE_URL) {
const { href } = new URL(process.env.GITPOD_WORKSPACE_URL);
const newURL = href.replace('https://', 'https://3000-').replace(/\/$/, '');
return newURL;
}
if (process.env.CODESANDBOX_HOST) {
return `https://${process.env.CODESANDBOX_HOST.replace(/\$PORT/, '3000')}`;
}
return 'http://localhost:3000';
}
const mainTraefikEndpoint = isDev
? `${getAPIUrl()}/webhooks/traefik/main.json`
: 'http://coolify:3000/webhooks/traefik/main.json';
const otherTraefikEndpoint = isDev
? `${getAPIUrl()}/webhooks/traefik/other.json`
: 'http://coolify:3000/webhooks/traefik/other.json';
export async function listSettings(): Promise<Setting | null> {
return await prisma.setting.findUnique({ where: { id: '0' } });
@@ -707,4 +740,86 @@ export function makeLabelForServices(type) {
];
}
export const asyncSleep = (delay: number): Promise<unknown> =>
new Promise((resolve) => setTimeout(resolve, delay));
new Promise((resolve) => setTimeout(resolve, delay));
export async function startTraefikTCPProxy(
destinationDocker: any,
id: string,
publicPort: number,
privatePort: number,
type?: string
): Promise<{ stdout: string; stderr: string }> {
const { network, id: dockerId, remoteEngine } = destinationDocker;
const container = `${id}-${publicPort}`;
const { found } = await checkContainer({ dockerId, container, remove: true });
const { ipv4, ipv6 } = await listSettings();
let dependentId = id;
if (type === 'wordpressftp') dependentId = `${id}-ftp`;
const { found: foundDependentContainer } = await checkContainer({
dockerId,
container: dependentId,
remove: true
});
if (foundDependentContainer && !found) {
const { stdout: Config } = await executeCommand({
dockerId,
command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'`
});
const ip = JSON.parse(Config)[0].Gateway;
let traefikUrl = otherTraefikEndpoint;
if (remoteEngine) {
let ip = null;
if (isDev) {
ip = getAPIUrl();
} else {
ip = `http://${ipv4 || ipv6}:3000`;
}
traefikUrl = `${ip}/webhooks/traefik/other.json`;
}
const tcpProxy = {
version: '3.8',
services: {
[`${id}-${publicPort}`]: {
container_name: container,
image: defaultTraefikImage,
command: [
`--entrypoints.tcp.address=:${publicPort}`,
`--entryPoints.tcp.forwardedHeaders.insecure=true`,
`--providers.http.endpoint=${traefikUrl}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp&address=${dependentId}`,
'--providers.http.pollTimeout=10s',
'--log.level=error'
],
ports: [`${publicPort}:${publicPort}`],
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal: ${ip}`],
volumes: ['/var/run/docker.sock:/var/run/docker.sock'],
networks: ['coolify-infra', network]
}
},
networks: {
[network]: {
external: false,
name: network
},
'coolify-infra': {
external: false,
name: 'coolify-infra'
}
}
};
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
await executeCommand({
dockerId,
command: `docker compose -f /tmp/docker-compose-${id}.yaml up -d`
});
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
}
if (!foundDependentContainer && found) {
await executeCommand({
dockerId,
command: `docker stop -t 0 ${container} && docker rm ${container}`,
shell: true
});
}
}

View File

@@ -1,84 +0,0 @@
import { z } from 'zod';
import { privateProcedure, router } from '../trpc';
import { decrypt } from '../../lib/common';
import { prisma } from '../../prisma';
import { executeCommand } from '../../lib/executeCommand';
import { stopDatabaseContainer, stopTcpHttpProxy } from '../../lib/docker';
export const databasesRouter = router({
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
const id = input.id;
const teamId = ctx.user?.teamId;
let isRunning = false;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database) {
const { destinationDockerId, destinationDocker } = database;
if (destinationDockerId) {
try {
const { stdout } = await executeCommand({
dockerId: destinationDocker.id,
command: `docker inspect --format '{{json .State}}' ${id}`
});
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
}
return {
isRunning
};
}),
cleanup: privateProcedure.query(async ({ ctx }) => {
const teamId = ctx.user?.teamId;
let databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }
});
for (const database of databases) {
if (!database?.version) {
const { id } = database;
if (database.destinationDockerId) {
const everStarted = await stopDatabaseContainer(database);
if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
}
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } });
}
}
return {};
}),
delete: privateProcedure
.input(z.object({ id: z.string(), force: z.boolean() }))
.mutation(async ({ ctx, input }) => {
const { id, force } = input;
const teamId = ctx.user?.teamId;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (!force) {
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword)
database.rootUserPassword = decrypt(database.rootUserPassword);
if (database.destinationDockerId) {
const everStarted = await stopDatabaseContainer(database);
if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
}
}
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } });
return {};
})
});

View File

@@ -0,0 +1,379 @@
import { z } from 'zod';
import fs from 'fs/promises';
import { privateProcedure, router } from '../../trpc';
import {
createDirectories,
decrypt,
encrypt,
getContainerUsage,
listSettings,
startTraefikTCPProxy
} from '../../../lib/common';
import { prisma } from '../../../prisma';
import { executeCommand } from '../../../lib/executeCommand';
import {
defaultComposeConfiguration,
stopDatabaseContainer,
stopTcpHttpProxy
} from '../../../lib/docker';
import {
generateDatabaseConfiguration,
getDatabaseVersions,
makeLabelForStandaloneDatabase,
updatePasswordInDb
} from './lib';
import yaml from 'js-yaml';
import { getFreePublicPort } from '../services/lib';
export const databasesRouter = router({
usage: privateProcedure
.input(
z.object({
id: z.string()
})
)
.query(async ({ ctx, input }) => {
const teamId = ctx.user?.teamId;
const { id } = input;
let usage = {};
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
if (database.destinationDockerId) {
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.id, id)]);
}
return {
success: true,
data: {
usage
}
};
}),
save: privateProcedure
.input(
z.object({
id: z.string()
})
)
.mutation(async ({ ctx, input }) => {
const teamId = ctx.user?.teamId;
const {
id,
name,
defaultDatabase,
dbUser,
dbUserPassword,
rootUser,
rootUserPassword,
version,
isRunning
} = input;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
if (isRunning) {
if (database.dbUserPassword !== dbUserPassword) {
await updatePasswordInDb(database, dbUser, dbUserPassword, false);
} else if (database.rootUserPassword !== rootUserPassword) {
await updatePasswordInDb(database, rootUser, rootUserPassword, true);
}
}
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
await prisma.database.update({
where: { id },
data: {
name,
defaultDatabase,
dbUser,
dbUserPassword: encryptedDbUserPassword,
rootUser,
rootUserPassword: encryptedRootUserPassword,
version
}
});
}),
saveSettings: privateProcedure
.input(
z.object({
id: z.string(),
isPublic: z.boolean(),
appendOnly: z.boolean().default(true)
})
)
.mutation(async ({ ctx, input }) => {
const teamId = ctx.user?.teamId;
const { id, isPublic, appendOnly = true } = input;
let publicPort = null;
const {
destinationDocker: { remoteEngine, engine, remoteIpAddress }
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
if (isPublic) {
publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress });
}
await prisma.database.update({
where: { id },
data: {
settings: {
upsert: { update: { isPublic, appendOnly }, create: { isPublic, appendOnly } }
}
}
});
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
const { arch } = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
const { privatePort } = generateDatabaseConfiguration(database, arch);
if (destinationDockerId) {
if (isPublic) {
await prisma.database.update({ where: { id }, data: { publicPort } });
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
} else {
await prisma.database.update({ where: { id }, data: { publicPort: null } });
await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);
}
}
return { publicPort };
}),
saveSecret: privateProcedure
.input(
z.object({
id: z.string(),
name: z.string(),
value: z.string(),
isNew: z.boolean().default(true)
})
)
.mutation(async ({ ctx, input }) => {
let { id, name, value, isNew } = input;
if (isNew) {
const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } });
if (found) {
throw `Secret ${name} already exists.`;
} else {
value = encrypt(value.trim());
await prisma.databaseSecret.create({
data: { name, value, database: { connect: { id } } }
});
}
} else {
value = encrypt(value.trim());
const found = await prisma.databaseSecret.findFirst({ where: { databaseId: id, name } });
if (found) {
await prisma.databaseSecret.updateMany({
where: { databaseId: id, name },
data: { value }
});
} else {
await prisma.databaseSecret.create({
data: { name, value, database: { connect: { id } } }
});
}
}
}),
start: privateProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true, databaseSecret: true }
});
const { arch } = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const {
type,
destinationDockerId,
destinationDocker,
publicPort,
settings: { isPublic },
databaseSecret
} = database;
const { privatePort, command, environmentVariables, image, volume, ulimits } =
generateDatabaseConfiguration(database, arch);
const network = destinationDockerId && destinationDocker.network;
const volumeName = volume.split(':')[0];
const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
const { workdir } = await createDirectories({ repository: type, buildId: id });
if (databaseSecret.length > 0) {
databaseSecret.forEach((secret) => {
environmentVariables[secret.name] = decrypt(secret.value);
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image,
command,
environment: environmentVariables,
volumes: [volume],
ulimits,
labels,
...defaultComposeConfiguration(network)
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[volumeName]: {
name: volumeName
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await executeCommand({
dockerId: destinationDocker.id,
command: `docker compose -f ${composeFileDestination} up -d`
});
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
}),
stop: privateProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const everStarted = await stopDatabaseContainer(database);
if (everStarted) await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
await prisma.database.update({
where: { id },
data: {
settings: { upsert: { update: { isPublic: false }, create: { isPublic: false } } }
}
});
await prisma.database.update({ where: { id }, data: { publicPort: null } });
}),
getDatabaseById: privateProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const { id } = input;
const teamId = ctx.user?.teamId;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (!database) {
throw { status: 404, message: 'Database not found.' };
}
const settings = await listSettings();
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const configuration = generateDatabaseConfiguration(database, settings.arch);
return {
success: true,
data: {
privatePort: configuration?.privatePort,
database,
versions: await getDatabaseVersions(database.type, settings.arch),
settings
}
};
}),
status: privateProcedure.input(z.object({ id: z.string() })).query(async ({ ctx, input }) => {
const id = input.id;
const teamId = ctx.user?.teamId;
let isRunning = false;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (database) {
const { destinationDockerId, destinationDocker } = database;
if (destinationDockerId) {
try {
const { stdout } = await executeCommand({
dockerId: destinationDocker.id,
command: `docker inspect --format '{{json .State}}' ${id}`
});
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
}
return {
success: true,
data: {
isRunning
}
};
}),
cleanup: privateProcedure.query(async ({ ctx }) => {
const teamId = ctx.user?.teamId;
let databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { settings: true, destinationDocker: true, teams: true }
});
for (const database of databases) {
if (!database?.version) {
const { id } = database;
if (database.destinationDockerId) {
const everStarted = await stopDatabaseContainer(database);
if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
}
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } });
}
}
return {};
}),
delete: privateProcedure
.input(z.object({ id: z.string(), force: z.boolean().default(false) }))
.mutation(async ({ ctx, input }) => {
const { id, force } = input;
const teamId = ctx.user?.teamId;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
if (!force) {
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword)
database.rootUserPassword = decrypt(database.rootUserPassword);
if (database.destinationDockerId) {
const everStarted = await stopDatabaseContainer(database);
if (everStarted)
await stopTcpHttpProxy(id, database.destinationDocker, database.publicPort);
}
}
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
await prisma.databaseSecret.deleteMany({ where: { databaseId: id } });
await prisma.database.delete({ where: { id } });
return {};
})
});

View File

@@ -0,0 +1,283 @@
import { base64Encode, isARM, version } from "../../../lib/common";
import { executeCommand } from "../../../lib/executeCommand";
import { prisma } from "../../../prisma";
export const supportedDatabaseTypesAndVersions = [
{
name: 'mongodb',
fancyName: 'MongoDB',
baseImage: 'bitnami/mongodb',
baseImageARM: 'mongo',
versions: ['5.0', '4.4', '4.2'],
versionsARM: ['5.0', '4.4', '4.2']
},
{
name: 'mysql',
fancyName: 'MySQL',
baseImage: 'bitnami/mysql',
baseImageARM: 'mysql',
versions: ['8.0', '5.7'],
versionsARM: ['8.0', '5.7']
},
{
name: 'mariadb',
fancyName: 'MariaDB',
baseImage: 'bitnami/mariadb',
baseImageARM: 'mariadb',
versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'],
versionsARM: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
},
{
name: 'postgresql',
fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql',
baseImageARM: 'postgres',
versions: ['14.5.0', '13.8.0', '12.12.0', '11.17.0', '10.22.0'],
versionsARM: ['14.5', '13.8', '12.12', '11.17', '10.22']
},
{
name: 'redis',
fancyName: 'Redis',
baseImage: 'bitnami/redis',
baseImageARM: 'redis',
versions: ['7.0', '6.2', '6.0', '5.0'],
versionsARM: ['7.0', '6.2', '6.0', '5.0']
},
{
name: 'couchdb',
fancyName: 'CouchDB',
baseImage: 'bitnami/couchdb',
baseImageARM: 'couchdb',
versions: ['3.2.2', '3.1.2', '2.3.1'],
versionsARM: ['3.2.2', '3.1.2', '2.3.1']
},
{
name: 'edgedb',
fancyName: 'EdgeDB',
baseImage: 'edgedb/edgedb',
versions: ['latest', '2.1', '2.0', '1.4']
}
];
export function getDatabaseImage(type: string, arch: string): string {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) {
if (isARM(arch)) {
return found.baseImageARM || found.baseImage;
}
return found.baseImage;
}
return '';
}
export function generateDatabaseConfiguration(database: any, arch: string) {
const { id, dbUser, dbUserPassword, rootUser, rootUserPassword, defaultDatabase, version, type } =
database;
const baseImage = getDatabaseImage(type, arch);
if (type === 'mysql') {
const configuration = {
privatePort: 3306,
environmentVariables: {
MYSQL_USER: dbUser,
MYSQL_PASSWORD: dbUserPassword,
MYSQL_ROOT_PASSWORD: rootUserPassword,
MYSQL_ROOT_USER: rootUser,
MYSQL_DATABASE: defaultDatabase
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/mysql/data`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
}
return configuration;
} else if (type === 'mariadb') {
const configuration = {
privatePort: 3306,
environmentVariables: {
MARIADB_ROOT_USER: rootUser,
MARIADB_ROOT_PASSWORD: rootUserPassword,
MARIADB_USER: dbUser,
MARIADB_PASSWORD: dbUserPassword,
MARIADB_DATABASE: defaultDatabase
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/mariadb`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/var/lib/mysql`;
}
return configuration;
} else if (type === 'mongodb') {
const configuration = {
privatePort: 27017,
environmentVariables: {
MONGODB_ROOT_USER: rootUser,
MONGODB_ROOT_PASSWORD: rootUserPassword
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/mongodb`,
ulimits: {}
};
if (isARM(arch)) {
configuration.environmentVariables = {
MONGO_INITDB_ROOT_USERNAME: rootUser,
MONGO_INITDB_ROOT_PASSWORD: rootUserPassword
};
configuration.volume = `${id}-${type}-data:/data/db`;
}
return configuration;
} else if (type === 'postgresql') {
const configuration = {
privatePort: 5432,
environmentVariables: {
POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword,
POSTGRESQL_PASSWORD: dbUserPassword,
POSTGRESQL_USERNAME: dbUser,
POSTGRESQL_DATABASE: defaultDatabase
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/postgresql`,
ulimits: {}
};
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') {
const {
settings: { appendOnly }
} = database;
const configuration = {
privatePort: 6379,
command: undefined,
environmentVariables: {
REDIS_PASSWORD: dbUserPassword,
REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no'
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/redis/data`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/data`;
configuration.command = `/usr/local/bin/redis-server --appendonly ${
appendOnly ? 'yes' : 'no'
} --requirepass ${dbUserPassword}`;
}
return configuration;
} else if (type === 'couchdb') {
const configuration = {
privatePort: 5984,
environmentVariables: {
COUCHDB_PASSWORD: dbUserPassword,
COUCHDB_USER: dbUser
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/bitnami/couchdb`,
ulimits: {}
};
if (isARM(arch)) {
configuration.volume = `${id}-${type}-data:/opt/couchdb/data`;
}
return configuration;
} else if (type === 'edgedb') {
const configuration = {
privatePort: 5656,
environmentVariables: {
EDGEDB_SERVER_PASSWORD: rootUserPassword,
EDGEDB_SERVER_USER: rootUser,
EDGEDB_SERVER_DATABASE: defaultDatabase,
EDGEDB_SERVER_TLS_CERT_MODE: 'generate_self_signed'
},
image: `${baseImage}:${version}`,
volume: `${id}-${type}-data:/var/lib/edgedb/data`,
ulimits: {}
};
return configuration;
}
return null;
}
export function getDatabaseVersions(type: string, arch: string): string[] {
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
if (found) {
if (isARM(arch)) {
return found.versionsARM || found.versions;
}
return found.versions;
}
return [];
}
export async function updatePasswordInDb(database, user, newPassword, isRoot) {
const {
id,
type,
rootUser,
rootUserPassword,
dbUser,
dbUserPassword,
defaultDatabase,
destinationDockerId,
destinationDocker: { id: dockerId }
} = database;
if (destinationDockerId) {
if (type === 'mysql') {
await executeCommand({
dockerId,
command: `docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"ALTER USER '${user}'@'%' IDENTIFIED WITH caching_sha2_password BY '${newPassword}';\"`
});
} else if (type === 'mariadb') {
await executeCommand({
dockerId,
command: `docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"SET PASSWORD FOR '${user}'@'%' = PASSWORD('${newPassword}');\"`
});
} else if (type === 'postgresql') {
if (isRoot) {
await executeCommand({
dockerId,
command: `docker exec ${id} psql postgresql://postgres:${rootUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role postgres WITH PASSWORD '${newPassword}'"`
});
} else {
await executeCommand({
dockerId,
command: `docker exec ${id} psql postgresql://${dbUser}:${dbUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role ${user} WITH PASSWORD '${newPassword}'"`
});
}
} else if (type === 'mongodb') {
await executeCommand({
dockerId,
command: `docker exec ${id} mongo 'mongodb://${rootUser}:${rootUserPassword}@${id}:27017/admin?readPreference=primary&ssl=false' --eval "db.changeUserPassword('${user}','${newPassword}')"`
});
} else if (type === 'redis') {
await executeCommand({
dockerId,
command: `docker exec ${id} redis-cli -u redis://${dbUserPassword}@${id}:6379 --raw CONFIG SET requirepass ${newPassword}`
});
}
}
}
export async function makeLabelForStandaloneDatabase({ id, image, volume }) {
const database = await prisma.database.findFirst({ where: { id } });
delete database.destinationDockerId;
delete database.createdAt;
delete database.updatedAt;
return [
'coolify.managed=true',
`coolify.version=${version}`,
`coolify.type=standalone-database`,
`coolify.name=${database.name}`,
`coolify.configuration=${base64Encode(
JSON.stringify({
version,
image,
volume,
...database
})
)}`
];
}