From 2fd001f6d25f41ca96774038b3367cfa32240e70 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Mon, 16 Jan 2023 10:06:41 +0100 Subject: [PATCH] fix: docker log sequence --- .../routes/api/v1/applications/handlers.ts | 7 +- .../src/routes/api/v1/databases/handlers.ts | 1027 +++++---- .../src/routes/api/v1/services/handlers.ts | 1948 +++++++++-------- .../src/trpc/routers/applications/index.ts | 7 +- .../server/src/trpc/routers/services/index.ts | 8 +- 5 files changed, 1617 insertions(+), 1380 deletions(-) diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 66186a178..3a47f7a08 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -1605,12 +1605,7 @@ export async function getApplicationLogs(request: FastifyRequest ansi(l)) .filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout); - const sortedLogs = logs.sort((a, b) => - day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1 - ); - return { logs: sortedLogs }; - // } + return { logs: stripLogsStderr.concat(stripLogsStdout) }; } catch (error) { const { statusCode, stderr } = error; if (stderr.startsWith('Error: No such container')) { diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index 20bf285b1..6127ac25e 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -3,550 +3,609 @@ import type { FastifyRequest } from 'fastify'; import { FastifyReply } from 'fastify'; import yaml from 'js-yaml'; import fs from 'fs/promises'; -import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeCommand, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; +import { + ComposeFile, + createDirectories, + decrypt, + defaultComposeConfiguration, + encrypt, + errorHandler, + executeCommand, + generateDatabaseConfiguration, + generatePassword, + getContainerUsage, + getDatabaseImage, + getDatabaseVersions, + getFreePublicPort, + listSettings, + makeLabelForStandaloneDatabase, + prisma, + startTraefikTCPProxy, + stopDatabaseContainer, + stopTcpHttpProxy, + supportedDatabaseTypesAndVersions, + uniqueName, + updatePasswordInDb +} from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; import type { OnlyId } from '../../../../types'; -import type { DeleteDatabase, DeleteDatabaseSecret, GetDatabaseLogs, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveDatabaseType, SaveVersion } from './types'; +import type { + DeleteDatabase, + DeleteDatabaseSecret, + GetDatabaseLogs, + SaveDatabase, + SaveDatabaseDestination, + SaveDatabaseSecret, + SaveDatabaseSettings, + SaveDatabaseType, + SaveVersion +} from './types'; export async function listDatabases(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { teams: true, destinationDocker: true } - }); - return { - databases - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const databases = await prisma.database.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true, destinationDocker: true } + }); + return { + databases + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function newDatabase(request: FastifyRequest, reply: FastifyReply) { - try { - const teamId = request.user.teamId; + try { + const teamId = request.user.teamId; - const name = uniqueName(); - const dbUser = cuid(); - const dbUserPassword = encrypt(generatePassword({})); - const rootUser = cuid(); - const rootUserPassword = encrypt(generatePassword({})); - const defaultDatabase = cuid(); + const name = uniqueName(); + const dbUser = cuid(); + const dbUserPassword = encrypt(generatePassword({})); + const rootUser = cuid(); + const rootUserPassword = encrypt(generatePassword({})); + const defaultDatabase = cuid(); - const { id } = await prisma.database.create({ - data: { - name, - defaultDatabase, - dbUser, - dbUserPassword, - rootUser, - rootUserPassword, - teams: { connect: { id: teamId } }, - settings: { create: { isPublic: false } } - } - }); - return reply.code(201).send({ id }) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const { id } = await prisma.database.create({ + data: { + name, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + teams: { connect: { id: teamId } }, + settings: { create: { isPublic: false } } + } + }); + return reply.code(201).send({ id }); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function cleanupUnconfiguredDatabases(request: FastifyRequest) { - try { - const teamId = request.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 {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.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 {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseStatus(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - let isRunning = false; + try { + const { id } = request.params; + const teamId = request.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}` }) + 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 - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (JSON.parse(stdout).Running) { + isRunning = true; + } + } catch (error) { + // + } + } + } + return { + isRunning + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabase(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.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 { - privatePort: configuration?.privatePort, - database, - versions: await getDatabaseVersions(database.type, settings.arch), - settings - }; - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const teamId = request.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 { + privatePort: configuration?.privatePort, + database, + versions: await getDatabaseVersions(database.type, settings.arch), + settings + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseTypes(request: FastifyRequest) { - try { - return { - types: supportedDatabaseTypesAndVersions - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + return { + types: supportedDatabaseTypesAndVersions + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveDatabaseType(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { type } = request.body; - await prisma.database.update({ - where: { id }, - data: { type } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveDatabaseType( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { type } = request.body; + await prisma.database.update({ + where: { id }, + data: { type } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getVersions(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { type } = await prisma.database.findFirst({ - 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 - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { type } = await prisma.database.findFirst({ + 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 + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveVersion(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { version } = request.body; + try { + const { id } = request.params; + const { version } = request.body; - await prisma.database.update({ - where: { id }, - data: { - version, - } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + await prisma.database.update({ + where: { id }, + data: { + version + } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveDatabaseDestination(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { destinationId } = request.body; +export async function saveDatabaseDestination( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { destinationId } = request.body; - const { arch } = await listSettings(); - await prisma.database.update({ - where: { id }, - data: { destinationDocker: { connect: { id: destinationId } } } - }); + const { arch } = await listSettings(); + await prisma.database.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); - const { - destinationDockerId, - destinationDocker: { engine, id: dockerId }, - version, - type - } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); + const { + destinationDockerId, + destinationDocker: { engine, id: dockerId }, + version, + type + } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); - if (destinationDockerId) { - if (type && version) { - const baseImage = getDatabaseImage(type, arch); - executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` }) - } - } - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (destinationDockerId) { + if (type && version) { + const baseImage = getDatabaseImage(type, arch); + executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` }); + } + } + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseUsage(request: FastifyRequest) { - try { - const { id } = request.params; - const teamId = request.user.teamId; - let usage = {}; + try { + const { id } = request.params; + const teamId = request.user.teamId; + 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 { - usage - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + 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 { + usage + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function startDatabase(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; + try { + const teamId = request.user.teamId; + const { id } = request.params; - 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 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 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: 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); - return {}; - - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const { workdir } = await createDirectories({ repository: type, buildId: id }); + if (databaseSecret.length > 0) { + databaseSecret.forEach((secret) => { + environmentVariables[secret.name] = decrypt(secret.value); + }); + } + const composeFile: 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); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function stopDatabase(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - 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 } }); - return {}; - - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + 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 } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseLogs(request: FastifyRequest) { - try { - const { id } = request.params; - let { since = 0 } = request.query - if (since !== 0) { - since = day(since).unix(); - } - const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ - where: { id }, - include: { destinationDocker: true } - }); - if (destinationDockerId) { - try { - // const found = await checkContainer({ dockerId, container: id }) - // if (found) { - const { default: ansi } = await import('strip-ansi') - const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` }) - const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout) - const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) - return { logs: sortedLogs } - // } - } catch (error) { - const { statusCode } = error; - if (statusCode === 404) { - return { - logs: [] - }; - } - } - } - return { - message: 'No logs found.' - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + let { since = 0 } = request.query; + if (since !== 0) { + since = day(since).unix(); + } + const { + destinationDockerId, + destinationDocker: { id: dockerId } + } = await prisma.database.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (destinationDockerId) { + try { + const { default: ansi } = await import('strip-ansi'); + const { stdout, stderr } = await executeCommand({ + dockerId, + command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` + }); + const stripLogsStdout = stdout + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + const stripLogsStderr = stderr + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + return { logs: stripLogsStderr.concat(stripLogsStdout) }; + } catch (error) { + const { statusCode } = error; + if (statusCode === 404) { + return { + logs: [] + }; + } + } + } + return { + message: 'No logs found.' + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteDatabase(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { force } = request.body; - 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 {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { force } = request.body; + 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 {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveDatabase(request: FastifyRequest, reply: FastifyReply) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { - name, - defaultDatabase, - dbUser, - dbUserPassword, - rootUser, - rootUserPassword, - version, - isRunning - } = request.body; - 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 - } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { + name, + defaultDatabase, + dbUser, + dbUserPassword, + rootUser, + rootUserPassword, + version, + isRunning + } = request.body; + 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 + } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveDatabaseSettings(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const { isPublic, appendOnly = true } = request.body; + try { + const teamId = request.user.teamId; + const { id } = request.params; + const { isPublic, appendOnly = true } = request.body; - let publicPort = null + let publicPort = null; - const { destinationDocker: { remoteEngine, engine, remoteIpAddress } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }) + 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); + 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); + 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 } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + 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 }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getDatabaseSecrets(request: FastifyRequest) { - try { - const { id } = request.params - let secrets = await prisma.databaseSecret.findMany({ - where: { databaseId: id }, - orderBy: { createdAt: 'desc' } - }); - secrets = secrets.map((secret) => { - secret.value = decrypt(secret.value); - return secret; - }); + try { + const { id } = request.params; + let secrets = await prisma.databaseSecret.findMany({ + where: { databaseId: id }, + orderBy: { createdAt: 'desc' } + }); + secrets = secrets.map((secret) => { + secret.value = decrypt(secret.value); + return secret; + }); - return { - secrets - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + return { + secrets + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveDatabaseSecret(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - let { name, value, isNew } = request.body +export async function saveDatabaseSecret( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + let { name, value, isNew } = request.body; - 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 (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 } } } - }); - } - } - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (found) { + await prisma.databaseSecret.updateMany({ + where: { databaseId: id, name }, + data: { value } + }); + } else { + await prisma.databaseSecret.create({ + data: { name, value, database: { connect: { id } } } + }); + } + } + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteDatabaseSecret(request: FastifyRequest) { - try { - const { id } = request.params - const { name } = request.body - await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const { name } = request.body; + await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } diff --git a/apps/api/src/routes/api/v1/services/handlers.ts b/apps/api/src/routes/api/v1/services/handlers.ts index 0cf51b4ea..3b1319202 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -4,966 +4,1160 @@ import yaml from 'js-yaml'; import bcrypt from 'bcryptjs'; import cuid from 'cuid'; -import { prisma, uniqueName, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, checkDomainsIsValidInDNS, checkExposedPort, listSettings, generateToken, executeCommand } from '../../../../lib/common'; +import { + prisma, + uniqueName, + getServiceFromDB, + getContainerUsage, + isDomainConfigured, + fixType, + decrypt, + encrypt, + ComposeFile, + getFreePublicPort, + getDomain, + errorHandler, + generatePassword, + isDev, + stopTcpHttpProxy, + checkDomainsIsValidInDNS, + checkExposedPort, + listSettings, + generateToken, + executeCommand +} from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; -import { checkContainer, } from '../../../../lib/docker'; +import { checkContainer } from '../../../../lib/docker'; import { removeService } from '../../../../lib/services/common'; import { getTags, getTemplates } from '../../../../lib/services'; -import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetGlitchTipSettings, SetWordpressSettings } from './types'; +import type { + ActivateWordpressFtp, + CheckService, + CheckServiceDomain, + DeleteServiceSecret, + DeleteServiceStorage, + GetServiceLogs, + SaveService, + SaveServiceDestination, + SaveServiceSecret, + SaveServiceSettings, + SaveServiceStorage, + SaveServiceType, + SaveServiceVersion, + ServiceStartStop, + SetGlitchTipSettings, + SetWordpressSettings +} from './types'; import type { OnlyId } from '../../../../types'; export async function listServices(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { teams: true, destinationDocker: true } - }); - return { - services - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const services = await prisma.service.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true, destinationDocker: true } + }); + return { + services + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function newService(request: FastifyRequest, reply: FastifyReply) { - try { - const teamId = request.user.teamId; - const name = uniqueName(); + try { + const teamId = request.user.teamId; + const name = uniqueName(); - const { id } = await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } }); - return reply.status(201).send({ id }); - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const { id } = await prisma.service.create({ + data: { name, teams: { connect: { id: teamId } } } + }); + return reply.status(201).send({ id }); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function cleanupUnconfiguredServices(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - let services = await prisma.service.findMany({ - where: { teams: { some: { id: teamId === "0" ? undefined : teamId } } }, - include: { destinationDocker: true, teams: true }, - }); - for (const service of services) { - if (!service.fqdn) { - if (service.destinationDockerId) { - const { stdout: containers } = await executeCommand({ - dockerId: service.destinationDockerId, - command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}` - }) - if (containers) { - const containerArray = containers.split('\n'); - if (containerArray.length > 0) { - for (const container of containerArray) { - await executeCommand({ dockerId: service.destinationDockerId, command: `docker stop -t 0 ${container}` }) - await executeCommand({ dockerId: service.destinationDockerId, command: `docker rm --force ${container}` }) - } - } - } - } - await removeService({ id: service.id }); - } - } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + let services = await prisma.service.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { destinationDocker: true, teams: true } + }); + for (const service of services) { + if (!service.fqdn) { + if (service.destinationDockerId) { + const { stdout: containers } = await executeCommand({ + dockerId: service.destinationDockerId, + command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}` + }); + if (containers) { + const containerArray = containers.split('\n'); + if (containerArray.length > 0) { + for (const container of containerArray) { + await executeCommand({ + dockerId: service.destinationDockerId, + command: `docker stop -t 0 ${container}` + }); + await executeCommand({ + dockerId: service.destinationDockerId, + command: `docker rm --force ${container}` + }); + } + } + } + } + await removeService({ id: service.id }); + } + } + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceStatus(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, settings } = service; - let payload = {} - if (destinationDockerId) { - const { stdout: containers } = await executeCommand({ - dockerId: service.destinationDocker.id, - command: - `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'` - }); - if (containers) { - const containersArray = containers.trim().split('\n'); - if (containersArray.length > 0 && containersArray[0] !== '') { - const templates = await getTemplates(); - let template = templates.find(t => t.type === service.type); - const templateStr = JSON.stringify(template) - if (templateStr) { - template = JSON.parse(templateStr.replaceAll('$$id', service.id)); - } - for (const container of containersArray) { - let isRunning = false; - let isExited = false; - let isRestarting = false; - let isExcluded = false; - const containerObj = JSON.parse(container); - const exclude = template?.services[containerObj.Names]?.exclude; - if (exclude) { - payload[containerObj.Names] = { - status: { - isExcluded: true, - isRunning: false, - isExited: false, - isRestarting: false, - } - } - continue; - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const service = await getServiceFromDB({ id, teamId }); + const { destinationDockerId, settings } = service; + let payload = {}; + if (destinationDockerId) { + const { stdout: containers } = await executeCommand({ + dockerId: service.destinationDocker.id, + command: `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'` + }); + if (containers) { + const containersArray = containers.trim().split('\n'); + if (containersArray.length > 0 && containersArray[0] !== '') { + const templates = await getTemplates(); + let template = templates.find((t) => t.type === service.type); + const templateStr = JSON.stringify(template); + if (templateStr) { + template = JSON.parse(templateStr.replaceAll('$$id', service.id)); + } + for (const container of containersArray) { + let isRunning = false; + let isExited = false; + let isRestarting = false; + let isExcluded = false; + const containerObj = JSON.parse(container); + const exclude = template?.services[containerObj.Names]?.exclude; + if (exclude) { + payload[containerObj.Names] = { + status: { + isExcluded: true, + isRunning: false, + isExited: false, + isRestarting: false + } + }; + continue; + } - const status = containerObj.State - if (status === 'running') { - isRunning = true; - } - if (status === 'exited') { - isExited = true; - } - if (status === 'restarting') { - isRestarting = true; - } - payload[containerObj.Names] = { - status: { - isExcluded, - isRunning, - isExited, - isRestarting - } - } - } - } - } - - } - return payload - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const status = containerObj.State; + if (status === 'running') { + isRunning = true; + } + if (status === 'exited') { + isExited = true; + } + if (status === 'restarting') { + isRestarting = true; + } + payload[containerObj.Names] = { + status: { + isExcluded, + isRunning, + isExited, + isRestarting + } + }; + } + } + } + } + return payload; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function parseAndFindServiceTemplates(service: any, workdir?: string, isDeploy: boolean = false) { - const templates = await getTemplates() - const foundTemplate = templates.find(t => fixType(t.type) === service.type) - let parsedTemplate = {} - if (foundTemplate) { - if (!isDeploy) { - for (const [key, value] of Object.entries(foundTemplate.services)) { - const realKey = key.replace('$$id', service.id) - let name = value.name - if (!name) { - if (Object.keys(foundTemplate.services).length === 1) { - name = foundTemplate.name || service.name.toLowerCase() - } else { - if (key === '$$id') { - name = foundTemplate.name || key.replaceAll('$$id-', '') || service.name.toLowerCase() - } else { - name = key.replaceAll('$$id-', '') || service.name.toLowerCase() - } - } - } - parsedTemplate[realKey] = { - value, - name, - documentation: value.documentation || foundTemplate.documentation || 'https://docs.coollabs.io', - image: value.image, - files: value?.files, - environment: [], - fqdns: [], - hostPorts: [], - proxy: {} - } - if (value.environment?.length > 0) { - for (const env of value.environment) { - let [envKey, ...envValue] = env.split('=') - envValue = envValue.join("=") - let variable = null - if (foundTemplate?.variables) { - variable = foundTemplate?.variables.find(v => v.name === envKey) || foundTemplate?.variables.find(v => v.id === envValue) - } - if (variable) { - const id = variable.id.replaceAll('$$', '') - const label = variable?.label - const description = variable?.description - const defaultValue = variable?.defaultValue - const main = variable?.main || '$$id' - const type = variable?.type || 'input' - const placeholder = variable?.placeholder || '' - const readOnly = variable?.readOnly || false - const required = variable?.required || false - if (envValue.startsWith('$$config') || variable?.showOnConfiguration) { - if (envValue.startsWith('$$config_coolify')) { - continue - } - parsedTemplate[realKey].environment.push( - { id, name: envKey, value: envValue, main, label, description, defaultValue, type, placeholder, required, readOnly } - ) - } - } +export async function parseAndFindServiceTemplates( + service: any, + workdir?: string, + isDeploy: boolean = false +) { + const templates = await getTemplates(); + const foundTemplate = templates.find((t) => fixType(t.type) === service.type); + let parsedTemplate = {}; + if (foundTemplate) { + if (!isDeploy) { + for (const [key, value] of Object.entries(foundTemplate.services)) { + const realKey = key.replace('$$id', service.id); + let name = value.name; + if (!name) { + if (Object.keys(foundTemplate.services).length === 1) { + name = foundTemplate.name || service.name.toLowerCase(); + } else { + if (key === '$$id') { + name = + foundTemplate.name || key.replaceAll('$$id-', '') || service.name.toLowerCase(); + } else { + name = key.replaceAll('$$id-', '') || service.name.toLowerCase(); + } + } + } + parsedTemplate[realKey] = { + value, + name, + documentation: + value.documentation || foundTemplate.documentation || 'https://docs.coollabs.io', + image: value.image, + files: value?.files, + environment: [], + fqdns: [], + hostPorts: [], + proxy: {} + }; + if (value.environment?.length > 0) { + for (const env of value.environment) { + let [envKey, ...envValue] = env.split('='); + envValue = envValue.join('='); + let variable = null; + if (foundTemplate?.variables) { + variable = + foundTemplate?.variables.find((v) => v.name === envKey) || + foundTemplate?.variables.find((v) => v.id === envValue); + } + if (variable) { + const id = variable.id.replaceAll('$$', ''); + const label = variable?.label; + const description = variable?.description; + const defaultValue = variable?.defaultValue; + const main = variable?.main || '$$id'; + const type = variable?.type || 'input'; + const placeholder = variable?.placeholder || ''; + const readOnly = variable?.readOnly || false; + const required = variable?.required || false; + if (envValue.startsWith('$$config') || variable?.showOnConfiguration) { + if (envValue.startsWith('$$config_coolify')) { + continue; + } + parsedTemplate[realKey].environment.push({ + id, + name: envKey, + value: envValue, + main, + label, + description, + defaultValue, + type, + placeholder, + required, + readOnly + }); + } + } + } + } + if (value?.proxy && value.proxy.length > 0) { + for (const proxyValue of value.proxy) { + if (proxyValue.domain) { + const variable = foundTemplate?.variables.find((v) => v.id === proxyValue.domain); + if (variable) { + const { id, name, label, description, defaultValue, required = false } = variable; + const found = await prisma.serviceSetting.findFirst({ + where: { serviceId: service.id, variableName: proxyValue.domain } + }); + parsedTemplate[realKey].fqdns.push({ + id, + name, + value: found?.value || '', + label, + description, + defaultValue, + required + }); + } + } + if (proxyValue.hostPort) { + const variable = foundTemplate?.variables.find((v) => v.id === proxyValue.hostPort); + if (variable) { + const { id, name, label, description, defaultValue, required = false } = variable; + const found = await prisma.serviceSetting.findFirst({ + where: { serviceId: service.id, variableName: proxyValue.hostPort } + }); + parsedTemplate[realKey].hostPorts.push({ + id, + name, + value: found?.value || '', + label, + description, + defaultValue, + required + }); + } + } + } + } + } + } else { + parsedTemplate = foundTemplate; + } + let strParsedTemplate = JSON.stringify(parsedTemplate); - } - } - if (value?.proxy && value.proxy.length > 0) { - for (const proxyValue of value.proxy) { - if (proxyValue.domain) { - const variable = foundTemplate?.variables.find(v => v.id === proxyValue.domain) - if (variable) { - const { id, name, label, description, defaultValue, required = false } = variable - const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.domain } }) - parsedTemplate[realKey].fqdns.push( - { id, name, value: found?.value || '', label, description, defaultValue, required } - ) - } - } - if (proxyValue.hostPort) { - const variable = foundTemplate?.variables.find(v => v.id === proxyValue.hostPort) - if (variable) { - const { id, name, label, description, defaultValue, required = false } = variable - const found = await prisma.serviceSetting.findFirst({ where: { serviceId: service.id, variableName: proxyValue.hostPort } }) - parsedTemplate[realKey].hostPorts.push( - { id, name, value: found?.value || '', label, description, defaultValue, required } - ) - } - } - } - } - } - } else { - parsedTemplate = foundTemplate - } - let strParsedTemplate = JSON.stringify(parsedTemplate) + // replace $$id and $$workdir + strParsedTemplate = strParsedTemplate.replaceAll('$$id', service.id); + strParsedTemplate = strParsedTemplate.replaceAll( + '$$core_version', + service.version || foundTemplate.defaultVersion + ); - // replace $$id and $$workdir - strParsedTemplate = strParsedTemplate.replaceAll('$$id', service.id) - strParsedTemplate = strParsedTemplate.replaceAll('$$core_version', service.version || foundTemplate.defaultVersion) + // replace $$workdir + if (workdir) { + strParsedTemplate = strParsedTemplate.replaceAll('$$workdir', workdir); + } - // replace $$workdir - if (workdir) { - strParsedTemplate = strParsedTemplate.replaceAll('$$workdir', workdir) - } + // replace $$config + if (service.serviceSetting.length > 0) { + for (const setting of service.serviceSetting) { + const { value, variableName } = setting; + const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi'); + if (value === '$$generate_fqdn') { + strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"'); + } else if (value === '$$generate_fqdn_slash') { + strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"'); + } else if (value === '$$generate_domain') { + strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"'); + } else if (service.destinationDocker?.network && value === '$$generate_network') { + strParsedTemplate = strParsedTemplate.replaceAll( + regex, + service.destinationDocker.network + '"' + ); + } else { + strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"'); + } + } + } - // replace $$config - if (service.serviceSetting.length > 0) { - for (const setting of service.serviceSetting) { - const { value, variableName } = setting - const regex = new RegExp(`\\$\\$config_${variableName.replace('$$config_', '')}\"`, 'gi') - if (value === '$$generate_fqdn') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '"' || '' + '"') - } else if (value === '$$generate_fqdn_slash') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, service.fqdn + '/' + '"') - } else if (value === '$$generate_domain') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, getDomain(service.fqdn) + '"') - } else if (service.destinationDocker?.network && value === '$$generate_network') { - strParsedTemplate = strParsedTemplate.replaceAll(regex, service.destinationDocker.network + '"') - } else { - strParsedTemplate = strParsedTemplate.replaceAll(regex, value + '"') - } - } - } - - // replace $$secret - if (service.serviceSecret.length > 0) { - for (const secret of service.serviceSecret) { - let { name, value } = secret - name = name.toLowerCase() - const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}`, 'gi') - const regex = new RegExp(`\\$\\$secret_${name}`, 'gi') - if (value) { - strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10)) - strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"")) - } else { - strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '') - strParsedTemplate = strParsedTemplate.replaceAll(regex, '') - } - } - } - parsedTemplate = JSON.parse(strParsedTemplate) - } - return parsedTemplate + // replace $$secret + if (service.serviceSecret.length > 0) { + for (const secret of service.serviceSecret) { + let { name, value } = secret; + name = name.toLowerCase(); + const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}`, 'gi'); + const regex = new RegExp(`\\$\\$secret_${name}`, 'gi'); + if (value) { + strParsedTemplate = strParsedTemplate.replaceAll( + regexHashed, + bcrypt.hashSync(value.replaceAll('"', '\\"'), 10) + ); + strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll('"', '\\"')); + } else { + strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, ''); + strParsedTemplate = strParsedTemplate.replaceAll(regex, ''); + } + } + } + parsedTemplate = JSON.parse(strParsedTemplate); + } + return parsedTemplate; } export async function getService(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - const service = await getServiceFromDB({ id, teamId }); - if (!service) { - throw { status: 404, message: 'Service not found.' } - } - let template = {} - let tags = [] - if (service.type) { - template = await parseAndFindServiceTemplates(service) - tags = await getTags(service.type) - } - return { - settings: await listSettings(), - service, - template, - tags - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + const service = await getServiceFromDB({ id, teamId }); + if (!service) { + throw { status: 404, message: 'Service not found.' }; + } + let template = {}; + let tags = []; + if (service.type) { + template = await parseAndFindServiceTemplates(service); + tags = await getTags(service.type); + } + return { + settings: await listSettings(), + service, + template, + tags + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceType(request: FastifyRequest) { - try { - return { - services: await getTemplates() - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + return { + services: await getTemplates() + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceType(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { type } = request.body; - const templates = await getTemplates() - let foundTemplate = templates.find(t => fixType(t.type) === fixType(type)) - if (foundTemplate) { - foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id)) - if (foundTemplate.variables) { - if (foundTemplate.variables.length > 0) { - for (const variable of foundTemplate.variables) { - const { defaultValue } = variable; - const regex = /^\$\$.*\((\d+)\)$/g; - const length = Number(regex.exec(defaultValue)?.[1]) || undefined - if (variable.defaultValue.startsWith('$$generate_password')) { - variable.value = generatePassword({ length }); - } else if (variable.defaultValue.startsWith('$$generate_hex')) { - variable.value = generatePassword({ length, isHex: true }); - } else if (variable.defaultValue.startsWith('$$generate_username')) { - variable.value = cuid(); - } else if (variable.defaultValue.startsWith('$$generate_token')) { - variable.value = generateToken() - } else { - variable.value = variable.defaultValue || ''; - } - const foundVariableSomewhereElse = foundTemplate.variables.find(v => v.defaultValue.includes(variable.id)) - if (foundVariableSomewhereElse) { - foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll(variable.id, variable.value) - } - } - } - for (const variable of foundTemplate.variables) { - if (variable.id.startsWith('$$secret_')) { - const found = await prisma.serviceSecret.findFirst({ where: { name: variable.name, serviceId: id } }) - if (!found) { - await prisma.serviceSecret.create({ - data: { name: variable.name, value: encrypt(variable.value) || '', service: { connect: { id } } } - }) - } +export async function saveServiceType( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { type } = request.body; + const templates = await getTemplates(); + let foundTemplate = templates.find((t) => fixType(t.type) === fixType(type)); + if (foundTemplate) { + foundTemplate = JSON.parse(JSON.stringify(foundTemplate).replaceAll('$$id', id)); + if (foundTemplate.variables) { + if (foundTemplate.variables.length > 0) { + for (const variable of foundTemplate.variables) { + const { defaultValue } = variable; + const regex = /^\$\$.*\((\d+)\)$/g; + const length = Number(regex.exec(defaultValue)?.[1]) || undefined; + if (variable.defaultValue.startsWith('$$generate_password')) { + variable.value = generatePassword({ length }); + } else if (variable.defaultValue.startsWith('$$generate_hex')) { + variable.value = generatePassword({ length, isHex: true }); + } else if (variable.defaultValue.startsWith('$$generate_username')) { + variable.value = cuid(); + } else if (variable.defaultValue.startsWith('$$generate_token')) { + variable.value = generateToken(); + } else { + variable.value = variable.defaultValue || ''; + } + const foundVariableSomewhereElse = foundTemplate.variables.find((v) => + v.defaultValue.includes(variable.id) + ); + if (foundVariableSomewhereElse) { + foundVariableSomewhereElse.value = foundVariableSomewhereElse.value.replaceAll( + variable.id, + variable.value + ); + } + } + } + for (const variable of foundTemplate.variables) { + if (variable.id.startsWith('$$secret_')) { + const found = await prisma.serviceSecret.findFirst({ + where: { name: variable.name, serviceId: id } + }); + if (!found) { + await prisma.serviceSecret.create({ + data: { + name: variable.name, + value: encrypt(variable.value) || '', + service: { connect: { id } } + } + }); + } + } + if (variable.id.startsWith('$$config_')) { + const found = await prisma.serviceSetting.findFirst({ + where: { name: variable.name, serviceId: id } + }); + if (!found) { + await prisma.serviceSetting.create({ + data: { + name: variable.name, + value: variable.value.toString(), + variableName: variable.id, + service: { connect: { id } } + } + }); + } + } + } + } + for (const service of Object.keys(foundTemplate.services)) { + if (foundTemplate.services[service].volumes) { + for (const volume of foundTemplate.services[service].volumes) { + const [volumeName, path] = volume.split(':'); + if (!volumeName.startsWith('/')) { + const found = await prisma.servicePersistentStorage.findFirst({ + where: { volumeName, serviceId: id } + }); + if (!found) { + await prisma.servicePersistentStorage.create({ + data: { + volumeName, + path, + containerId: service, + predefined: true, + service: { connect: { id } } + } + }); + } + } + } + } + } + await prisma.service.update({ + where: { id }, + data: { + type, + version: foundTemplate.defaultVersion, + templateVersion: foundTemplate.templateVersion + } + }); - } - if (variable.id.startsWith('$$config_')) { - const found = await prisma.serviceSetting.findFirst({ where: { name: variable.name, serviceId: id } }) - if (!found) { - await prisma.serviceSetting.create({ - data: { name: variable.name, value: variable.value.toString(), variableName: variable.id, service: { connect: { id } } } - }) - } - } - } - } - for (const service of Object.keys(foundTemplate.services)) { - if (foundTemplate.services[service].volumes) { - for (const volume of foundTemplate.services[service].volumes) { - const [volumeName, path] = volume.split(':') - if (!volumeName.startsWith('/')) { - const found = await prisma.servicePersistentStorage.findFirst({ where: { volumeName, serviceId: id } }) - if (!found) { - await prisma.servicePersistentStorage.create({ - data: { volumeName, path, containerId: service, predefined: true, service: { connect: { id } } } - }); - } - } - } - } - } - await prisma.service.update({ where: { id }, data: { type, version: foundTemplate.defaultVersion, templateVersion: foundTemplate.templateVersion } }) - - if (type.startsWith('wordpress')) { - await prisma.service.update({ where: { id }, data: { wordpress: { create: {} } } }) - } - return reply.code(201).send() - } else { - throw { status: 404, message: 'Service type not found.' } - } - - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (type.startsWith('wordpress')) { + await prisma.service.update({ where: { id }, data: { wordpress: { create: {} } } }); + } + return reply.code(201).send(); + } else { + throw { status: 404, message: 'Service type not found.' }; + } + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceVersion(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { version } = request.body; - await prisma.service.update({ - where: { id }, - data: { version } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveServiceVersion( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { version } = request.body; + await prisma.service.update({ + where: { id }, + data: { version } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceDestination(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { destinationId } = request.body; - await prisma.service.update({ - where: { id }, - data: { destinationDocker: { connect: { id: destinationId } } } - }); - return reply.code(201).send({}) - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveServiceDestination( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { destinationId } = request.body; + await prisma.service.update({ + where: { id }, + data: { destinationDocker: { connect: { id: destinationId } } } + }); + return reply.code(201).send({}); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceUsage(request: FastifyRequest) { - try { - const teamId = request.user.teamId; - const { id } = request.params; - let usage = {}; - - const service = await getServiceFromDB({ id, teamId }); - if (service.destinationDockerId) { - [usage] = await Promise.all([getContainerUsage(service.destinationDocker.id, id)]); - } - return { - usage - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const teamId = request.user.teamId; + const { id } = request.params; + let usage = {}; + const service = await getServiceFromDB({ id, teamId }); + if (service.destinationDockerId) { + [usage] = await Promise.all([getContainerUsage(service.destinationDocker.id, id)]); + } + return { + usage + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceLogs(request: FastifyRequest) { - try { - const { id, containerId } = request.params; - let { since = 0 } = request.query - if (since !== 0) { - since = day(since).unix(); - } - const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.service.findUnique({ - where: { id }, - include: { destinationDocker: true } - }); - if (destinationDockerId) { - try { - const { default: ansi } = await import('strip-ansi') - const { stdout, stderr } = await executeCommand({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` }) - const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout) - const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) - return { logs: sortedLogs } - // } - } catch (error) { - const { statusCode, stderr } = error; - if (stderr.startsWith('Error: No such container')) { - return { logs: [], noContainer: true } - } - if (statusCode === 404) { - return { - logs: [] - }; - } - } - } - return { - message: 'No logs found.' - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id, containerId } = request.params; + let { since = 0 } = request.query; + if (since !== 0) { + since = day(since).unix(); + } + const { + destinationDockerId, + destinationDocker: { id: dockerId } + } = await prisma.service.findUnique({ + where: { id }, + include: { destinationDocker: true } + }); + if (destinationDockerId) { + try { + const { default: ansi } = await import('strip-ansi'); + const { stdout, stderr } = await executeCommand({ + dockerId, + command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` + }); + const stripLogsStdout = stdout + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + const stripLogsStderr = stderr + .toString() + .split('\n') + .map((l) => ansi(l)) + .filter((a) => a); + return { logs: stripLogsStderr.concat(stripLogsStdout) }; + // } + } catch (error) { + const { statusCode, stderr } = error; + if (stderr.startsWith('Error: No such container')) { + return { logs: [], noContainer: true }; + } + if (statusCode === 404) { + return { + logs: [] + }; + } + } + } + return { + message: 'No logs found.' + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteService(request: FastifyRequest) { - try { - const { id } = request.params; - await removeService({ id }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + await removeService({ id }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceSettings(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - const { dualCerts } = request.body; - await prisma.service.update({ - where: { id }, - data: { dualCerts } - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function saveServiceSettings( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { dualCerts } = request.body; + await prisma.service.update({ + where: { id }, + data: { dualCerts } + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function checkServiceDomain(request: FastifyRequest) { - try { - const { id } = request.params - const { domain } = request.query - const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } }) - // TODO: Disabled this because it is having problems with remote docker engines. - // return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const { domain } = request.query; + const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } }); + // TODO: Disabled this because it is having problems with remote docker engines. + // return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function checkService(request: FastifyRequest) { - try { - const { id } = request.params; - let { fqdn, exposePort, forceSave, dualCerts, otherFqdn = false } = request.body; + try { + const { id } = request.params; + let { fqdn, exposePort, forceSave, dualCerts, otherFqdn = false } = request.body; - const domainsList = await prisma.serviceSetting.findMany({ where: { variableName: { startsWith: '$$config_coolify_fqdn' } } }) + const domainsList = await prisma.serviceSetting.findMany({ + where: { variableName: { startsWith: '$$config_coolify_fqdn' } } + }); - if (fqdn) fqdn = fqdn.toLowerCase(); - if (exposePort) exposePort = Number(exposePort); + if (fqdn) fqdn = fqdn.toLowerCase(); + if (exposePort) exposePort = Number(exposePort); - const { destinationDocker: { remoteIpAddress, remoteEngine, engine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }) - const { isDNSCheckEnabled } = await prisma.setting.findFirst({}); + const { + destinationDocker: { remoteIpAddress, remoteEngine, engine }, + exposePort: configuredPort + } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }); + const { isDNSCheckEnabled } = await prisma.setting.findFirst({}); - let found = await isDomainConfigured({ id, fqdn, remoteIpAddress, checkOwn: otherFqdn }); - if (found) { - throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } - } - if (domainsList.find(d => getDomain(d.value) === getDomain(fqdn))) { - throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } - } - if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress }) - // TODO: Disabled this because it is having problems with remote docker engines. - // if (isDNSCheckEnabled && !isDev && !forceSave) { - // let hostname = request.hostname.split(':')[0]; - // if (remoteEngine) hostname = remoteIpAddress; - // return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }); - // } - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + let found = await isDomainConfigured({ id, fqdn, remoteIpAddress, checkOwn: otherFqdn }); + if (found) { + throw { + status: 500, + message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` + }; + } + if (domainsList.find((d) => getDomain(d.value) === getDomain(fqdn))) { + throw { + status: 500, + message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` + }; + } + if (exposePort) + await checkExposedPort({ + id, + configuredPort, + exposePort, + engine, + remoteEngine, + remoteIpAddress + }); + // TODO: Disabled this because it is having problems with remote docker engines. + // if (isDNSCheckEnabled && !isDev && !forceSave) { + // let hostname = request.hostname.split(':')[0]; + // if (remoteEngine) hostname = remoteIpAddress; + // return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts }); + // } + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function saveService(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params; - let { name, fqdn, exposePort, type, serviceSetting, version } = request.body; - if (fqdn) fqdn = fqdn.toLowerCase(); - if (exposePort) exposePort = Number(exposePort); - type = fixType(type) + try { + const { id } = request.params; + let { name, fqdn, exposePort, type, serviceSetting, version } = request.body; + if (fqdn) fqdn = fqdn.toLowerCase(); + if (exposePort) exposePort = Number(exposePort); + type = fixType(type); - const data = { - fqdn, - name, - exposePort, - version, - } - const templates = await getTemplates() - const service = await prisma.service.findUnique({ where: { id } }) - const foundTemplate = templates.find(t => fixType(t.type) === fixType(service.type)) - for (const setting of serviceSetting) { - let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting - if (value) { - if (changed) { - await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } }) - } - if (isNew) { - if (!variableName) { - variableName = foundTemplate?.variables.find(v => v.name === name).id - } - await prisma.serviceSetting.create({ data: { name, value, variableName, service: { connect: { id } } } }) - } - } - } - await prisma.service.update({ - where: { id }, data - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + const data = { + fqdn, + name, + exposePort, + version + }; + const templates = await getTemplates(); + const service = await prisma.service.findUnique({ where: { id } }); + const foundTemplate = templates.find((t) => fixType(t.type) === fixType(service.type)); + for (const setting of serviceSetting) { + let { id: settingId, name, value, changed = false, isNew = false, variableName } = setting; + if (value) { + if (changed) { + await prisma.serviceSetting.update({ where: { id: settingId }, data: { value } }); + } + if (isNew) { + if (!variableName) { + variableName = foundTemplate?.variables.find((v) => v.name === name).id; + } + await prisma.serviceSetting.create({ + data: { name, value, variableName, service: { connect: { id } } } + }); + } + } + } + await prisma.service.update({ + where: { id }, + data + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceSecrets(request: FastifyRequest) { - try { - const { id } = request.params - const teamId = request.user.teamId; - const service = await getServiceFromDB({ id, teamId }); - let secrets = await prisma.serviceSecret.findMany({ - where: { serviceId: id }, - orderBy: { createdAt: 'desc' } - }); - const templates = await getTemplates() - const foundTemplate = templates.find(t => fixType(t.type) === service.type) - secrets = secrets.map((secret) => { - const foundVariable = foundTemplate?.variables.find(v => v.name === secret.name) || null - if (foundVariable) { - secret.readOnly = foundVariable.readOnly - } - secret.value = decrypt(secret.value); - return secret; - }); + try { + const { id } = request.params; + const teamId = request.user.teamId; + const service = await getServiceFromDB({ id, teamId }); + let secrets = await prisma.serviceSecret.findMany({ + where: { serviceId: id }, + orderBy: { createdAt: 'desc' } + }); + const templates = await getTemplates(); + const foundTemplate = templates.find((t) => fixType(t.type) === service.type); + secrets = secrets.map((secret) => { + const foundVariable = foundTemplate?.variables.find((v) => v.name === secret.name) || null; + if (foundVariable) { + secret.readOnly = foundVariable.readOnly; + } + secret.value = decrypt(secret.value); + return secret; + }); - return { - secrets - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + return { + secrets + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceSecret(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - let { name, value, isNew } = request.body - if (isNew) { - const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } }); - if (found) { - throw `Secret ${name} already exists.` - } else { - value = encrypt(value.trim()); - await prisma.serviceSecret.create({ - data: { name, value, service: { connect: { id } } } - }); - } - } else { - value = encrypt(value.trim()); - const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } }); +export async function saveServiceSecret( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + let { name, value, isNew } = request.body; + if (isNew) { + const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } }); + if (found) { + throw `Secret ${name} already exists.`; + } else { + value = encrypt(value.trim()); + await prisma.serviceSecret.create({ + data: { name, value, service: { connect: { id } } } + }); + } + } else { + value = encrypt(value.trim()); + const found = await prisma.serviceSecret.findFirst({ where: { serviceId: id, name } }); - if (found) { - await prisma.serviceSecret.updateMany({ - where: { serviceId: id, name }, - data: { value } - }); - } else { - await prisma.serviceSecret.create({ - data: { name, value, service: { connect: { id } } } - }); - } - } - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (found) { + await prisma.serviceSecret.updateMany({ + where: { serviceId: id, name }, + data: { value } + }); + } else { + await prisma.serviceSecret.create({ + data: { name, value, service: { connect: { id } } } + }); + } + } + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteServiceSecret(request: FastifyRequest) { - try { - const { id } = request.params - const { name } = request.body - await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const { name } = request.body; + await prisma.serviceSecret.deleteMany({ where: { serviceId: id, name } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function getServiceStorages(request: FastifyRequest) { - try { - const { id } = request.params - const persistentStorages = await prisma.servicePersistentStorage.findMany({ - where: { serviceId: id } - }); - return { - persistentStorages - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const persistentStorages = await prisma.servicePersistentStorage.findMany({ + where: { serviceId: id } + }); + return { + persistentStorages + }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function saveServiceStorage(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const { path, isNewStorage, storageId, containerId } = request.body +export async function saveServiceStorage( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { path, isNewStorage, storageId, containerId } = request.body; - if (isNewStorage) { - const volumeName = `${id}-custom${path.replace(/\//gi, '-')}` - const found = await prisma.servicePersistentStorage.findFirst({ where: { path, containerId } }); - if (found) { - throw { status: 500, message: 'Persistent storage already exists for this container and path.' } - } - await prisma.servicePersistentStorage.create({ - data: { path, volumeName, containerId, service: { connect: { id } } } - }); - } else { - await prisma.servicePersistentStorage.update({ - where: { id: storageId }, - data: { path, containerId } - }); - } - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + if (isNewStorage) { + const volumeName = `${id}-custom${path.replace(/\//gi, '-')}`; + const found = await prisma.servicePersistentStorage.findFirst({ + where: { path, containerId } + }); + if (found) { + throw { + status: 500, + message: 'Persistent storage already exists for this container and path.' + }; + } + await prisma.servicePersistentStorage.create({ + data: { path, volumeName, containerId, service: { connect: { id } } } + }); + } else { + await prisma.servicePersistentStorage.update({ + where: { id: storageId }, + data: { path, containerId } + }); + } + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function deleteServiceStorage(request: FastifyRequest) { - try { - const { storageId } = request.body - await prisma.servicePersistentStorage.deleteMany({ where: { id: storageId } }); - return {} - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { storageId } = request.body; + await prisma.servicePersistentStorage.deleteMany({ where: { id: storageId } }); + return {}; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function setSettingsService(request: FastifyRequest, reply: FastifyReply) { - try { - const { type } = request.params - if (type === 'wordpress') { - return await setWordpressSettings(request, reply) - } - if (type === 'glitchtip') { - return await setGlitchTipSettings(request, reply) - } - throw `Service type ${type} not supported.` - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +export async function setSettingsService( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { type } = request.params; + if (type === 'wordpress') { + return await setWordpressSettings(request, reply); + } + if (type === 'glitchtip') { + return await setGlitchTipSettings(request, reply); + } + throw `Service type ${type} not supported.`; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -async function setGlitchTipSettings(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } = request.body - await prisma.glitchTip.update({ - where: { serviceId: id }, - data: { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +async function setGlitchTipSettings( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } = request.body; + await prisma.glitchTip.update({ + where: { serviceId: id }, + data: { enableOpenUserRegistration, emailSmtpUseSsl, emailSmtpUseTls } + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -async function setWordpressSettings(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const { ownMysql } = request.body - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ownMysql } - }); - return reply.code(201).send() - } catch ({ status, message }) { - return errorHandler({ status, message }) - } +async function setWordpressSettings( + request: FastifyRequest, + reply: FastifyReply +) { + try { + const { id } = request.params; + const { ownMysql } = request.body; + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ownMysql } + }); + return reply.code(201).send(); + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } - export async function activatePlausibleUsers(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const teamId = request.user.teamId; - const { - destinationDockerId, - destinationDocker, - serviceSecret - } = await getServiceFromDB({ id, teamId }); - if (destinationDockerId) { - const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL'); - if (databaseUrl) { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"` - }) - return await reply.code(201).send() - } - } - throw { status: 500, message: 'Could not activate users.' } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const teamId = request.user.teamId; + const { destinationDockerId, destinationDocker, serviceSecret } = await getServiceFromDB({ + id, + teamId + }); + if (destinationDockerId) { + const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL'); + if (databaseUrl) { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"` + }); + return await reply.code(201).send(); + } + } + throw { status: 500, message: 'Could not activate users.' }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } export async function cleanupPlausibleLogs(request: FastifyRequest, reply: FastifyReply) { - try { - const { id } = request.params - const teamId = request.user.teamId; - const { - destinationDockerId, - destinationDocker, - } = await getServiceFromDB({ id, teamId }); - if (destinationDockerId) { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`, - shell: true - }) - return await reply.code(201).send() - } - throw { status: 500, message: 'Could cleanup logs.' } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } + try { + const { id } = request.params; + const teamId = request.user.teamId; + const { destinationDockerId, destinationDocker } = await getServiceFromDB({ id, teamId }); + if (destinationDockerId) { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker exec ${id}-clickhouse /usr/bin/clickhouse-client -q \\"SELECT name FROM system.tables WHERE name LIKE '%log%';\\"| xargs -I{} /usr/bin/clickhouse-client -q \"TRUNCATE TABLE system.{};\"`, + shell: true + }); + return await reply.code(201).send(); + } + throw { status: 500, message: 'Could cleanup logs.' }; + } catch ({ status, message }) { + return errorHandler({ status, message }); + } } -export async function activateWordpressFtp(request: FastifyRequest, reply: FastifyReply) { - const { id } = request.params - const { ftpEnabled } = request.body; +export async function activateWordpressFtp( + request: FastifyRequest, + reply: FastifyReply +) { + const { id } = request.params; + const { ftpEnabled } = request.body; - const { service: { destinationDocker: { engine, remoteEngine, remoteIpAddress } } } = await prisma.wordpress.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } }) + const { + service: { + destinationDocker: { engine, remoteEngine, remoteIpAddress } + } + } = await prisma.wordpress.findUnique({ + where: { serviceId: id }, + include: { service: { include: { destinationDocker: true } } } + }); - const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); + const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); - let ftpUser = cuid(); - let ftpPassword = generatePassword({}); + let ftpUser = cuid(); + let ftpPassword = generatePassword({}); - const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; - try { - const data = await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpEnabled }, - include: { service: { include: { destinationDocker: true } } } - }); - const { - service: { destinationDockerId, destinationDocker }, - ftpPublicPort, - ftpUser: user, - ftpPassword: savedPassword, - ftpHostKey, - ftpHostKeyPrivate - } = data; - const { network, engine } = destinationDocker; - if (ftpEnabled) { - if (user) ftpUser = user; - if (savedPassword) ftpPassword = decrypt(savedPassword); + const hostkeyDir = isDev ? '/tmp/hostkeys' : '/app/ssl/hostkeys'; + try { + const data = await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpEnabled }, + include: { service: { include: { destinationDocker: true } } } + }); + const { + service: { destinationDockerId, destinationDocker }, + ftpPublicPort, + ftpUser: user, + ftpPassword: savedPassword, + ftpHostKey, + ftpHostKeyPrivate + } = data; + const { network, engine } = destinationDocker; + if (ftpEnabled) { + if (user) ftpUser = user; + if (savedPassword) ftpPassword = decrypt(savedPassword); - // TODO: rewrite these to usable without shell - const { stdout: password } = await executeCommand({ - command: - `echo ${ftpPassword} | openssl passwd -1 -stdin`, - shell: true - } - ); - if (destinationDockerId) { - try { - await fs.stat(hostkeyDir); - } catch (error) { - await executeCommand({ command: `mkdir -p ${hostkeyDir}` }); - } - if (!ftpHostKey) { - await executeCommand({ - command: - `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` - } - ); - const { stdout: ftpHostKey } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.ed25519` }); - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKey: encrypt(ftpHostKey) } - }); - } else { - await executeCommand({ command: `echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`, shell: true }); - } - if (!ftpHostKeyPrivate) { - await executeCommand({ command: `ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa` }); - const { stdout: ftpHostKeyPrivate } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.rsa` }); - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } - }); - } else { - await executeCommand({ command: `echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`, shell: true }); - } + // TODO: rewrite these to usable without shell + const { stdout: password } = await executeCommand({ + command: `echo ${ftpPassword} | openssl passwd -1 -stdin`, + shell: true + }); + if (destinationDockerId) { + try { + await fs.stat(hostkeyDir); + } catch (error) { + await executeCommand({ command: `mkdir -p ${hostkeyDir}` }); + } + if (!ftpHostKey) { + await executeCommand({ + command: `ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` + }); + const { stdout: ftpHostKey } = await executeCommand({ + command: `cat ${hostkeyDir}/${id}.ed25519` + }); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKey: encrypt(ftpHostKey) } + }); + } else { + await executeCommand({ + command: `echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`, + shell: true + }); + } + if (!ftpHostKeyPrivate) { + await executeCommand({ + command: `ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa` + }); + const { stdout: ftpHostKeyPrivate } = await executeCommand({ + command: `cat ${hostkeyDir}/${id}.rsa` + }); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } + }); + } else { + await executeCommand({ + command: `echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`, + shell: true + }); + } - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { - ftpPublicPort: publicPort, - ftpUser: user ? undefined : ftpUser, - ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) - } - }); + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { + ftpPublicPort: publicPort, + ftpUser: user ? undefined : ftpUser, + ftpPassword: savedPassword ? undefined : encrypt(ftpPassword) + } + }); - try { - const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` }); - if (isRunning) { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, - shell: true - }) - } - } catch (error) { } - const volumes = [ - `${id}-wordpress-data:/home/${ftpUser}/wordpress`, - `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' - }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, - `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' - }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, - `${isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' - }/${id}.sh:/etc/sftp.d/chmod.sh` - ]; - - const compose: ComposeFile = { - version: '3.8', - services: { - [`${id}-ftp`]: { - image: `atmoz/sftp:alpine`, - command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`, - extra_hosts: ['host.docker.internal:host-gateway'], - container_name: `${id}-ftp`, - volumes, - networks: [network], - depends_on: [], - restart: 'always' - } - }, - networks: { - [network]: { - external: true - } - }, - volumes: { - [`${id}-wordpress-data`]: { - external: true, - name: `${id}-wordpress-data` - } - } - }; - await fs.writeFile( - `${hostkeyDir}/${id}.sh`, - `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/` - ); - await executeCommand({ command: `chmod +x ${hostkeyDir}/${id}.sh` }); - await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` - }) - - } - return reply.code(201).send({ - publicPort, - ftpUser, - ftpPassword - }) - } else { - await prisma.wordpress.update({ - where: { serviceId: id }, - data: { ftpPublicPort: null } - }); - try { - await executeCommand({ - dockerId: destinationDocker.id, - command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, - shell: true - }) - - } catch (error) { - // - } - await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort); - return { - }; - } - } catch ({ status, message }) { - return errorHandler({ status, message }) - } finally { - try { - await executeCommand({ - command: - `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` - } - ); - } catch (error) { } - - } + try { + const { found: isRunning } = await checkContainer({ + dockerId: destinationDocker.id, + container: `${id}-ftp` + }); + if (isRunning) { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, + shell: true + }); + } + } catch (error) {} + const volumes = [ + `${id}-wordpress-data:/home/${ftpUser}/wordpress`, + `${ + isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.ed25519:/etc/ssh/ssh_host_ed25519_key`, + `${ + isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.rsa:/etc/ssh/ssh_host_rsa_key`, + `${ + isDev ? hostkeyDir : '/var/lib/docker/volumes/coolify-ssl-certs/_data/hostkeys' + }/${id}.sh:/etc/sftp.d/chmod.sh` + ]; + const compose: ComposeFile = { + version: '3.8', + services: { + [`${id}-ftp`]: { + image: `atmoz/sftp:alpine`, + command: `'${ftpUser}:${password.replace('\n', '').replace(/\$/g, '$$$')}:e:33'`, + extra_hosts: ['host.docker.internal:host-gateway'], + container_name: `${id}-ftp`, + volumes, + networks: [network], + depends_on: [], + restart: 'always' + } + }, + networks: { + [network]: { + external: true + } + }, + volumes: { + [`${id}-wordpress-data`]: { + external: true, + name: `${id}-wordpress-data` + } + } + }; + await fs.writeFile( + `${hostkeyDir}/${id}.sh`, + `#!/bin/bash\nchmod 600 /etc/ssh/ssh_host_ed25519_key /etc/ssh/ssh_host_rsa_key\nuserdel -f xfs\nchown -R 33:33 /home/${ftpUser}/wordpress/` + ); + await executeCommand({ command: `chmod +x ${hostkeyDir}/${id}.sh` }); + await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` + }); + } + return reply.code(201).send({ + publicPort, + ftpUser, + ftpPassword + }); + } else { + await prisma.wordpress.update({ + where: { serviceId: id }, + data: { ftpPublicPort: null } + }); + try { + await executeCommand({ + dockerId: destinationDocker.id, + command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`, + shell: true + }); + } catch (error) { + // + } + await stopTcpHttpProxy(id, destinationDocker, ftpPublicPort); + return {}; + } + } catch ({ status, message }) { + return errorHandler({ status, message }); + } finally { + try { + await executeCommand({ + command: `rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` + }); + } catch (error) {} + } } diff --git a/apps/server/src/trpc/routers/applications/index.ts b/apps/server/src/trpc/routers/applications/index.ts index 12956b610..3243ff894 100644 --- a/apps/server/src/trpc/routers/applications/index.ts +++ b/apps/server/src/trpc/routers/applications/index.ts @@ -561,12 +561,7 @@ export const applicationsRouter = router({ .split('\n') .map((l) => ansi(l)) .filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout); - const sortedLogs = logs.sort((a, b) => - day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1 - ); - return { logs: sortedLogs }; - // } + return { logs: stripLogsStderr.concat(stripLogsStdout) }; } catch (error) { const { statusCode, stderr } = error; if (stderr.startsWith('Error: No such container')) { diff --git a/apps/server/src/trpc/routers/services/index.ts b/apps/server/src/trpc/routers/services/index.ts index e58faf2e5..755446ee1 100644 --- a/apps/server/src/trpc/routers/services/index.ts +++ b/apps/server/src/trpc/routers/services/index.ts @@ -68,16 +68,11 @@ export const servicesRouter = router({ .split('\n') .map((l) => ansi(l)) .filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout); - const sortedLogs = logs.sort((a, b) => - day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1 - ); return { data: { - logs: sortedLogs + logs: stripLogsStderr.concat(stripLogsStdout) } }; - // } } catch (error) { const { statusCode, stderr } = error; if (stderr.startsWith('Error: No such container')) { @@ -92,7 +87,6 @@ export const servicesRouter = router({ return { data: { logs: [] - } }; }