diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..c9e331b1e --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- Database connecting string for remote docker engines \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index ef63a504b..e230e4411 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -32,6 +32,7 @@ "dayjs": "1.11.3", "dockerode": "3.3.2", "dotenv-extended": "2.9.0", + "execa": "^6.1.0", "fastify": "4.2.1", "fastify-plugin": "4.0.0", "generate-password": "1.7.0", diff --git a/apps/api/prisma/migrations/20220718134234_ssh_key/migration.sql b/apps/api/prisma/migrations/20220721084020_ssh_key/migration.sql similarity index 81% rename from apps/api/prisma/migrations/20220718134234_ssh_key/migration.sql rename to apps/api/prisma/migrations/20220721084020_ssh_key/migration.sql index 08812482c..8323af409 100644 --- a/apps/api/prisma/migrations/20220718134234_ssh_key/migration.sql +++ b/apps/api/prisma/migrations/20220721084020_ssh_key/migration.sql @@ -3,10 +3,8 @@ CREATE TABLE "SshKey" ( "id" TEXT NOT NULL PRIMARY KEY, "name" TEXT NOT NULL, "privateKey" TEXT NOT NULL, - "destinationDockerId" TEXT, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL, - CONSTRAINT "SshKey_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE + "updatedAt" DATETIME NOT NULL ); -- RedefineTables @@ -23,7 +21,9 @@ CREATE TABLE "new_DestinationDocker" ( "remoteVerified" BOOLEAN NOT NULL DEFAULT false, "isCoolifyProxyUsed" BOOLEAN DEFAULT false, "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" DATETIME NOT NULL + "updatedAt" DATETIME NOT NULL, + "sshKeyId" TEXT, + CONSTRAINT "DestinationDocker_sshKeyId_fkey" FOREIGN KEY ("sshKeyId") REFERENCES "SshKey" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt" FROM "DestinationDocker"; DROP TABLE "DestinationDocker"; @@ -31,6 +31,3 @@ ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker"; CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network"); PRAGMA foreign_key_check; PRAGMA foreign_keys=ON; - --- CreateIndex -CREATE UNIQUE INDEX "SshKey_destinationDockerId_key" ON "SshKey"("destinationDockerId"); diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index e35e06290..72c926f3f 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -213,17 +213,17 @@ model DestinationDocker { updatedAt DateTime @updatedAt database Database[] service Service[] - sshKey SshKey? + sshKey SshKey? @relation(fields: [sshKeyId], references: [id]) + sshKeyId String? } model SshKey { - id String @id @default(cuid()) - name String - privateKey String - destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id]) - destinationDockerId String? @unique - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + name String + privateKey String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + destinationDocker DestinationDocker[] } model GitSource { diff --git a/apps/api/src/jobs/deployApplication.ts b/apps/api/src/jobs/deployApplication.ts index 8b7cb358e..5936dcc91 100644 --- a/apps/api/src/jobs/deployApplication.ts +++ b/apps/api/src/jobs/deployApplication.ts @@ -5,7 +5,7 @@ import yaml from 'js-yaml'; import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common'; import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common'; -import { dockerInstance, getEngine } from '../lib/docker'; +import Dockerode from 'dockerode'; import * as importers from '../lib/importers'; import * as buildpacks from '../lib/buildPacks'; @@ -104,9 +104,6 @@ import * as buildpacks from '../lib/buildPacks'; destinationType = 'docker'; } if (destinationType === 'docker') { - const docker = dockerInstance({ destinationDocker }); - const host = getEngine(destinationDocker.engine); - await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } }); const { workdir, repodir } = await createDirectories({ repository, buildId }); const configuration = await setDefaultConfiguration(message); @@ -185,18 +182,23 @@ import * as buildpacks from '../lib/buildPacks'; } else { deployNeeded = true; } - const image = await docker.engine.getImage(`${applicationId}:${tag}`); + let imageFound = false; try { - await image.inspect(); + await executeDockerCmd({ + dockerId: destinationDocker.id, + command: `docker image inspect ${applicationId}:${tag}` + }) imageFound = true; } catch (error) { // } - if (!imageFound || deployNeeded) { + // if (!imageFound || deployNeeded) { + if (true) { await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage); if (buildpacks[buildPack]) await buildpacks[buildPack]({ + dockerId: destinationDocker.id, buildId, applicationId, domain, @@ -212,7 +214,6 @@ import * as buildpacks from '../lib/buildPacks'; commit, tag, workdir, - docker, port: exposePort ? `${exposePort}:${port}` : port, installCommand, buildCommand, @@ -299,7 +300,7 @@ import * as buildpacks from '../lib/buildPacks'; container_name: imageId, volumes, env_file: envFound ? [`${workdir}/.env`] : [], - networks: [docker.network], + networks: [destinationDocker.network], labels, depends_on: [], restart: 'always', @@ -318,7 +319,7 @@ import * as buildpacks from '../lib/buildPacks'; } }, networks: { - [docker.network]: { + [destinationDocker.network]: { external: true } }, diff --git a/apps/api/src/lib/buildPacks/common.ts b/apps/api/src/lib/buildPacks/common.ts index 1feb33822..ea75a20e3 100644 --- a/apps/api/src/lib/buildPacks/common.ts +++ b/apps/api/src/lib/buildPacks/common.ts @@ -1,7 +1,8 @@ -import { asyncExecShell, base64Encode, generateTimestamp, getDomain, isDev, prisma, version } from "../common"; +import { asyncExecShell, base64Encode, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common"; import { scheduler } from "../scheduler"; import { promises as fs } from 'fs'; import { day } from "../dayjs"; +import { spawn } from 'node:child_process' const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy']; const nodeBased = [ 'react', @@ -511,8 +512,8 @@ export async function buildImage({ applicationId, tag, workdir, - docker, buildId, + dockerId, isCache = false, debug = false, dockerFileLocation = '/Dockerfile' @@ -522,6 +523,9 @@ export async function buildImage({ } else { await saveBuildLog({ line: `Building image started.`, buildId, applicationId }); } + if (debug) { + await saveBuildLog({ line: `\n###############\nIMPORTANT: Due to some issues during implementing Remote Docker Engine, the builds logs are not streamed at the moment. You will see the full build log when the build is finished!\n###############`, buildId, applicationId }); + } if (!debug && isCache) { await saveBuildLog({ line: `Debug turned off. To see more details, allow it in the configuration.`, @@ -529,15 +533,56 @@ export async function buildImage({ applicationId }); } - - const stream = await docker.engine.buildImage( - { src: ['.'], context: workdir }, - { - dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation, - t: `${applicationId}:${tag}${isCache ? '-cache' : ''}` + const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}` + const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}` + const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` }) + if (debug) { + const array = stderr.split('\n') + for (const line of array) { + if (line !== '\n') { + await saveBuildLog({ + line: `${line.replace('\n', '')}`, + buildId, + applicationId + }); + } } - ); - await streamEvents({ stream, docker, buildId, applicationId, debug }); + } + + + // await new Promise((resolve, reject) => { + // const command = spawn(`docker`, ['build', '-f', `${workdir}${dockerFile}`, '-t', `${cache}`,`${workdir}`], { + // env: { + // DOCKER_HOST: 'ssh://root@95.217.178.202', + // DOCKER_BUILDKIT: '1' + // } + // }); + // command.stdout.on('data', function (data) { + // console.log('stdout: ' + data); + // }); + // command.stderr.on('data', function (data) { + // console.log('stderr: ' + data); + // }); + // command.on('error', function (error) { + // console.log(error) + // reject(error) + // }) + // command.on('exit', function (code) { + // console.log('exit code: ' + code); + // resolve(code) + // }); + // }) + + + // console.log({ stdout, stderr }) + // const stream = await docker.engine.buildImage( + // { src: ['.'], context: workdir }, + // { + // dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation, + // t: `${applicationId}:${tag}${isCache ? '-cache' : ''}` + // } + // ); + // await streamEvents({ stream, docker, buildId, applicationId, debug }); await saveBuildLog({ line: `Building image successful!`, buildId, applicationId }); } @@ -617,18 +662,17 @@ export function makeLabelForStandaloneApplication({ export async function buildCacheImageWithNode(data, imageForBuild) { const { - applicationId, - tag, workdir, - docker, buildId, baseDirectory, installCommand, buildCommand, - debug, secrets, pullmergeRequestId } = data; + + data.isCache = true + const isPnpm = checkPnpm(installCommand, buildCommand); const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild}`); @@ -659,11 +703,13 @@ export async function buildCacheImageWithNode(data, imageForBuild) { Dockerfile.push(`COPY .${baseDirectory || ''} ./`); Dockerfile.push(`RUN ${buildCommand}`); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); - await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); + await buildImage(data); } export async function buildCacheImageForLaravel(data, imageForBuild) { - const { applicationId, tag, workdir, docker, buildId, debug, secrets, pullmergeRequestId } = data; + const { workdir, buildId, secrets, pullmergeRequestId } = data; + data.isCache = true + const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild}`); Dockerfile.push('WORKDIR /app'); @@ -687,22 +733,17 @@ export async function buildCacheImageForLaravel(data, imageForBuild) { Dockerfile.push(`COPY resources /app/resources`); Dockerfile.push(`RUN yarn install && yarn production`); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); - await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); + await buildImage(data); } export async function buildCacheImageWithCargo(data, imageForBuild) { const { applicationId, - tag, workdir, - docker, buildId, - baseDirectory, - installCommand, - buildCommand, - debug, - secrets } = data; + data.isCache = true + const Dockerfile: Array = []; Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); Dockerfile.push(`LABEL coolify.buildId=${buildId}`); @@ -717,5 +758,5 @@ export async function buildCacheImageWithCargo(data, imageForBuild) { Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); - await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); + await buildImage(data); } \ No newline at end of file diff --git a/apps/api/src/lib/buildPacks/docker.ts b/apps/api/src/lib/buildPacks/docker.ts index 88d900c38..04041b190 100644 --- a/apps/api/src/lib/buildPacks/docker.ts +++ b/apps/api/src/lib/buildPacks/docker.ts @@ -1,18 +1,18 @@ import { promises as fs } from 'fs'; import { buildImage } from './common'; -export default async function ({ - applicationId, - debug, - tag, - workdir, - docker, - buildId, - baseDirectory, - secrets, - pullmergeRequestId, - dockerFileLocation -}) { +export default async function (data) { + let { + applicationId, + debug, + tag, + workdir, + buildId, + baseDirectory, + secrets, + pullmergeRequestId, + dockerFileLocation + } = data try { const file = `${workdir}${dockerFileLocation}`; let dockerFileOut = `${workdir}`; @@ -45,7 +45,7 @@ export default async function ({ } await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n')); - await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation }); + await buildImage(data); } catch (error) { throw error; } diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 2df889ed8..9907895f0 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -473,6 +473,8 @@ export async function createRemoteEngineConfiguration(id: string) { const sshKeyFile = `/tmp/id_rsa-${id}` const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } }) await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 }) + // Needed for remote docker compose + await asyncExecShell(`eval $(ssh-agent -s) && ssh-add -q ${sshKeyFile}`) const config = sshConfig.parse('') const found = config.find({ Host: remoteIpAddress }) if (!found) { @@ -542,14 +544,13 @@ export async function startTraefikProxy(id: string): Promise { data: { isCoolifyProxyUsed: true } }); } - if (!remoteEngine) await configureNetworkTraefikProxy(engine); + if (!remoteEngine) await configureNetworkTraefikProxy(engine, id); } -export async function configureNetworkTraefikProxy(engine: string): Promise { +export async function configureNetworkTraefikProxy(engine: string, id: string): Promise { const destinations = await prisma.destinationDocker.findMany({ where: { engine } }); - const { stdout: networks } = await executeDockerCmd({ - dockerId: '', + dockerId: id, command: `docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'` }); diff --git a/apps/api/src/lib/docker.ts b/apps/api/src/lib/docker.ts index cded70acf..14f5b97f5 100644 --- a/apps/api/src/lib/docker.ts +++ b/apps/api/src/lib/docker.ts @@ -47,13 +47,10 @@ export async function checkContainer({ dockerId, container, remove = false }: { return containerFound; } -export async function isContainerExited(engine: string, containerName: string): Promise { +export async function isContainerExited(dockerId: string, containerName: string): Promise { let isExited = false; - const host = getEngine(engine); try { - const { stdout } = await asyncExecShell( - `DOCKER_HOST="${host}" docker inspect -f '{{.State.Status}}' ${containerName}` - ); + const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` }) if (stdout.trim() === 'exited') { isExited = true; } @@ -72,11 +69,11 @@ export async function removeContainer({ dockerId: string; }): Promise { try { - const { stdout } =await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}`}) - + const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` }) + if (JSON.parse(stdout).Running) { - await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}`}) - await executeDockerCmd({ dockerId, command: `docker rm ${id}`}) + await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` }) + await executeDockerCmd({ dockerId, command: `docker rm ${id}` }) } } catch (error) { console.log(error); diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 6c73361d7..0eca0249c 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -5,8 +5,8 @@ import axios from 'axios'; import { FastifyReply } from 'fastify'; import { day } from '../../../../lib/dayjs'; import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; -import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; -import { checkContainer, dockerInstance, getEngine, isContainerExited, removeContainer } from '../../../../lib/docker'; +import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; +import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker'; import { scheduler } from '../../../../lib/scheduler'; import type { FastifyRequest } from 'fastify'; @@ -62,23 +62,36 @@ export async function getImages(request: FastifyRequest) { return errorHandler({ status, message }) } } +export async function getApplicationStatus(request: FastifyRequest) { + try { + const { id } = request.params + const { teamId } = request.user + let isRunning = false; + let isExited = false; + + const application: any = await getApplicationFromDB(id, teamId); + if (application?.destinationDockerId) { + isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id }); + isExited = await isContainerExited(application.destinationDocker.id, id); + } + return { + isQueueActive: scheduler.workers.has('deployApplication'), + isRunning, + isExited, + }; + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} export async function getApplication(request: FastifyRequest) { try { const { id } = request.params const { teamId } = request.user const appId = process.env['COOLIFY_APP_ID']; - let isRunning = false; - let isExited = false; const application: any = await getApplicationFromDB(id, teamId); - if (application?.destinationDockerId && application.destinationDocker?.engine) { - isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id }); - isExited = await isContainerExited(application.destinationDocker.engine, id); - } + return { - isQueueActive: scheduler.workers.has('deployApplication'), - isRunning, - isExited, application, appId }; @@ -284,8 +297,8 @@ export async function stopApplication(request: FastifyRequest, reply: Fa const { id } = request.params const { teamId } = request.user const application: any = await getApplicationFromDB(id, teamId); - if (application?.destinationDockerId && application.destinationDocker?.engine) { - const { engine, id: dockerId } = application.destinationDocker; + if (application?.destinationDockerId) { + const { id: dockerId } = application.destinationDocker; const found = await checkContainer({ dockerId, container: id }); if (found) { await removeContainer({ id, dockerId: application.destinationDocker.id }); @@ -304,11 +317,11 @@ export async function deleteApplication(request: FastifyRequest) { export async function getApplicationLogs(request: FastifyRequest) { try { - const { id } = request.params + const { id } = request.params; let { since = 0 } = request.query if (since !== 0) { since = day(since).unix(); } - const { destinationDockerId, destinationDocker } = await prisma.application.findUnique({ + const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }); if (destinationDockerId) { - const docker = dockerInstance({ destinationDocker }); try { - const container = await docker.engine.getContainer(id); - if (container) { + // const found = await checkContainer({ dockerId, container: id }) + // if (found) { const { default: ansi } = await import('strip-ansi') - const logs = ( - await container.logs({ - stdout: true, - stderr: true, - timestamps: true, - since, - tail: 5000 - }) - ) - .toString() - .split('\n') - .map((l) => ansi(l.slice(8))) - .filter((a) => a); + const { stdout, stderr } = await executeDockerCmd({ 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 + logs: [] }; } - } catch (error) { - return { - logs: [] - }; } } + return { + message: 'No logs found.' + } } catch ({ status, message }) { return errorHandler({ status, message }) } diff --git a/apps/api/src/routes/api/v1/applications/index.ts b/apps/api/src/routes/api/v1/applications/index.ts index aa359f973..d6c8e1a76 100644 --- a/apps/api/src/routes/api/v1/applications/index.ts +++ b/apps/api/src/routes/api/v1/applications/index.ts @@ -1,6 +1,6 @@ import { FastifyPluginAsync } from 'fastify'; import { OnlyId } from '../../../../types'; -import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers'; +import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers'; import type { CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage } from './types'; @@ -17,6 +17,8 @@ const root: FastifyPluginAsync = async (fastify): Promise => { fastify.post('/:id', async (request, reply) => await saveApplication(request, reply)); fastify.delete('/:id', async (request, reply) => await deleteApplication(request, reply)); + fastify.get('/:id/status', async (request) => await getApplicationStatus(request)); + fastify.post('/:id/stop', async (request, reply) => await stopApplication(request, reply)); fastify.post('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply)); diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index b31d3a751..670de896d 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -3,9 +3,10 @@ import type { FastifyRequest } from 'fastify'; import { FastifyReply } from 'fastify'; import yaml from 'js-yaml'; import fs from 'fs/promises'; -import { asyncExecShell, ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; -import { dockerInstance, getEngine } from '../../../../lib/docker'; +import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; +import { checkContainer } from '../../../../lib/docker'; import { day } from '../../../../lib/dayjs'; + import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types'; import { SaveDatabaseType } from './types'; @@ -98,7 +99,7 @@ export async function getDatabase(request: FastifyRequest) { throw { status: 404, message: 'Database not found.' } } if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); - if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); + if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); const configuration = generateDatabaseConfiguration(database); const settings = await listSettings(); return { @@ -236,7 +237,6 @@ export async function startDatabase(request: FastifyRequest) { generateDatabaseConfiguration(database); const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const volumeName = volume.split(':')[0]; const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); @@ -322,39 +322,27 @@ export async function stopDatabase(request: FastifyRequest) { } export async function getDatabaseLogs(request: FastifyRequest) { try { - const teamId = request.user.teamId; const { id } = request.params; let { since = 0 } = request.query if (since !== 0) { since = day(since).unix(); } - const { destinationDockerId, destinationDocker } = await prisma.database.findUnique({ + const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }); if (destinationDockerId) { - const docker = dockerInstance({ destinationDocker }); try { - const container = await docker.engine.getContainer(id); - if (container) { + // const found = await checkContainer({ dockerId, container: id }) + // if (found) { const { default: ansi } = await import('strip-ansi') - const logs = ( - await container.logs({ - stdout: true, - stderr: true, - timestamps: true, - since, - tail: 5000 - }) - ) - .toString() - .split('\n') - .map((l) => ansi(l.slice(8))) - .filter((a) => a); - return { - logs - }; - } + const { stdout, stderr } = await executeDockerCmd({ 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) { diff --git a/apps/api/src/routes/api/v1/databases/index.ts b/apps/api/src/routes/api/v1/databases/index.ts index 229b5bb86..d31a7b9d9 100644 --- a/apps/api/src/routes/api/v1/databases/index.ts +++ b/apps/api/src/routes/api/v1/databases/index.ts @@ -12,10 +12,11 @@ const root: FastifyPluginAsync = async (fastify): Promise => { fastify.post('/new', async (request, reply) => await newDatabase(request, reply)); fastify.get('/:id', async (request) => await getDatabase(request)); - fastify.get('/:id/status', async (request) => await getDatabaseStatus(request)); fastify.post('/:id', async (request, reply) => await saveDatabase(request, reply)); fastify.delete('/:id', async (request) => await deleteDatabase(request)); + fastify.get('/:id/status', async (request) => await getDatabaseStatus(request)); + fastify.post('/:id/settings', async (request) => await saveDatabaseSettings(request)); fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request)); diff --git a/apps/api/src/routes/api/v1/destinations/handlers.ts b/apps/api/src/routes/api/v1/destinations/handlers.ts index c8f1be658..b5cd6baa4 100644 --- a/apps/api/src/routes/api/v1/destinations/handlers.ts +++ b/apps/api/src/routes/api/v1/destinations/handlers.ts @@ -2,7 +2,7 @@ import type { FastifyRequest } from 'fastify'; import { FastifyReply } from 'fastify'; import sshConfig from 'ssh-config' import fs from 'fs/promises' -import { asyncExecShell, decrypt, errorHandler, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; +import { asyncExecShell, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker'; import type { OnlyId } from '../../../../types'; @@ -67,9 +67,11 @@ export async function getDestination(request: FastifyRequest) { } export async function newDestination(request: FastifyRequest, reply: FastifyReply) { try { - const { id } = request.params - let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body const teamId = request.user.teamId; + const { id } = request.params + + let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body + console.log({ name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort }) if (id === 'new') { if (engine) { const host = getEngine(engine); @@ -100,17 +102,12 @@ export async function newDestination(request: FastifyRequest, re } } return reply.code(201).send({ id: destination.id }); - } - if (remoteIpAddress) { - await prisma.destinationDocker.create({ + } else { + const destination = await prisma.destinationDocker.create({ data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort } }); - return reply.code(201).send() - + return reply.code(201).send({ id: destination.id }) } - throw { - message: `Cannot save Docker Engine.` - }; } else { await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } }); return reply.code(201).send(); @@ -125,22 +122,20 @@ export async function newDestination(request: FastifyRequest, re export async function deleteDestination(request: FastifyRequest) { try { const { id } = request.params - const destination = await prisma.destinationDocker.delete({ where: { id } }); - if (destination.isCoolifyProxyUsed) { - const host = getEngine(destination.engine); - const { network } = destination; - const settings = await prisma.setting.findFirst(); - const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy'; - const { stdout: found } = await asyncExecShell( - `DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'` - ); - if (found) { - await asyncExecShell( - `DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}` - ); - await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`); + const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } }); + if (isCoolifyProxyUsed) { + if (engine || remoteVerified) { + const { stdout: found } = await executeDockerCmd({ + dockerId: id, + command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'` + }) + if (found) { + await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` }) + await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` }) + } } } + await prisma.destinationDocker.delete({ where: { id } }); return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -168,6 +163,7 @@ export async function startProxy(request: FastifyRequest) { await startTraefikProxy(id); return {} } catch ({ status, message }) { + console.log({status, message}) await stopTraefikProxy(id); return errorHandler({ status, message }) } @@ -245,9 +241,9 @@ export async function getDestinationStatus(request: FastifyRequest) { try { const { id } = request.params const destination = await prisma.destinationDocker.findUnique({ where: { id } }) - const containerName = 'coolify-proxy'; + const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy' }) return { - isRunning: await checkContainer({ dockerId: destination.id, container: containerName }) + isRunning } } 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 d04f4554d..bd429f37c 100644 --- a/apps/api/src/routes/api/v1/services/handlers.ts +++ b/apps/api/src/routes/api/v1/services/handlers.ts @@ -2,9 +2,9 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; import fs from 'fs/promises'; import yaml from 'js-yaml'; import bcrypt from 'bcryptjs'; -import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions } from '../../../../lib/common'; +import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd } from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; -import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker'; +import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker'; import cuid from 'cuid'; import type { OnlyId } from '../../../../types'; @@ -172,6 +172,31 @@ export async function newService(request: FastifyRequest, reply: FastifyReply) { return errorHandler({ status, message }) } } +export async function getServiceStatus(request: FastifyRequest) { + try { + const teamId = request.user.teamId; + const { id } = request.params; + + let isRunning = false; + let isExited = false + + const service = await getServiceFromDB({ id, teamId }); + const { destinationDockerId, settings } = service; + + if (destinationDockerId) { + isRunning = await checkContainer({ dockerId: service.destinationDocker.id, container: id }); + isExited = await isContainerExited(service.destinationDocker.id, id); + } + return { + isRunning, + isExited, + settings + } + } catch ({ status, message }) { + return errorHandler({ status, message }) + } +} + export async function getService(request: FastifyRequest) { try { const teamId = request.user.teamId; @@ -181,36 +206,8 @@ export async function getService(request: FastifyRequest) { if (!service) { throw { status: 404, message: 'Service not found.' } } - - const { destinationDockerId, destinationDocker, type, version, settings } = service; - let isRunning = false; - if (destinationDockerId) { - const host = getEngine(destinationDocker.engine); - const docker = dockerInstance({ destinationDocker }); - const baseImage = getServiceImage(type); - const images = getServiceImages(type); - docker.engine.pull(`${baseImage}:${version}`); - if (images?.length > 0) { - for (const image of images) { - docker.engine.pull(`${image}:latest`); - } - } - try { - const { stdout } = await asyncExecShell( - `DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}` - ); - - if (JSON.parse(stdout).Running) { - isRunning = true; - } - } catch (error) { - // - } - } return { - isRunning, service, - settings } } catch ({ status, message }) { return errorHandler({ status, message }) @@ -299,33 +296,22 @@ export async function getServiceLogs(request: FastifyRequest) { if (since !== 0) { since = day(since).unix(); } - const { destinationDockerId, destinationDocker } = await prisma.service.findUnique({ + const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }); if (destinationDockerId) { - const docker = dockerInstance({ destinationDocker }); try { - const container = await docker.engine.getContainer(id); - if (container) { + // const found = await checkContainer({ dockerId, container: id }) + // if (found) { const { default: ansi } = await import('strip-ansi') - const logs = ( - await container.logs({ - stdout: true, - stderr: true, - timestamps: true, - since, - tail: 5000 - }) - ) - .toString() - .split('\n') - .map((l) => ansi(l.slice(8))) - .filter((a) => a); - return { - logs - }; - } + const { stdout, stderr } = await executeDockerCmd({ 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) { @@ -742,7 +728,6 @@ async function startPlausibleAnalyticsService(request: FastifyRequest) { const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('nocodb'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -956,8 +938,8 @@ async function startNocodbService(request: FastifyRequest) { }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -970,7 +952,6 @@ async function stopNocodbService(request: FastifyRequest) { const service = await getServiceFromDB({ id, teamId }); const { destinationDockerId, destinationDocker, fqdn } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { await removeContainer({ id, dockerId: destinationDocker.id }); @@ -999,7 +980,6 @@ async function startMinioService(request: FastifyRequest) { } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('minio'); const publicPort = await getFreePort(); @@ -1058,8 +1038,8 @@ async function startMinioService(request: FastifyRequest) { }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } }); return {} } catch ({ status, message }) { @@ -1071,10 +1051,9 @@ async function stopMinioService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } }) if (destinationDockerId) { - const engine = destinationDocker.engine; const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { await removeContainer({ id, dockerId: destinationDocker.id }); @@ -1103,7 +1082,6 @@ async function startVscodeService(request: FastifyRequest) { } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('vscodeserver'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -1175,16 +1153,16 @@ async function startVscodeService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) const changePermissionOn = persistentStorage.map((p) => p.path); if (changePermissionOn.length > 0) { - await asyncExecShell( - `DOCKER_HOST=${host} docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join( + await executeDockerCmd({ + dockerId: destinationDocker.id, command: `docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join( ' ' )}` - ); + }) } return {} } catch ({ status, message }) { @@ -1196,9 +1174,8 @@ async function stopVscodeService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { await removeContainer({ id, dockerId: destinationDocker.id }); @@ -1236,7 +1213,6 @@ async function startWordpressService(request: FastifyRequest) } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const image = getServiceImage(type); const port = getServiceMainPort('wordpress'); @@ -1328,8 +1304,10 @@ async function startWordpressService(request: FastifyRequest) } const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1346,7 +1324,6 @@ async function stopWordpressService(request: FastifyRequest) { wordpress: { ftpEnabled } } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -1393,7 +1370,6 @@ async function startVaultwardenService(request: FastifyRequest service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('vaultwarden'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -1444,8 +1420,10 @@ async function startVaultwardenService(request: FastifyRequest }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1456,10 +1434,8 @@ async function stopVaultwardenService(request: FastifyRequest) const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -1483,7 +1459,6 @@ async function startLanguageToolService(request: FastifyRequest const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -1575,7 +1549,6 @@ async function startN8nService(request: FastifyRequest) { const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('n8n'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -1628,8 +1601,10 @@ async function startN8nService(request: FastifyRequest) { }; const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1640,10 +1615,8 @@ async function stopN8nService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -1667,7 +1640,6 @@ async function startUptimekumaService(request: FastifyRequest) const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('uptimekuma'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -1719,8 +1691,9 @@ async function startUptimekumaService(request: FastifyRequest) const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1731,10 +1704,8 @@ async function stopUptimekumaService(request: FastifyRequest) const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -1774,7 +1745,6 @@ async function startGhostService(request: FastifyRequest) { } } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const { workdir } = await createDirectories({ repository: type, buildId: id }); const image = getServiceImage(type); @@ -1871,8 +1841,9 @@ async function startGhostService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1883,10 +1854,8 @@ async function stopGhostService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { let found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -1917,7 +1886,6 @@ async function startMeilisearchService(request: FastifyRequest const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('meilisearch'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -1972,8 +1940,10 @@ async function startMeilisearchService(request: FastifyRequest const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -1984,10 +1954,8 @@ async function stopMeilisearchService(request: FastifyRequest) const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -2024,7 +1992,6 @@ async function startUmamiService(request: FastifyRequest) { } } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('umami'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -2191,8 +2158,10 @@ async function startUmamiService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -2203,10 +2172,8 @@ async function stopUmamiService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -2245,7 +2212,6 @@ async function startHasuraService(request: FastifyRequest) { hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase } } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('hasura'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -2327,8 +2293,9 @@ async function startHasuraService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { return errorHandler({ status, message }) @@ -2339,10 +2306,8 @@ async function stopHasuraService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -2396,7 +2361,6 @@ async function startFiderService(request: FastifyRequest) { } } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('fider'); const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -2489,8 +2453,8 @@ async function startFiderService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) return {} } catch ({ status, message }) { @@ -2502,10 +2466,8 @@ async function stopFiderService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -2554,12 +2516,10 @@ async function startMoodleService(request: FastifyRequest) { } } = service; const network = destinationDockerId && destinationDocker.network; - const host = getEngine(destinationDocker.engine); const port = getServiceMainPort('moodle'); const { workdir } = await createDirectories({ repository: type, buildId: id }); const image = getServiceImage(type); - const domain = getDomain(fqdn); const config = { moodle: { image: `${image}:${version}`, @@ -2652,8 +2612,10 @@ async function startMoodleService(request: FastifyRequest) { const composeFileDestination = `${workdir}/docker-compose.yaml`; await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); - await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); + + //await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` }) + await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` }) + return {} } catch ({ status, message }) { @@ -2665,10 +2627,8 @@ async function stopMoodleService(request: FastifyRequest) { const { id } = request.params; const teamId = request.user.teamId; const service = await getServiceFromDB({ id, teamId }); - const { destinationDockerId, destinationDocker, fqdn } = service; + const { destinationDockerId, destinationDocker } = service; if (destinationDockerId) { - const engine = destinationDocker.engine; - try { const found = await checkContainer({ dockerId: destinationDocker.id, container: id }); if (found) { @@ -2703,14 +2663,10 @@ export async function activatePlausibleUsers(request: FastifyRequest, re plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase } } = await getServiceFromDB({ id, teamId }); if (destinationDockerId) { - const docker = dockerInstance({ destinationDocker }); - const container = await docker.engine.getContainer(id); - const command = await container.exec({ - Cmd: [ - `psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"` - ] - }); - await command.start(); + await executeDockerCmd({ + dockerId: destinationDocker.id, + command: `docker exec ${id} 'psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"'` + }) return await reply.code(201).send() } throw { status: 500, message: 'Could not activate users.' } @@ -2742,7 +2698,6 @@ export async function activateWordpressFtp(request: FastifyRequest => { fastify.post('/:id', async (request, reply) => await saveService(request, reply)); fastify.delete('/:id', async (request) => await deleteService(request)); + fastify.get('/:id/status', async (request) => await getServiceStatus(request)); + fastify.post('/:id/check', async (request) => await checkService(request)); fastify.post('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply)); diff --git a/apps/ui/src/lib/locales/en.json b/apps/ui/src/lib/locales/en.json index cab9ecc80..df84a976b 100644 --- a/apps/ui/src/lib/locales/en.json +++ b/apps/ui/src/lib/locales/en.json @@ -250,7 +250,7 @@ "no_destination_found": "No destination found", "new_error_network_already_exists": "Network {{network}} already configured for another team!", "new": { - "saving_and_configuring_proxy": "Saving and configuring proxy...", + "saving_and_configuring_proxy": "Saving...", "install_proxy": "This will install a proxy on the destination to allow you to access your applications and services without any manual configuration (recommended for Docker).

Databases will have their own proxy.", "add_new_destination": "Add New Destination", "predefined_destinations": "Predefined destinations" diff --git a/apps/ui/src/lib/store.ts b/apps/ui/src/lib/store.ts index 245e8d6eb..096be2e52 100644 --- a/apps/ui/src/lib/store.ts +++ b/apps/ui/src/lib/store.ts @@ -41,14 +41,16 @@ export const status: Writable = writable({ initialLoading: true }, service: { - initialLoading: true, + isRunning: false, + isExited: false, loading: false, - isRunning: false + initialLoading: true }, database: { - initialLoading: true, + isRunning: false, + isExited: false, loading: false, - isRunning: false + initialLoading: true } }); @@ -60,7 +62,6 @@ export const features = readable({ export const location: Writable = writable(null) export const setLocation = (resource: any) => { - console.log(GITPOD_WORKSPACE_URL) if (GITPOD_WORKSPACE_URL && resource.exposePort) { const { href } = new URL(GITPOD_WORKSPACE_URL); const newURL = href diff --git a/apps/ui/src/routes/applications/[id]/__layout.svelte b/apps/ui/src/routes/applications/[id]/__layout.svelte index 02a1bd330..8d1f52015 100644 --- a/apps/ui/src/routes/applications/[id]/__layout.svelte +++ b/apps/ui/src/routes/applications/[id]/__layout.svelte @@ -36,7 +36,6 @@ return { props: { - isQueueActive, application }, stuff: { @@ -53,7 +52,6 @@ - - @@ -145,24 +157,27 @@ {#if !destination.remoteVerified} - {loading.verify ? 'Verifying...' : 'Verify Remote Docker Engine'} + {:else} + {/if} - {/if}
@@ -232,7 +247,7 @@
{/if} + {#if $status.service.isExited} + + + + + + + + + {/if} {#if $status.service.initialLoading}