From 8d3ca92fe92b1c5dba4f65589359c37d5736020e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 12 Aug 2022 11:48:38 +0200 Subject: [PATCH] feat: Databases on ARM --- apps/api/src/jobs/checkProxies.ts | 5 +- apps/api/src/lib/common.ts | 107 +++++++++++++++--- .../src/routes/api/v1/databases/handlers.ts | 22 ++-- apps/ui/src/lib/common.ts | 29 ----- .../[id]/_Databases/_Databases.svelte | 12 +- package.json | 2 +- 6 files changed, 115 insertions(+), 62 deletions(-) diff --git a/apps/api/src/jobs/checkProxies.ts b/apps/api/src/jobs/checkProxies.ts index e307ef858..992281c3a 100644 --- a/apps/api/src/jobs/checkProxies.ts +++ b/apps/api/src/jobs/checkProxies.ts @@ -1,10 +1,11 @@ import { parentPort } from 'node:worker_threads'; -import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd } from '../lib/common'; +import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd, listSettings } from '../lib/common'; import { checkContainer } from '../lib/docker'; (async () => { if (parentPort) { try { + const { arch } = await listSettings(); // Coolify Proxy local const engine = '/var/run/docker.sock'; const localDocker = await prisma.destinationDocker.findFirst({ @@ -30,7 +31,7 @@ import { checkContainer } from '../lib/docker'; for (const database of databasesWithPublicPort) { const { destinationDockerId, destinationDocker, publicPort, id } = database; if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { - const { privatePort } = generateDatabaseConfiguration(database); + const { privatePort } = generateDatabaseConfiguration(database, arch); // Remove HAProxy const found = await checkContainer({ dockerId: localDocker.id, container: `haproxy-for-${publicPort}` diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index ccbd969c9..45945962f 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -17,7 +17,7 @@ import { checkContainer, removeContainer } from './docker'; import { day } from './dayjs'; import * as serviceFields from './serviceFields' -export const version = '3.2.3'; +export const version = '3.3.0'; export const isDev = process.env.NODE_ENV === 'development'; const algorithm = 'aes-256-ctr'; @@ -462,26 +462,46 @@ export const supportedDatabaseTypesAndVersions = [ baseImage: 'bitnami/mongodb', versions: ['5.0', '4.4', '4.2'] }, - { name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] }, + { + 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', - versions: ['10.8', '10.7', '10.6', '10.5', '10.4', '10.3', '10.2'] + 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', - versions: ['14.4.0', '13.6.0', '12.10.0', '11.15.0', '10.20.0'] + 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', - versions: ['7.0', '6.2', '6.0', '5.0'] + 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', versions: ['3.2.2'] } + { + 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'] + } ]; export async function getFreeSSHLocalPort(id: string): Promise { @@ -674,10 +694,11 @@ export function generatePassword(length = 24, symbols = false): string { }); } -export function generateDatabaseConfiguration(database: any): +export function generateDatabaseConfiguration(database: any, arch: string): | { volume: string; image: string; + command?: string; ulimits: Record; privatePort: number; environmentVariables: { @@ -691,6 +712,7 @@ export function generateDatabaseConfiguration(database: any): | { volume: string; image: string; + command?: string; ulimits: Record; privatePort: number; environmentVariables: { @@ -701,6 +723,7 @@ export function generateDatabaseConfiguration(database: any): | { volume: string; image: string; + command?: string; ulimits: Record; privatePort: number; environmentVariables: { @@ -714,6 +737,7 @@ export function generateDatabaseConfiguration(database: any): | { volume: string; image: string; + command?: string; ulimits: Record; privatePort: number; environmentVariables: { @@ -726,6 +750,19 @@ export function generateDatabaseConfiguration(database: any): | { volume: string; image: string; + command?: string; + ulimits: Record; + privatePort: number; + environmentVariables: { + POSTGRES_USER: string; + POSTGRES_PASSWORD: string; + POSTGRES_DB: string; + }; + } + | { + volume: string; + image: string; + command?: string; ulimits: Record; privatePort: number; environmentVariables: { @@ -736,6 +773,7 @@ export function generateDatabaseConfiguration(database: any): | { volume: string; image: string; + command?: string; ulimits: Record; privatePort: number; environmentVariables: { @@ -754,9 +792,9 @@ export function generateDatabaseConfiguration(database: any): type, settings: { appendOnly } } = database; - const baseImage = getDatabaseImage(type); + const baseImage = getDatabaseImage(type, arch); if (type === 'mysql') { - return { + const configuration = { privatePort: 3306, environmentVariables: { MYSQL_USER: dbUser, @@ -768,9 +806,13 @@ export function generateDatabaseConfiguration(database: any): 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') { - return { + const configuration = { privatePort: 3306, environmentVariables: { MARIADB_ROOT_USER: rootUser, @@ -783,6 +825,10 @@ export function generateDatabaseConfiguration(database: any): volume: `${id}-${type}-data:/bitnami/mariadb`, ulimits: {} }; + if (!isARM(arch)) { + configuration.volume = `${id}-${type}-data:/var/lib/mysql`; + } + return configuration } else if (type === 'mongodb') { return { privatePort: 27017, @@ -795,7 +841,7 @@ export function generateDatabaseConfiguration(database: any): ulimits: {} }; } else if (type === 'postgresql') { - return { + const configuration = { privatePort: 5432, environmentVariables: { POSTGRESQL_POSTGRES_PASSWORD: rootUserPassword, @@ -806,10 +852,15 @@ export function generateDatabaseConfiguration(database: any): image: `${baseImage}:${version}`, volume: `${id}-${type}-data:/bitnami/postgresql`, ulimits: {} - }; + } + if (!isARM(arch)) { + configuration.volume = `${id}-${type}-data:/var/lib/postgresql`; + } + return configuration } else if (type === 'redis') { - return { + const configuration = { privatePort: 6379, + command: undefined, environmentVariables: { REDIS_PASSWORD: dbUserPassword, REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no' @@ -818,8 +869,13 @@ export function generateDatabaseConfiguration(database: any): 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') { - return { + const configuration = { privatePort: 5984, environmentVariables: { COUCHDB_PASSWORD: dbUserPassword, @@ -829,20 +885,35 @@ export function generateDatabaseConfiguration(database: any): volume: `${id}-${type}-data:/bitnami/couchdb`, ulimits: {} }; + if (!isARM(arch)) { + configuration.volume = `${id}-${type}-data:/opt/couchdb/data`; + } + return configuration } } - -export function getDatabaseImage(type: string): string { +export function isARM(arch) { + if (arch === 'arm' || arch === 'arm64') { + return true + } + return false +} +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 getDatabaseVersions(type: string): string[] { +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 []; diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index ca5fc67c0..5d3bcac51 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -3,7 +3,7 @@ import type { FastifyRequest } from 'fastify'; import { FastifyReply } from 'fastify'; import yaml from 'js-yaml'; import fs from 'fs/promises'; -import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; +import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, isARM, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import { checkContainer } from '../../../../lib/docker'; import { day } from '../../../../lib/dayjs'; @@ -93,14 +93,15 @@ export async function getDatabase(request: FastifyRequest) { if (!database) { throw { status: 404, message: 'Database not found.' } } + const { arch } = await listSettings(); if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - const configuration = generateDatabaseConfiguration(database); + const configuration = generateDatabaseConfiguration(database, arch); const settings = await listSettings(); return { privatePort: configuration?.privatePort, database, - versions: await getDatabaseVersions(database.type), + versions: await getDatabaseVersions(database.type, arch), settings }; } catch ({ status, message }) { @@ -137,8 +138,10 @@ export async function getVersions(request: FastifyRequest) { where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }, include: { destinationDocker: true, settings: true } }); + const { arch } = await listSettings(); + const versions = getDatabaseVersions(type, arch); return { - versions: supportedDatabaseTypesAndVersions.find((name) => name.name === type).versions + versions } } catch ({ status, message }) { return errorHandler({ status, message }) @@ -219,6 +222,7 @@ export async function startDatabase(request: FastifyRequest) { 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 { @@ -228,8 +232,8 @@ export async function startDatabase(request: FastifyRequest) { publicPort, settings: { isPublic } } = database; - const { privatePort, environmentVariables, image, volume, ulimits } = - generateDatabaseConfiguration(database); + const { privatePort, command, environmentVariables, image, volume, ulimits } = + generateDatabaseConfiguration(database, arch); const network = destinationDockerId && destinationDocker.network; const volumeName = volume.split(':')[0]; @@ -243,6 +247,7 @@ export async function startDatabase(request: FastifyRequest) { [id]: { container_name: id, image, + command, networks: [network], environment: environmentVariables, volumes: [volume], @@ -270,6 +275,7 @@ export async function startDatabase(request: FastifyRequest) { } } }; + const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); try { @@ -282,6 +288,7 @@ export async function startDatabase(request: FastifyRequest) { if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); return {}; } catch (error) { + console.log(error) throw { error }; @@ -440,11 +447,12 @@ export async function saveDatabaseSettings(request: FastifyRequest { const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service); if (serviceType) { diff --git a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte index 5b4617178..8a59301f2 100644 --- a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte +++ b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -58,7 +58,9 @@ } async function changeSettings(name: any) { - if (publicLoading || !$status.database.isRunning) return; + if (name !== 'appendOnly') { + if (publicLoading || !$status.database.isRunning || name !== 'appendOnly') return; + } publicLoading = true; let data = { isPublic, @@ -92,9 +94,9 @@ await post(`/databases/${id}`, { ...database, isRunning: $status.database.isRunning }); generateDbDetails(); addToast({ - message: 'Configuration saved.', - type: 'success' - }); + message: 'Configuration saved.', + type: 'success' + }); } catch (error) { return errorNotification(error); } finally { @@ -111,7 +113,7 @@ diff --git a/package.json b/package.json index 751b76f3b..cc558daff 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "3.2.3", + "version": "3.3.0", "license": "Apache-2.0", "repository": "github:coollabsio/coolify", "scripts": {