fix: security hole

This commit is contained in:
Andras Bacsai 2022-12-06 10:27:51 +01:00
parent f12d453b5f
commit b45ad19732
22 changed files with 416 additions and 277 deletions

View File

@ -49,6 +49,7 @@
"is-port-reachable": "4.0.0", "is-port-reachable": "4.0.0",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"minimist": "^1.2.7",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"node-os-utils": "1.3.7", "node-os-utils": "1.3.7",
"p-all": "4.0.0", "p-all": "4.0.0",
@ -56,6 +57,7 @@
"prisma": "4.6.1", "prisma": "4.6.1",
"public-ip": "6.0.1", "public-ip": "6.0.1",
"pump": "3.0.0", "pump": "3.0.0",
"shell-quote": "^1.7.4",
"socket.io": "4.5.3", "socket.io": "4.5.3",
"ssh-config": "4.1.6", "ssh-config": "4.1.6",
"strip-ansi": "7.0.1", "strip-ansi": "7.0.1",

View File

@ -9,7 +9,7 @@ import autoLoad from '@fastify/autoload';
import socketIO from 'fastify-socket.io' import socketIO from 'fastify-socket.io'
import socketIOServer from './realtime' import socketIOServer from './realtime'
import { asyncExecShell, cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeDockerCmd, executeSSHCmd, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common'; import { cleanupDockerStorage, createRemoteEngineConfiguration, decrypt, executeCommand, executeSSHCmd, generateDatabaseConfiguration, isDev, listSettings, prisma, sentryDSN, startTraefikProxy, startTraefikTCPProxy, version } from './lib/common';
import { scheduler } from './lib/scheduler'; import { scheduler } from './lib/scheduler';
import { compareVersions } from 'compare-versions'; import { compareVersions } from 'compare-versions';
import Graceful from '@ladjs/graceful' import Graceful from '@ladjs/graceful'
@ -261,7 +261,7 @@ async function initServer() {
} }
try { try {
console.log(`[001] Initializing server...`); console.log(`[001] Initializing server...`);
await asyncExecShell(`docker network create --attachable coolify`); await executeCommand({ command: `docker network create --attachable coolify` });
} catch (error) { } } catch (error) { }
try { try {
console.log(`[002] Cleanup stucked builds...`); console.log(`[002] Cleanup stucked builds...`);
@ -272,7 +272,7 @@ async function initServer() {
} catch (error) { } } catch (error) { }
try { try {
console.log('[003] Cleaning up old build sources under /tmp/build-sources/...'); console.log('[003] Cleaning up old build sources under /tmp/build-sources/...');
if (!isDev) await fs.rm('/tmp/build-sources', { recursive: true, force: true }) await fs.rm('/tmp/build-sources', { recursive: true, force: true })
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }
@ -323,14 +323,10 @@ async function autoUpdater() {
if (!isDev) { if (!isDev) {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
if (isAutoUpdateEnabled) { if (isAutoUpdateEnabled) {
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` })
await asyncExecShell(`env | grep '^COOLIFY' > .env`); await executeCommand({ command: `env | grep '^COOLIFY' > .env` })
await asyncExecShell( await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` })
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` await executeCommand({ command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` })
);
await asyncExecShell(
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
);
} }
} else { } else {
console.log('Updating (not really in dev mode).'); console.log('Updating (not really in dev mode).');
@ -351,8 +347,8 @@ async function checkFluentBit() {
}); });
const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true }); const { found } = await checkContainer({ dockerId: id, container: 'coolify-fluentbit', remove: true });
if (!found) { if (!found) {
await asyncExecShell(`env | grep '^COOLIFY' > .env`); await executeCommand({ command: `env | grep '^COOLIFY' > .env` });
await asyncExecShell(`docker compose up -d fluent-bit`); await executeCommand({ command: `docker compose up -d fluent-bit` });
} }
} }
} catch (error) { } catch (error) {
@ -462,13 +458,13 @@ async function copySSLCertificates() {
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} finally { } finally {
await asyncExecShell(`find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete`) await executeCommand({ command: `find /tmp/ -maxdepth 1 -type f -name '*-*.pem' -delete` })
} }
} }
async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) { async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddress: string) {
try { try {
await asyncExecShell(`scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/`) await executeCommand({ command: `scp /tmp/${id}-cert.pem /tmp/${id}-key.pem ${remoteIpAddress}:/tmp/` })
await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` }) await executeSSHCmd({ dockerId, command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` }) await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` }) await executeSSHCmd({ dockerId, command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
@ -478,9 +474,9 @@ async function copyRemoteCertificates(id: string, dockerId: string, remoteIpAddr
} }
async function copyLocalCertificates(id: string) { async function copyLocalCertificates(id: string) {
try { try {
await asyncExecShell(`docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`) await executeCommand({ command: `docker exec coolify-proxy sh -c 'test -d /etc/traefik/acme/custom/ || mkdir -p /etc/traefik/acme/custom/'`, shell: true })
await asyncExecShell(`docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/`) await executeCommand({ command: `docker cp /tmp/${id}-key.pem coolify-proxy:/etc/traefik/acme/custom/` })
await asyncExecShell(`docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/`) await executeCommand({ command: `docker cp /tmp/${id}-cert.pem coolify-proxy:/etc/traefik/acme/custom/` })
} catch (error) { } catch (error) {
console.log({ error }) console.log({ error })
} }
@ -498,12 +494,13 @@ async function cleanupStorage() {
try { try {
let stdout = null let stdout = null
if (!isDev) { if (!isDev) {
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` }) const output = await executeCommand({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'`, shell: true })
stdout = output.stdout; stdout = output.stdout;
} else { } else {
const output = await asyncExecShell( const output = await executeCommand({
`df -kPT /` command:
); `df -kPT /`
});
stdout = output.stdout; stdout = output.stdout;
} }
let lines = stdout.trim().split('\n'); let lines = stdout.trim().split('\n');

View File

@ -4,7 +4,7 @@ import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { copyBaseConfigurationFiles, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, saveDockerRegistryCredentials, setDefaultConfiguration } from '../lib/buildPacks/common'; import { copyBaseConfigurationFiles, makeLabelForSimpleDockerfile, makeLabelForStandaloneApplication, saveBuildLog, saveDockerRegistryCredentials, setDefaultConfiguration } from '../lib/buildPacks/common';
import { createDirectories, decrypt, defaultComposeConfiguration, executeDockerCmd, getDomain, prisma, decryptApplication, isDev, pushToRegistry } from '../lib/common'; import { createDirectories, decrypt, defaultComposeConfiguration, getDomain, prisma, decryptApplication, isDev, pushToRegistry, executeCommand } from '../lib/common';
import * as importers from '../lib/importers'; import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks'; import * as buildpacks from '../lib/buildPacks';
@ -70,14 +70,19 @@ import * as buildpacks from '../lib/buildPacks';
if (destinationDockerId) { if (destinationDockerId) {
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } }); await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
try { try {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
}) })
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
}
}
}
} catch (error) { } catch (error) {
// //
} }
@ -154,7 +159,7 @@ import * as buildpacks from '../lib/buildPacks';
volumes: Object.assign({}, ...composeVolumes) volumes: Object.assign({}, ...composeVolumes)
}; };
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` }) await executeCommand({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId }); await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId });
} catch (error) { } catch (error) {
await saveBuildLog({ line: error, buildId, applicationId }); await saveBuildLog({ line: error, buildId, applicationId });
@ -187,9 +192,7 @@ import * as buildpacks from '../lib/buildPacks';
if (error instanceof Error) { if (error instanceof Error) {
await saveBuildLog({ line: error.message, buildId, applicationId: application.id }); await saveBuildLog({ line: error.message, buildId, applicationId: application.id });
} }
if (!isDev) { await fs.rm(workdir, { recursive: true, force: true });
await fs.rm(workdir, { recursive: true, force: true });
}
return; return;
} }
try { try {
@ -208,9 +211,7 @@ import * as buildpacks from '../lib/buildPacks';
await saveBuildLog({ line: error.stderr, buildId, applicationId }); await saveBuildLog({ line: error.stderr, buildId, applicationId });
} }
} finally { } finally {
if (!isDev) { await fs.rm(workdir, { recursive: true, force: true });
await fs.rm(workdir, { recursive: true, force: true });
}
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } }); await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
} }
return; return;
@ -409,7 +410,7 @@ import * as buildpacks from '../lib/buildPacks';
} }
try { try {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker image inspect ${applicationId}:${tag}` command: `docker image inspect ${applicationId}:${tag}`
}) })
@ -423,7 +424,7 @@ import * as buildpacks from '../lib/buildPacks';
} }
try { try {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker ${location ? `--config ${location}` : ''} pull ${imageName}:${customTag}` command: `docker ${location ? `--config ${location}` : ''} pull ${imageName}:${customTag}`
}) })
@ -514,19 +515,24 @@ import * as buildpacks from '../lib/buildPacks';
if (buildPack === 'compose') { if (buildPack === 'compose') {
try { try {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=coolify.applicationId=${applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
}) })
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
}
}
}
} catch (error) { } catch (error) {
// //
} }
try { try {
await executeDockerCmd({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` }) await executeCommand({ debug, buildId, applicationId, dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId }); await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId });
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } }); await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
await prisma.application.update({ await prisma.application.update({
@ -549,14 +555,19 @@ import * as buildpacks from '../lib/buildPacks';
} else { } else {
try { try {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.service=${pullmergeRequestId ? imageId : applicationId}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
}) })
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
}
}
}
} catch (error) { } catch (error) {
// //
} }
@ -622,7 +633,7 @@ import * as buildpacks from '../lib/buildPacks';
volumes: Object.assign({}, ...composeVolumes) volumes: Object.assign({}, ...composeVolumes)
}; };
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` }) await executeCommand({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId }); await saveBuildLog({ line: 'Deployed successfully', buildId, applicationId });
} catch (error) { } catch (error) {
await saveBuildLog({ line: error, buildId, applicationId }); await saveBuildLog({ line: error, buildId, applicationId });
@ -660,9 +671,7 @@ import * as buildpacks from '../lib/buildPacks';
if (error instanceof Error) { if (error instanceof Error) {
await saveBuildLog({ line: error.message, buildId, applicationId: application.id }); await saveBuildLog({ line: error.message, buildId, applicationId: application.id });
} }
if (!isDev) { await fs.rm(workdir, { recursive: true, force: true });
await fs.rm(workdir, { recursive: true, force: true });
}
return; return;
} }
try { try {
@ -680,9 +689,7 @@ import * as buildpacks from '../lib/buildPacks';
} }
} finally { } finally {
if (!isDev) { await fs.rm(workdir, { recursive: true, force: true });
await fs.rm(workdir, { recursive: true, force: true });
}
await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } }); await prisma.build.update({ where: { id: buildId }, data: { status: 'success' } });
} }
}); });

View File

@ -1,4 +1,4 @@
import { base64Encode, decrypt, encrypt, executeDockerCmd, generateTimestamp, getDomain, isARM, isDev, prisma, version } from "../common"; import { base64Encode, decrypt, encrypt, executeCommand, generateTimestamp, getDomain, isARM, isDev, prisma, version } from "../common";
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { day } from "../dayjs"; import { day } from "../dayjs";
@ -656,7 +656,7 @@ export async function buildImage({
location = await saveDockerRegistryCredentials({ url, username, password, workdir }) location = await saveDockerRegistryCredentials({ url, username, password, workdir })
} }
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` }) await executeCommand({ debug, buildId, applicationId, dockerId, command: `docker ${location ? `--config ${location}` : ''} build --progress plain -f ${workdir}/${dockerFile} -t ${cache} --build-arg SOURCE_COMMIT=${commit} ${workdir}` })
const { status } = await prisma.build.findUnique({ where: { id: buildId } }) const { status } = await prisma.build.findUnique({ where: { id: buildId } })
if (status === 'canceled') { if (status === 'canceled') {

View File

@ -1,5 +1,5 @@
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { defaultComposeConfiguration, executeDockerCmd } from '../common'; import { defaultComposeConfiguration, executeCommand } from '../common';
import { buildImage, saveBuildLog } from './common'; import { buildImage, saveBuildLog } from './common';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
@ -108,8 +108,8 @@ export default async function (data) {
} }
dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } }) dockerComposeYaml['networks'] = Object.assign({ ...networks }, { [network]: { external: true } })
await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml)); await fs.writeFile(fileYaml, yaml.dump(dockerComposeYaml));
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` }) await executeCommand({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} pull` })
await saveBuildLog({ line: 'Pulling images from Compose file', buildId, applicationId }); await saveBuildLog({ line: 'Pulling images from Compose file', buildId, applicationId });
await executeDockerCmd({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain` }) await executeCommand({ debug, buildId, applicationId, dockerId, command: `docker compose --project-directory ${workdir} build --progress plain` })
await saveBuildLog({ line: 'Building images from Compose file', buildId, applicationId }); await saveBuildLog({ line: 'Building images from Compose file', buildId, applicationId });
} }

View File

@ -1,11 +1,11 @@
import { executeDockerCmd, prisma } from "../common" import { executeCommand } from "../common"
import { saveBuildLog } from "./common"; import { saveBuildLog } from "./common";
export default async function (data: any): Promise<void> { export default async function (data: any): Promise<void> {
const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data const { buildId, applicationId, tag, dockerId, debug, workdir, baseDirectory, baseImage } = data
try { try {
await saveBuildLog({ line: `Building image started.`, buildId, applicationId }); await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
await executeDockerCmd({ await executeCommand({
buildId, buildId,
debug, debug,
dockerId, dockerId,

View File

@ -1,6 +1,6 @@
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import TOML from '@iarna/toml'; import TOML from '@iarna/toml';
import { asyncExecShell } from '../common'; import { executeCommand } from '../common';
import { buildCacheImageWithCargo, buildImage } from './common'; import { buildCacheImageWithCargo, buildImage } from './common';
const createDockerfile = async (data, image, name): Promise<void> => { const createDockerfile = async (data, image, name): Promise<void> => {
@ -28,7 +28,7 @@ const createDockerfile = async (data, image, name): Promise<void> => {
export default async function (data) { export default async function (data) {
try { try {
const { workdir, baseImage, baseBuildImage } = data; const { workdir, baseImage, baseBuildImage } = data;
const { stdout: cargoToml } = await asyncExecShell(`cat ${workdir}/Cargo.toml`); const { stdout: cargoToml } = await executeCommand({ command: `cat ${workdir}/Cargo.toml` });
const parsedToml: any = TOML.parse(cargoToml); const parsedToml: any = TOML.parse(cargoToml);
const name = parsedToml.package.name; const name = parsedToml.package.name;
await buildCacheImageWithCargo(data, baseBuildImage); await buildCacheImageWithCargo(data, baseBuildImage);

View File

@ -17,6 +17,7 @@ import { checkContainer, removeContainer } from './docker';
import { day } from './dayjs'; import { day } from './dayjs';
import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common'; import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common';
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa';
export const version = '3.12.0'; export const version = '3.12.0';
export const isDev = process.env.NODE_ENV === 'development'; export const isDev = process.env.NODE_ENV === 'development';
@ -63,7 +64,6 @@ const otherTraefikEndpoint = isDev
: 'http://coolify:3000/webhooks/traefik/other.json'; : 'http://coolify:3000/webhooks/traefik/other.json';
export const uniqueName = (): string => uniqueNamesGenerator(customConfig); export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
export const asyncExecShell = util.promisify(exec);
export const asyncExecShellStream = async ({ export const asyncExecShellStream = async ({
debug, debug,
buildId, buildId,
@ -303,7 +303,7 @@ export async function isDomainConfigured({
export async function getContainerUsage(dockerId: string, container: string): Promise<any> { export async function getContainerUsage(dockerId: string, container: string): Promise<any> {
try { try {
const { stdout } = await executeDockerCmd({ const { stdout } = await executeCommand({
dockerId, dockerId,
command: `docker container stats ${container} --no-stream --no-trunc --format "{{json .}}"` command: `docker container stats ${container} --no-stream --no-trunc --format "{{json .}}"`
}); });
@ -508,36 +508,13 @@ export async function createRemoteEngineConfiguration(id: string) {
remoteUser remoteUser
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } }); } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 }); await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
// Needed for remote docker compose
// const { stdout: numberOfSSHAgentsRunning } = await asyncExecShell(
// `ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l`
// );
// if (numberOfSSHAgentsRunning !== '' && Number(numberOfSSHAgentsRunning.trim()) == 0) {
// try {
// await fs.stat(`/tmp/coolify-ssh-agent.pid`);
// await fs.rm(`/tmp/coolify-ssh-agent.pid`);
// } catch (error) { }
// await asyncExecShell(`eval $(ssh-agent -sa /tmp/coolify-ssh-agent.pid)`);
// }
// await asyncExecShell(`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${sshKeyFile}`);
// const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(
// `ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${localPort}:localhost:${remotePort}' | grep -v grep | wc -l`
// );
// if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) {
// try {
// await asyncExecShell(
// `SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`
// );
// } catch (error) { }
// }
const config = sshConfig.parse(''); const config = sshConfig.parse('');
const Host = `${remoteIpAddress}-remote` const Host = `${remoteIpAddress}-remote`
try { try {
await asyncExecShell(`ssh-keygen -R ${Host}`); await executeCommand({ command: `ssh-keygen -R ${Host}` });
await asyncExecShell(`ssh-keygen -R ${remoteIpAddress}`); await executeCommand({ command: `ssh-keygen -R ${remoteIpAddress}` });
await asyncExecShell(`ssh-keygen -R localhost:${localPort}`); await executeCommand({ command: `ssh-keygen -R localhost:${localPort}` });
} catch (error) { } } catch (error) { }
@ -566,8 +543,102 @@ export async function createRemoteEngineConfiguration(id: string) {
} }
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config)); return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config));
} }
export async function executeCommand({ command, dockerId = null, sshCommand = false, shell = false, buildId, applicationId, debug }: { command: string, sshCommand?: boolean, shell?: boolean, dockerId?: string, buildId?: string, applicationId?: string, debug?: boolean }): Promise<ExecaChildProcess<string>> {
const { execa, execaCommand } = await import('execa')
const { parse } = await import('shell-quote')
const parsedCommand = parse(command);
const dockerCommand = parsedCommand[0];
const dockerArgs = parsedCommand.slice(1);
if (dockerId) {
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId);
engine = `ssh://${remoteIpAddress}-remote`;
} else {
engine = 'unix:///var/run/docker.sock';
}
if (process.env.CODESANDBOX_HOST) {
if (command.startsWith('docker compose')) {
command = command.replace(/docker compose/gi, 'docker-compose');
}
}
if (sshCommand) {
if (shell) {
return execaCommand(`ssh ${remoteIpAddress}-remote ${command}`, { shell: true, stdio: 'inherit' });
}
return await execa('ssh', [`${remoteIpAddress}-remote`, ...dockerArgs]);
}
return await new Promise(async (resolve, reject) => {
let subprocess = null;
if (shell) {
subprocess = execaCommand(command, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
});
} else {
subprocess = execa(dockerCommand, dockerArgs, {
env: { DOCKER_BUILDKIT: '1', DOCKER_HOST: engine }
});
}
const logs = [];
subprocess.stdout.on('data', async (data) => {
const stdout = data.toString();
const array = stdout.split('\n');
for (const line of array) {
if (line !== '\n' && line !== '') {
const log = {
line: `${line.replace('\n', '')}`,
buildId,
applicationId
}
logs.push(log);
if (debug) {
await saveBuildLog(log);
}
}
}
});
subprocess.stderr.on('data', async (data) => {
const stderr = data.toString();
const array = stderr.split('\n');
for (const line of array) {
if (line !== '\n' && line !== '') {
const log = {
line: `${line.replace('\n', '')}`,
buildId,
applicationId
}
logs.push(log);
if (debug) {
await saveBuildLog(log);
}
}
}
});
subprocess.on('exit', async (code) => {
await asyncSleep(1000);
if (code === 0) {
resolve(code);
} else {
if (!debug) {
for (const log of logs) {
await saveBuildLog(log);
}
}
reject(code);
}
});
})
} else {
if (shell) {
return execaCommand(command, { shell: true });
}
return await execa(dockerCommand, dockerArgs);
}
}
export async function executeSSHCmd({ dockerId, command }) { export async function executeSSHCmd({ dockerId, command }) {
const { execaCommand } = await import('execa') const { execaCommand, execa } = await import('execa')
const { parse } = await import('shell-quote')
let { remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } }) let { remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) { if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId) await createRemoteEngineConfiguration(dockerId)
@ -577,10 +648,12 @@ export async function executeSSHCmd({ dockerId, command }) {
command = command.replace(/docker compose/gi, 'docker-compose') command = command.replace(/docker compose/gi, 'docker-compose')
} }
} }
return await execaCommand(`ssh ${remoteIpAddress}-remote ${command}`) const dockerArgs = parse(command);
return await execa('ssh', [`${remoteIpAddress}-remote`, ...dockerArgs]);
} }
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> { export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
const { execaCommand } = await import('execa') const { execa } = await import('execa')
const { parse } = await import('shell-quote')
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } }) let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) { if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId); await createRemoteEngineConfiguration(dockerId);
@ -593,10 +666,13 @@ export async function executeDockerCmd({ debug, buildId, applicationId, dockerId
command = command.replace(/docker compose/gi, 'docker-compose'); command = command.replace(/docker compose/gi, 'docker-compose');
} }
} }
const parsedCommand = parse(command);
const dockerCommand = parsedCommand[0];
const dockerArgs = parsedCommand.slice(1);
if (command.startsWith(`docker build`) || command.startsWith(`pack build`) || command.startsWith(`docker compose build`)) { if (command.startsWith(`docker build`) || command.startsWith(`pack build`) || command.startsWith(`docker compose build`)) {
return await asyncExecShellStream({ debug, buildId, applicationId, command, engine }); return await asyncExecShellStream({ debug, buildId, applicationId, command, engine });
} }
return await execaCommand(command, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine }, shell: true }) return await execa(dockerCommand, dockerArgs, { env: { DOCKER_BUILDKIT: "1", DOCKER_HOST: engine } });
} }
export async function startTraefikProxy(id: string): Promise<void> { export async function startTraefikProxy(id: string): Promise<void> {
const { engine, network, remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id } }) const { engine, network, remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id } })
@ -604,18 +680,18 @@ export async function startTraefikProxy(id: string): Promise<void> {
const { id: settingsId, ipv4, ipv6 } = await listSettings(); const { id: settingsId, ipv4, ipv6 } = await listSettings();
if (!found) { if (!found) {
const { stdout: coolifyNetwork } = await executeDockerCmd({ const { stdout: coolifyNetwork } = await executeCommand({
dockerId: id, dockerId: id,
command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"` command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`
}); });
if (!coolifyNetwork) { if (!coolifyNetwork) {
await executeDockerCmd({ await executeCommand({
dockerId: id, dockerId: id,
command: `docker network create --attachable coolify-infra` command: `docker network create --attachable coolify-infra`
}); });
} }
const { stdout: Config } = await executeDockerCmd({ const { stdout: Config } = await executeCommand({
dockerId: id, dockerId: id,
command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'`
}); });
@ -630,7 +706,7 @@ export async function startTraefikProxy(id: string): Promise<void> {
} }
traefikUrl = `${ip}/webhooks/traefik/remote/${id}`; traefikUrl = `${ip}/webhooks/traefik/remote/${id}`;
} }
await executeDockerCmd({ await executeCommand({
dockerId: id, dockerId: id,
command: `docker run --restart always \ command: `docker run --restart always \
--add-host 'host.docker.internal:host-gateway' \ --add-host 'host.docker.internal:host-gateway' \
@ -655,7 +731,6 @@ export async function startTraefikProxy(id: string): Promise<void> {
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \ --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web \
--log.level=error` --log.level=error`
}); });
await prisma.setting.update({ where: { id: settingsId }, data: { proxyHash: null } });
await prisma.destinationDocker.update({ await prisma.destinationDocker.update({
where: { id }, where: { id },
data: { isCoolifyProxyUsed: true } data: { isCoolifyProxyUsed: true }
@ -679,13 +754,13 @@ export async function startTraefikProxy(id: string): Promise<void> {
export async function configureNetworkTraefikProxy(destination: any): Promise<void> { export async function configureNetworkTraefikProxy(destination: any): Promise<void> {
const { id } = destination; const { id } = destination;
const { stdout: networks } = await executeDockerCmd({ const { stdout: networks } = await executeCommand({
dockerId: id, dockerId: id,
command: `docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'` command: `docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'`
}); });
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(','); const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
if (!configuredNetworks.includes(destination.network)) { if (!configuredNetworks.includes(destination.network)) {
await executeDockerCmd({ await executeCommand({
dockerId: destination.id, dockerId: destination.id,
command: `docker network connect ${destination.network} coolify-proxy` command: `docker network connect ${destination.network} coolify-proxy`
}); });
@ -700,13 +775,12 @@ export async function stopTraefikProxy(
where: { id }, where: { id },
data: { isCoolifyProxyUsed: false } data: { isCoolifyProxyUsed: false }
}); });
const { id: settingsId } = await prisma.setting.findFirst({});
await prisma.setting.update({ where: { id: settingsId }, data: { proxyHash: null } });
try { try {
if (found) { if (found) {
await executeDockerCmd({ await executeCommand({
dockerId: id, dockerId: id,
command: `docker stop -t 0 coolify-proxy && docker rm coolify-proxy` command: `docker stop -t 0 coolify-proxy && docker rm coolify-proxy`,
shell: true
}); });
} }
} catch (error) { } catch (error) {
@ -1099,9 +1173,9 @@ export const createDirectories = async ({
workdirFound = !!(await fs.stat(workdir)); workdirFound = !!(await fs.stat(workdir));
} catch (error) { } } catch (error) { }
if (workdirFound) { if (workdirFound) {
await asyncExecShell(`rm -fr ${workdir}`); await executeCommand({ command: `rm -fr ${workdir}` });
} }
await asyncExecShell(`mkdir -p ${workdir}`); await executeCommand({ command: `mkdir -p ${workdir}` });
return { return {
workdir, workdir,
repodir repodir
@ -1117,7 +1191,7 @@ export async function stopDatabaseContainer(database: any): Promise<boolean> {
} = database; } = database;
if (destinationDockerId) { if (destinationDockerId) {
try { try {
const { stdout } = await executeDockerCmd({ const { stdout } = await executeCommand({
dockerId, dockerId,
command: `docker inspect --format '{{json .State}}' ${id}` command: `docker inspect --format '{{json .State}}' ${id}`
}); });
@ -1145,9 +1219,10 @@ export async function stopTcpHttpProxy(
const { found } = await checkContainer({ dockerId, container }); const { found } = await checkContainer({ dockerId, container });
try { try {
if (found) { if (found) {
return await executeDockerCmd({ return await executeCommand({
dockerId, dockerId,
command: `docker stop -t 0 ${container} && docker rm ${container}` command: `docker stop -t 0 ${container} && docker rm ${container}`,
shell: true
}); });
} }
} catch (error) { } catch (error) {
@ -1169,34 +1244,34 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) {
} = database; } = database;
if (destinationDockerId) { if (destinationDockerId) {
if (type === 'mysql') { if (type === 'mysql') {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"ALTER USER '${user}'@'%' IDENTIFIED WITH caching_sha2_password BY '${newPassword}';\"` command: `docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"ALTER USER '${user}'@'%' IDENTIFIED WITH caching_sha2_password BY '${newPassword}';\"`
}); });
} else if (type === 'mariadb') { } else if (type === 'mariadb') {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"SET PASSWORD FOR '${user}'@'%' = PASSWORD('${newPassword}');\"` command: `docker exec ${id} mysql -u ${rootUser} -p${rootUserPassword} -e \"SET PASSWORD FOR '${user}'@'%' = PASSWORD('${newPassword}');\"`
}); });
} else if (type === 'postgresql') { } else if (type === 'postgresql') {
if (isRoot) { if (isRoot) {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker exec ${id} psql postgresql://postgres:${rootUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role postgres WITH PASSWORD '${newPassword}'"` command: `docker exec ${id} psql postgresql://postgres:${rootUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role postgres WITH PASSWORD '${newPassword}'"`
}); });
} else { } else {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker exec ${id} psql postgresql://${dbUser}:${dbUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role ${user} WITH PASSWORD '${newPassword}'"` command: `docker exec ${id} psql postgresql://${dbUser}:${dbUserPassword}@${id}:5432/${defaultDatabase} -c "ALTER role ${user} WITH PASSWORD '${newPassword}'"`
}); });
} }
} else if (type === 'mongodb') { } else if (type === 'mongodb') {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker exec ${id} mongo 'mongodb://${rootUser}:${rootUserPassword}@${id}:27017/admin?readPreference=primary&ssl=false' --eval "db.changeUserPassword('${user}','${newPassword}')"` command: `docker exec ${id} mongo 'mongodb://${rootUser}:${rootUserPassword}@${id}:27017/admin?readPreference=primary&ssl=false' --eval "db.changeUserPassword('${user}','${newPassword}')"`
}); });
} else if (type === 'redis') { } else if (type === 'redis') {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker exec ${id} redis-cli -u redis://${dbUserPassword}@${id}:6379 --raw CONFIG SET requirepass ${newPassword}` command: `docker exec ${id} redis-cli -u redis://${dbUserPassword}@${id}:6379 --raw CONFIG SET requirepass ${newPassword}`
}); });
@ -1370,7 +1445,7 @@ export async function startTraefikTCPProxy(
}); });
try { try {
if (foundDependentContainer && !found) { if (foundDependentContainer && !found) {
const { stdout: Config } = await executeDockerCmd({ const { stdout: Config } = await executeCommand({
dockerId, dockerId,
command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'` command: `docker network inspect ${network} --format '{{json .IPAM.Config }}'`
}); });
@ -1417,16 +1492,17 @@ export async function startTraefikTCPProxy(
} }
}; };
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy)); await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker compose -f /tmp/docker-compose-${id}.yaml up -d` command: `docker compose -f /tmp/docker-compose-${id}.yaml up -d`
}); });
await fs.rm(`/tmp/docker-compose-${id}.yaml`); await fs.rm(`/tmp/docker-compose-${id}.yaml`);
} }
if (!foundDependentContainer && found) { if (!foundDependentContainer && found) {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker stop -t 0 ${container} && docker rm ${container}` command: `docker stop -t 0 ${container} && docker rm ${container}`,
shell: true
}); });
} }
} catch (error) { } catch (error) {
@ -1537,7 +1613,7 @@ export async function stopBuild(buildId, applicationId) {
await cleanupDB(buildId, applicationId); await cleanupDB(buildId, applicationId);
return reject(new Error('Canceled.')); return reject(new Error('Canceled.'));
} }
const { stdout: buildContainers } = await executeDockerCmd({ const { stdout: buildContainers } = await executeCommand({
dockerId, dockerId,
command: `docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'` command: `docker container ls --filter "label=coolify.buildId=${buildId}" --format '{{json .}}'`
}); });
@ -1580,26 +1656,28 @@ export function convertTolOldVolumeNames(type) {
export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) { export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
// Cleanup old coolify images // Cleanup old coolify images
try { try {
let { stdout: images } = await executeDockerCmd({ let { stdout: images } = await executeCommand({
dockerId, dockerId,
command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r` command: `docker images coollabsio/coolify --filter before="coollabsio/coolify:${version}" -q | xargs -r`,
shell: true
}); });
images = images.trim(); images = images.trim();
if (images) { if (images) {
await executeDockerCmd({ dockerId, command: `docker rmi -f ${images}" -q | xargs -r` }); await executeCommand({ dockerId, command: `docker rmi -f ${images}" -q | xargs -r`, shell: true });
} }
} catch (error) { } } catch (error) { }
if (lowDiskSpace || force) { if (lowDiskSpace || force) {
// Cleanup images that are not used // Cleanup images that are not used
try { try {
await executeDockerCmd({ dockerId, command: `docker image prune -f` }); await executeCommand({ dockerId, command: `docker image prune -f` });
} catch (error) { } } catch (error) { }
const { numberOfDockerImagesKeptLocally } = await prisma.setting.findUnique({ where: { id: '0' } }) const { numberOfDockerImagesKeptLocally } = await prisma.setting.findUnique({ where: { id: '0' } })
const { stdout: images } = await executeDockerCmd({ const { stdout: images } = await executeCommand({
dockerId, dockerId,
command: `docker images | grep -v "<none>" | grep -v REPOSITORY | awk '{print $1, $2}'` command: `docker images | grep -v "<none>" | grep -v REPOSITORY | awk '{print $1, $2}'`,
shell: true
}); });
const imagesArray = images.trim().replaceAll(' ', ':').split('\n'); const imagesArray = images.trim().replaceAll(' ', ':').split('\n');
const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0])); const imagesSet = new Set(imagesArray.map((image) => image.split(':')[0]));
@ -1618,12 +1696,12 @@ export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
} }
} }
for (const image of deleteImage) { for (const image of deleteImage) {
await executeDockerCmd({ dockerId, command: `docker image rm -f ${image}` }); await executeCommand({ dockerId, command: `docker image rm -f ${image}` });
} }
// Prune coolify managed containers // Prune coolify managed containers
try { try {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker container prune -f --filter "label=coolify.managed=true"` command: `docker container prune -f --filter "label=coolify.managed=true"`
}); });
@ -1631,7 +1709,7 @@ export async function cleanupDockerStorage(dockerId, lowDiskSpace, force) {
// Cleanup build caches // Cleanup build caches
try { try {
await executeDockerCmd({ dockerId, command: `docker builder prune -a -f` }); await executeCommand({ dockerId, command: `docker builder prune -a -f` });
} catch (error) { } } catch (error) { }
} }
} }
@ -1718,11 +1796,11 @@ export async function pushToRegistry(application: any, workdir: string, tag: str
const location = `${workdir}/.docker` const location = `${workdir}/.docker`
const tagCommand = `docker tag ${application.id}:${tag} ${imageName}:${customTag}` const tagCommand = `docker tag ${application.id}:${tag} ${imageName}:${customTag}`
const pushCommand = `docker --config ${location} push ${imageName}:${customTag}` const pushCommand = `docker --config ${location} push ${imageName}:${customTag}`
await executeDockerCmd({ await executeCommand({
dockerId: application.destinationDockerId, dockerId: application.destinationDockerId,
command: tagCommand command: tagCommand
}) })
await executeDockerCmd({ await executeCommand({
dockerId: application.destinationDockerId, dockerId: application.destinationDockerId,
command: pushCommand command: pushCommand
}) })

View File

@ -1,4 +1,4 @@
import { executeDockerCmd } from './common'; import { executeCommand } from './common';
export function formatLabelsOnDocker(data) { export function formatLabelsOnDocker(data) {
return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => { return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => {
@ -16,7 +16,7 @@ export function formatLabelsOnDocker(data) {
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> { export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> {
let containerFound = false; let containerFound = false;
try { try {
const { stdout } = await executeDockerCmd({ const { stdout } = await executeCommand({
dockerId, dockerId,
command: command:
`docker inspect --format '{{json .State}}' ${container}` `docker inspect --format '{{json .State}}' ${container}`
@ -28,14 +28,14 @@ export async function checkContainer({ dockerId, container, remove = false }: {
const isRestarting = status === 'restarting' const isRestarting = status === 'restarting'
const isExited = status === 'exited' const isExited = status === 'exited'
if (status === 'created') { if (status === 'created') {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: command:
`docker rm ${container}` `docker rm ${container}`
}); });
} }
if (remove && status === 'exited') { if (remove && status === 'exited') {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: command:
`docker rm ${container}` `docker rm ${container}`
@ -62,7 +62,7 @@ export async function checkContainer({ dockerId, container, remove = false }: {
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> { export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
let isExited = false; let isExited = false;
try { try {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` }) const { stdout } = await executeCommand({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
if (stdout.trim() === 'exited') { if (stdout.trim() === 'exited') {
isExited = true; isExited = true;
} }
@ -81,13 +81,13 @@ export async function removeContainer({
dockerId: string; dockerId: string;
}): Promise<void> { }): Promise<void> {
try { try {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` }) const { stdout } = await executeCommand({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
if (JSON.parse(stdout).Running) { if (JSON.parse(stdout).Running) {
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` }) await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}` }) await executeCommand({ dockerId, command: `docker rm ${id}` })
} }
if (JSON.parse(stdout).Status === 'exited') { if (JSON.parse(stdout).Status === 'exited') {
await executeDockerCmd({ dockerId, command: `docker rm ${id}` }) await executeCommand({ dockerId, command: `docker rm ${id}` })
} }
} catch (error) { } catch (error) {
throw error; throw error;

View File

@ -1,7 +1,7 @@
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { saveBuildLog } from '../buildPacks/common'; import { saveBuildLog } from '../buildPacks/common';
import { asyncExecShell, decrypt, prisma } from '../common'; import { decrypt, executeCommand, prisma } from '../common';
export default async function ({ export default async function ({
applicationId, applicationId,
@ -43,9 +43,11 @@ export default async function ({
applicationId applicationId
}); });
} }
await asyncExecShell( await executeCommand({
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. ` command:
); `git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `,
shell: true
});
} else { } else {
const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } }); const body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
@ -81,11 +83,13 @@ export default async function ({
applicationId applicationId
}); });
} }
await asyncExecShell( await executeCommand({
`git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. ` command:
); `git clone -q -b ${branch} https://x-access-token:${token}@${url}/${repository}.git --config core.sshCommand="ssh -p ${customPort}" ${workdir}/ && cd ${workdir} && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `,
shell: true
});
} }
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
console.log({ commit })
return commit.replace('\n', ''); return commit.replace('\n', '');
} }

View File

@ -1,5 +1,5 @@
import { saveBuildLog } from "../buildPacks/common"; import { saveBuildLog } from "../buildPacks/common";
import { asyncExecShell } from "../common"; import { executeCommand } from "../common";
export default async function ({ export default async function ({
applicationId, applicationId,
@ -28,8 +28,8 @@ export default async function ({
}): Promise<string> { }): Promise<string> {
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, ''); const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
if (!forPublic) { if (!forPublic) {
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`); await executeCommand({ command: `echo '${privateSshKey}' > ${repodir}/id.rsa`, shell: true });
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`); await executeCommand({ command: `chmod 600 ${repodir}/id.rsa` });
} }
await saveBuildLog({ await saveBuildLog({
@ -45,15 +45,19 @@ export default async function ({
}); });
} }
if (forPublic) { if (forPublic) {
await asyncExecShell( await executeCommand({
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. ` command:
`git clone -q -b ${branch} https://${url}/${repository}.git ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
}
); );
} else { } else {
await asyncExecShell( await executeCommand({
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. ` command:
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
}
); );
} }
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`); const { stdout: commit } = await executeCommand({ command: `cd ${workdir}/ && git rev-parse HEAD`, shell: true });
return commit.replace('\n', ''); return commit.replace('\n', '');
} }

View File

@ -2,7 +2,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises'; import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import path from 'path'; import path from 'path';
import { asyncSleep, ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, errorHandler, executeDockerCmd, getServiceFromDB, isARM, makeLabelForServices, persistentVolumes, prisma, stopTcpHttpProxy } from '../common'; import { asyncSleep, ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, errorHandler, executeCommand, getServiceFromDB, isARM, makeLabelForServices, persistentVolumes, prisma, stopTcpHttpProxy } from '../common';
import { parseAndFindServiceTemplates } from '../../routes/api/v1/services/handlers'; import { parseAndFindServiceTemplates } from '../../routes/api/v1/services/handlers';
import { ServiceStartStop } from '../../routes/api/v1/services/types'; import { ServiceStartStop } from '../../routes/api/v1/services/types';
@ -15,14 +15,19 @@ export async function stopService(request: FastifyRequest<ServiceStartStop>) {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { destinationDockerId } = await getServiceFromDB({ id, teamId }); const { destinationDockerId } = await getServiceFromDB({ id, teamId });
if (destinationDockerId) { if (destinationDockerId) {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}`
})
await executeDockerCmd({
dockerId: destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.project=${id}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
}) })
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
}
}
}
return {} return {}
} }
throw { status: 500, message: 'Could not stop containers.' } throw { status: 500, message: 'Could not stop containers.' }
@ -182,19 +187,36 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
// Workaround: Stop old minio proxies // Workaround: Stop old minio proxies
if (service.type === 'minio') { if (service.type === 'minio') {
try { try {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: command:
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}|xargs -r -n 1 docker container stop -t 0` `docker container ls -a --filter 'name=${id}-' --format {{.ID}}`
}); });
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
}
}
}
} catch (error) { } } catch (error) { }
try { try {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: command:
`docker container ls -a --filter 'name=${id}-' --format {{.ID}}|xargs -r -n 1 docker container rm -f` `docker container ls -a --filter 'name=${id}-' --format {{.ID}}`
}); });
if (containers) {
const containerArray = containers.split('\n');
if (containerArray.length > 0) {
for (const container of containerArray) {
await executeCommand({ dockerId: destinationDockerId, command: `docker stop -t 0 ${container}` })
await executeCommand({ dockerId: destinationDockerId, command: `docker rm --force ${container}` })
}
}
}
} catch (error) { } } catch (error) { }
} }
return {} return {}
@ -205,16 +227,16 @@ export async function startService(request: FastifyRequest<ServiceStartStop>, fa
async function startServiceContainers(fastify, id, teamId, dockerId, composeFileDestination) { async function startServiceContainers(fastify, id, teamId, dockerId, composeFileDestination) {
try { try {
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Pulling images...' }) fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Pulling images...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} pull` }) await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} pull` })
} catch (error) { } } catch (error) { }
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Building images...' }) fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Building images...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` }) await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} build --no-cache` })
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Creating containers...' }) fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Creating containers...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} create` }) await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} create` })
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Starting containers...' }) fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 'Starting containers...' })
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} start` }) await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} start` })
await asyncSleep(1000); await asyncSleep(1000);
await executeDockerCmd({ dockerId, command: `docker compose -f ${composeFileDestination} up -d` }) await executeCommand({ dockerId, command: `docker compose -f ${composeFileDestination} up -d` })
fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 0 }) fastify.io.to(teamId).emit(`start-service`, { serviceId: id, state: 0 })
} }
export async function migrateAppwriteDB(request: FastifyRequest<OnlyId>, reply: FastifyReply) { export async function migrateAppwriteDB(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
@ -226,7 +248,7 @@ export async function migrateAppwriteDB(request: FastifyRequest<OnlyId>, reply:
destinationDocker, destinationDocker,
} = await getServiceFromDB({ id, teamId }); } = await getServiceFromDB({ id, teamId });
if (destinationDockerId) { if (destinationDockerId) {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker exec ${id} migrate` command: `docker exec ${id} migrate`
}) })

View File

@ -8,7 +8,7 @@ import csv from 'csvtojson';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { saveDockerRegistryCredentials, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; import { saveDockerRegistryCredentials, setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { checkDomainsIsValidInDNS, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { checkDomainsIsValidInDNS, checkExposedPort, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeCommand, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, listSettings, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker'; import { checkContainer, formatLabelsOnDocker, removeContainer } from '../../../../lib/docker';
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
@ -78,7 +78,7 @@ export async function cleanupUnconfiguredApplications(request: FastifyRequest<an
for (const application of applications) { for (const application of applications) {
if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn)) { if (!application.buildPack || !application.destinationDockerId || !application.branch || (!application.settings?.isBot && !application?.fqdn)) {
if (application?.destinationDockerId && application.destinationDocker?.network) { if (application?.destinationDockerId && application.destinationDocker?.network) {
const { stdout: containers } = await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id, dockerId: application.destinationDocker.id,
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'` command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${application.id} --format '{{json .}}'`
}) })
@ -113,7 +113,7 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
const application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) { if (application?.destinationDockerId) {
if (application.buildPack === 'compose') { if (application.buildPack === 'compose') {
const { stdout: containers } = await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id, dockerId: application.destinationDocker.id,
command: command:
`docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'` `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
@ -485,7 +485,7 @@ export async function restartApplication(request: FastifyRequest<RestartApplicat
if (imageId) { if (imageId) {
image = imageId image = imageId
} else { } else {
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` }) const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}' --format '{{json .}}'` })
const containersArray = container.trim().split('\n'); const containersArray = container.trim().split('\n');
for (const container of containersArray) { for (const container of containersArray) {
const containerObj = formatLabelsOnDocker(container); const containerObj = formatLabelsOnDocker(container);
@ -504,7 +504,7 @@ export async function restartApplication(request: FastifyRequest<RestartApplicat
let imageFoundLocally = false; let imageFoundLocally = false;
try { try {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker image inspect ${image}` command: `docker image inspect ${image}`
}) })
@ -514,7 +514,7 @@ export async function restartApplication(request: FastifyRequest<RestartApplicat
} }
let imageFoundRemotely = false; let imageFoundRemotely = false;
try { try {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker ${location ? `--config ${location}` : ''} pull ${image}` command: `docker ${location ? `--config ${location}` : ''} pull ${image}`
}) })
@ -570,13 +570,13 @@ export async function restartApplication(request: FastifyRequest<RestartApplicat
}; };
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
try { try {
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` }) await executeCommand({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}` }) await executeCommand({ dockerId, command: `docker rm ${id}` })
} catch (error) { } catch (error) {
// //
} }
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` }) await executeCommand({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
return reply.code(201).send(); return reply.code(201).send();
} }
throw { status: 500, message: 'Application cannot be restarted.' } throw { status: 500, message: 'Application cannot be restarted.' }
@ -592,7 +592,7 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
if (application?.destinationDockerId) { if (application?.destinationDockerId) {
const { id: dockerId } = application.destinationDocker; const { id: dockerId } = application.destinationDocker;
if (application.buildPack === 'compose') { if (application.buildPack === 'compose') {
const { stdout: containers } = await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id, dockerId: application.destinationDocker.id,
command: command:
`docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'` `docker ps -a --filter "label=coolify.applicationId=${id}" --format '{{json .}}'`
@ -627,7 +627,7 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
include: { destinationDocker: true } include: { destinationDocker: true }
}); });
if (!force && application?.destinationDockerId && application.destinationDocker?.network) { if (!force && application?.destinationDockerId && application.destinationDocker?.network) {
const { stdout: containers } = await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: application.destinationDocker.id, dockerId: application.destinationDocker.id,
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'` command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
}) })
@ -720,8 +720,8 @@ export async function getDockerImages(request) {
const application: any = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
let imagesAvailables = []; let imagesAvailables = [];
try { try {
const { stdout } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}' | grep -i ${id} | grep -v cache` }); const { stdout } = await executeCommand({ dockerId: application.destinationDocker.id, command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}' | grep -i ${id} | grep -v cache`, shell: true });
const { stdout: runningImage } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}` }); const { stdout: runningImage } = await executeCommand({ dockerId: application.destinationDocker.id, command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}` });
const images = stdout.trim().split('\n'); const images = stdout.trim().split('\n');
for (const image of images) { for (const image of images) {
@ -1184,7 +1184,7 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
const { workdir } = await createDirectories({ repository, buildId }); const { workdir } = await createDirectories({ repository, buildId });
const labels = [] const labels = []
let image = null let image = null
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}-${pullmergeRequestId}' --format '{{json .}}'` }) const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=com.docker.compose.service=${id}-${pullmergeRequestId}' --format '{{json .}}'` })
const containersArray = container.trim().split('\n'); const containersArray = container.trim().split('\n');
for (const container of containersArray) { for (const container of containersArray) {
const containerObj = formatLabelsOnDocker(container); const containerObj = formatLabelsOnDocker(container);
@ -1197,7 +1197,7 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
} }
let imageFound = false; let imageFound = false;
try { try {
await executeDockerCmd({ await executeCommand({
dockerId, dockerId,
command: `docker image inspect ${image}` command: `docker image inspect ${image}`
}) })
@ -1251,9 +1251,9 @@ export async function restartPreview(request: FastifyRequest<RestartPreviewAppli
volumes: Object.assign({}, ...composeVolumes) volumes: Object.assign({}, ...composeVolumes)
}; };
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile)); await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}-${pullmergeRequestId}` }) await executeCommand({ dockerId, command: `docker stop -t 0 ${id}-${pullmergeRequestId}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}-${pullmergeRequestId}` }) await executeCommand({ dockerId, command: `docker rm ${id}-${pullmergeRequestId}` })
await executeDockerCmd({ dockerId, command: `docker compose --project-directory ${workdir} up -d` }) await executeCommand({ dockerId, command: `docker compose --project-directory ${workdir} up -d` })
return reply.code(201).send(); return reply.code(201).send();
} }
throw { status: 500, message: 'Application cannot be restarted.' } throw { status: 500, message: 'Application cannot be restarted.' }
@ -1294,7 +1294,7 @@ export async function loadPreviews(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const application = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }); const application = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } });
const { stdout } = await executeDockerCmd({ dockerId: application.destinationDocker.id, command: `docker container ls --filter 'name=${id}-' --format "{{json .}}"` }) const { stdout } = await executeCommand({ dockerId: application.destinationDocker.id, command: `docker container ls --filter 'name=${id}-' --format "{{json .}}"` })
if (stdout === '') { if (stdout === '') {
throw { status: 500, message: 'No previews found.' } throw { status: 500, message: 'No previews found.' }
} }
@ -1369,7 +1369,7 @@ export async function getApplicationLogs(request: FastifyRequest<GetApplicationL
if (destinationDockerId) { if (destinationDockerId) {
try { try {
const { default: ansi } = await import('strip-ansi') const { default: ansi } = await import('strip-ansi')
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` }) 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 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 stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout) const logs = stripLogsStderr.concat(stripLogsStdout)
@ -1560,19 +1560,19 @@ export async function createdBranchDatabase(database: any, baseDatabaseBranch: s
if (destinationDockerId) { if (destinationDockerId) {
if (type === 'postgresql') { if (type === 'postgresql') {
const decryptedRootUserPassword = decrypt(rootUserPassword); const decryptedRootUserPassword = decrypt(rootUserPassword);
await executeDockerCmd({ await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump` command: `docker exec ${id} pg_dump -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/${baseDatabaseBranch}" --encoding=UTF8 --schema-only -f /tmp/${baseDatabaseBranch}.dump`
}) })
await executeDockerCmd({ await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"` command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "CREATE DATABASE branch_${pullmergeRequestId}"`
}) })
await executeDockerCmd({ await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump` command: `docker exec ${id} psql -d "postgresql://postgres:${decryptedRootUserPassword}@${id}:5432/branch_${pullmergeRequestId}" -f /tmp/${baseDatabaseBranch}.dump`
}) })
await executeDockerCmd({ await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"` command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "ALTER DATABASE branch_${pullmergeRequestId} OWNER TO ${dbUser}"`
}) })
@ -1591,12 +1591,12 @@ export async function removeBranchDatabase(database: any, pullmergeRequestId: st
if (type === 'postgresql') { if (type === 'postgresql') {
const decryptedRootUserPassword = decrypt(rootUserPassword); const decryptedRootUserPassword = decrypt(rootUserPassword);
// Terminate all connections to the database // Terminate all connections to the database
await executeDockerCmd({ await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"` command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'branch_${pullmergeRequestId}' AND pid <> pg_backend_pid();"`
}) })
await executeDockerCmd({ await executeCommand({
dockerId: destinationDockerId, dockerId: destinationDockerId,
command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"` command: `docker exec ${id} psql postgresql://postgres:${decryptedRootUserPassword}@${id}:5432 -c "DROP DATABASE branch_${pullmergeRequestId}"`
}) })

View File

@ -3,7 +3,7 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import { ComposeFile, createDirectories, decrypt, 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 { day } from '../../../../lib/dayjs';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
@ -89,7 +89,7 @@ export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
const { destinationDockerId, destinationDocker } = database; const { destinationDockerId, destinationDocker } = database;
if (destinationDockerId) { if (destinationDockerId) {
try { try {
const { stdout } = await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` }) const { stdout } = await executeCommand({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` })
if (JSON.parse(stdout).Running) { if (JSON.parse(stdout).Running) {
isRunning = true; isRunning = true;
@ -208,7 +208,7 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
if (destinationDockerId) { if (destinationDockerId) {
if (type && version) { if (type && version) {
const baseImage = getDatabaseImage(type, arch); const baseImage = getDatabaseImage(type, arch);
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` }) executeCommand({ dockerId, command: `docker pull ${baseImage}:${version}` })
} }
} }
return reply.code(201).send({}) return reply.code(201).send({})
@ -298,7 +298,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` }) await executeCommand({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort); if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
return {}; return {};
@ -347,7 +347,7 @@ export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>)
// const found = await checkContainer({ dockerId, container: id }) // const found = await checkContainer({ dockerId, container: id })
// if (found) { // if (found) {
const { default: ansi } = await import('strip-ansi') const { default: ansi } = await import('strip-ansi')
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` }) 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 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 stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout) const logs = stripLogsStderr.concat(stripLogsStdout)

View File

@ -4,7 +4,7 @@ import sshConfig from 'ssh-config'
import fs from 'fs/promises' import fs from 'fs/promises'
import os from 'os'; import os from 'os';
import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, executeSSHCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import { createRemoteEngineConfiguration, decrypt, errorHandler, executeCommand, executeSSHCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker'; import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
@ -79,9 +79,9 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
if (id === 'new') { if (id === 'new') {
if (engine) { if (engine) {
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`); const { stdout } = await await executeCommand({ command: `docker network ls --filter 'name=^${network}$' --format '{{json .}}'` });
if (stdout === '') { if (stdout === '') {
await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network create --attachable ${network}`); await await executeCommand({ command: `docker network create --attachable ${network}` });
} }
await prisma.destinationDocker.create({ await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed } data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
@ -122,13 +122,13 @@ export async function deleteDestination(request: FastifyRequest<OnlyId>) {
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } }); const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
if (isCoolifyProxyUsed) { if (isCoolifyProxyUsed) {
if (engine || remoteVerified) { if (engine || remoteVerified) {
const { stdout: found } = await executeDockerCmd({ const { stdout: found } = await executeCommand({
dockerId: id, dockerId: id,
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'` command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
}) })
if (found) { if (found) {
await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` }) await executeCommand({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` }) await executeCommand({ dockerId: id, command: `docker network rm ${network}` })
} }
} }
} }
@ -206,13 +206,13 @@ export async function verifyRemoteDockerEngineFn(id: string) {
await createRemoteEngineConfiguration(id); await createRemoteEngineConfiguration(id);
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } }) const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
const host = `ssh://${remoteIpAddress}-remote` const host = `ssh://${remoteIpAddress}-remote`
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`); const { stdout } = await executeCommand({ command: `docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`, dockerId: id });
if (!stdout) { if (!stdout) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`); await executeCommand({ command: `docker network create --attachable ${network}`, dockerId: id });
} }
const { stdout: coolifyNetwork } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`); const { stdout: coolifyNetwork } = await executeCommand({ command: `docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`, dockerId: id });
if (!coolifyNetwork) { if (!coolifyNetwork) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`); await executeCommand({ command: `docker network create --attachable coolify-infra`, dockerId: id });
} }
if (isCoolifyProxyUsed) await startTraefikProxy(id); if (isCoolifyProxyUsed) await startTraefikProxy(id);
try { try {

View File

@ -4,7 +4,6 @@ import bcrypt from "bcryptjs";
import fs from 'fs/promises'; import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { import {
asyncExecShell,
asyncSleep, asyncSleep,
cleanupDockerStorage, cleanupDockerStorage,
errorHandler, errorHandler,
@ -15,6 +14,7 @@ import {
version, version,
sentryDSN, sentryDSN,
executeDockerCmd, executeDockerCmd,
executeCommand,
} from "../../../lib/common"; } from "../../../lib/common";
import { scheduler } from "../../../lib/scheduler"; import { scheduler } from "../../../lib/scheduler";
import type { FastifyReply, FastifyRequest } from "fastify"; import type { FastifyReply, FastifyRequest } from "fastify";
@ -38,7 +38,7 @@ export async function backup(request: FastifyRequest) {
// dockerId: database.destinationDockerId, // dockerId: database.destinationDockerId,
// command: `docker pull coollabsio/backup:latest`, // command: `docker pull coollabsio/backup:latest`,
// }) // })
std = await executeDockerCmd({ std = await executeCommand({
dockerId: database.destinationDockerId, dockerId: database.destinationDockerId,
command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup` command: `docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v coolify-local-backup:/app/backups -e CONTAINERS_TO_BACKUP="${backupData}" coollabsio/backup`
}) })
@ -141,14 +141,10 @@ export async function update(request: FastifyRequest<Update>) {
try { try {
if (!isDev) { if (!isDev) {
const { isAutoUpdateEnabled } = await prisma.setting.findFirst(); const { isAutoUpdateEnabled } = await prisma.setting.findFirst();
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); await executeCommand({ command: `docker pull coollabsio/coolify:${latestVersion}` });
await asyncExecShell(`env | grep COOLIFY > .env`); await executeCommand({ command: `env | grep COOLIFY > .env` });
await asyncExecShell( await executeCommand({ command: `sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` });
`sed -i '/COOLIFY_AUTO_UPDATE=/cCOOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` await executeCommand({ command: `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"` });
);
await asyncExecShell(
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-fluentbit && docker rm coolify coolify-fluentbit && docker compose pull && docker compose up -d --force-recreate"`
);
return {}; return {};
} else { } else {
await asyncSleep(2000); await asyncSleep(2000);
@ -177,7 +173,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
const teamId = request.user.teamId; const teamId = request.user.teamId;
if (teamId === "0") { if (teamId === "0") {
if (!isDev) { if (!isDev) {
asyncExecShell(`docker restart coolify`); await executeCommand({ command: `docker restart coolify` });
return {}; return {};
} else { } else {
return {}; return {};

View File

@ -53,9 +53,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
onRequest: [fastify.authenticate] onRequest: [fastify.authenticate]
}, async (request) => await cleanupManually(request)); }, async (request) => await cleanupManually(request));
fastify.get('/internal/backup/:backupData', { // fastify.get('/internal/backup/:backupData', {
onRequest: [fastify.authenticate] // onRequest: [fastify.authenticate]
}, async (request) => await backup(request)); // }, async (request) => await backup(request));
}; };
export default root; export default root;

View File

@ -1,5 +1,5 @@
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
import { errorHandler, executeDockerCmd, prisma, createRemoteEngineConfiguration, executeSSHCmd } from '../../../../lib/common'; import { errorHandler, prisma, executeSSHCmd } from '../../../../lib/common';
import os from 'node:os'; import os from 'node:os';
import osu from 'node-os-utils'; import osu from 'node-os-utils';

View File

@ -4,7 +4,7 @@ import yaml from 'js-yaml';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import cuid from 'cuid'; import cuid from 'cuid';
import { prisma, uniqueName, asyncExecShell, getServiceFromDB, getContainerUsage, isDomainConfigured, fixType, decrypt, encrypt, ComposeFile, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, executeDockerCmd, checkDomainsIsValidInDNS, checkExposedPort, listSettings, generateToken } 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 { day } from '../../../../lib/dayjs';
import { checkContainer, } from '../../../../lib/docker'; import { checkContainer, } from '../../../../lib/docker';
import { removeService } from '../../../../lib/services/common'; import { removeService } from '../../../../lib/services/common';
@ -48,14 +48,19 @@ export async function cleanupUnconfiguredServices(request: FastifyRequest) {
for (const service of services) { for (const service of services) {
if (!service.fqdn) { if (!service.fqdn) {
if (service.destinationDockerId) { if (service.destinationDockerId) {
await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: service.destinationDockerId, dockerId: service.destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}|xargs -r -n 1 docker stop -t 0` command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}`
})
await executeDockerCmd({
dockerId: service.destinationDockerId,
command: `docker ps -a --filter 'label=com.docker.compose.project=${service.id}' --format {{.ID}}|xargs -r -n 1 docker rm --force`
}) })
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 }); await removeService({ id: service.id });
} }
@ -73,7 +78,7 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
const { destinationDockerId, settings } = service; const { destinationDockerId, settings } = service;
let payload = {} let payload = {}
if (destinationDockerId) { if (destinationDockerId) {
const { stdout: containers } = await executeDockerCmd({ const { stdout: containers } = await executeCommand({
dockerId: service.destinationDocker.id, dockerId: service.destinationDocker.id,
command: command:
`docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'` `docker ps -a --filter "label=com.docker.compose.project=${id}" --format '{{json .}}'`
@ -443,7 +448,7 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
if (destinationDockerId) { if (destinationDockerId) {
try { try {
const { default: ansi } = await import('strip-ansi') const { default: ansi } = await import('strip-ansi')
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${containerId}` }) 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 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 stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout) const logs = stripLogsStderr.concat(stripLogsStdout)
@ -749,7 +754,7 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
if (destinationDockerId) { if (destinationDockerId) {
const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL'); const databaseUrl = serviceSecret.find((secret) => secret.name === 'DATABASE_URL');
if (databaseUrl) { if (databaseUrl) {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"` command: `docker exec ${id}-postgresql psql -H ${databaseUrl.value} -c "UPDATE users SET email_verified = true;"`
}) })
@ -770,9 +775,10 @@ export async function cleanupPlausibleLogs(request: FastifyRequest<OnlyId>, repl
destinationDocker, destinationDocker,
} = await getServiceFromDB({ id, teamId }); } = await getServiceFromDB({ id, teamId });
if (destinationDockerId) { if (destinationDockerId) {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, 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.{};\"` 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() return await reply.code(201).send()
} }
@ -812,36 +818,42 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
if (user) ftpUser = user; if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword); if (savedPassword) ftpPassword = decrypt(savedPassword);
const { stdout: password } = await asyncExecShell( // TODO: rewrite these to usable without shell
`echo ${ftpPassword} | openssl passwd -1 -stdin` const { stdout: password } = await executeCommand({
command:
`echo ${ftpPassword} | openssl passwd -1 -stdin`,
shell: true
}
); );
if (destinationDockerId) { if (destinationDockerId) {
try { try {
await fs.stat(hostkeyDir); await fs.stat(hostkeyDir);
} catch (error) { } catch (error) {
await asyncExecShell(`mkdir -p ${hostkeyDir}`); await executeCommand({ command: `mkdir -p ${hostkeyDir}` });
} }
if (!ftpHostKey) { if (!ftpHostKey) {
await asyncExecShell( await executeCommand({
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519` command:
`ssh-keygen -t ed25519 -f ssh_host_ed25519_key -N "" -q -f ${hostkeyDir}/${id}.ed25519`
}
); );
const { stdout: ftpHostKey } = await asyncExecShell(`cat ${hostkeyDir}/${id}.ed25519`); const { stdout: ftpHostKey } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.ed25519` });
await prisma.wordpress.update({ await prisma.wordpress.update({
where: { serviceId: id }, where: { serviceId: id },
data: { ftpHostKey: encrypt(ftpHostKey) } data: { ftpHostKey: encrypt(ftpHostKey) }
}); });
} else { } else {
await asyncExecShell(`echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`); await executeCommand({ command: `echo "${decrypt(ftpHostKey)}" > ${hostkeyDir}/${id}.ed25519`, shell: true });
} }
if (!ftpHostKeyPrivate) { if (!ftpHostKeyPrivate) {
await asyncExecShell(`ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa`); await executeCommand({ command: `ssh-keygen -t rsa -b 4096 -N "" -f ${hostkeyDir}/${id}.rsa` });
const { stdout: ftpHostKeyPrivate } = await asyncExecShell(`cat ${hostkeyDir}/${id}.rsa`); const { stdout: ftpHostKeyPrivate } = await executeCommand({ command: `cat ${hostkeyDir}/${id}.rsa` });
await prisma.wordpress.update({ await prisma.wordpress.update({
where: { serviceId: id }, where: { serviceId: id },
data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) } data: { ftpHostKeyPrivate: encrypt(ftpHostKeyPrivate) }
}); });
} else { } else {
await asyncExecShell(`echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`); await executeCommand({ command: `echo "${decrypt(ftpHostKeyPrivate)}" > ${hostkeyDir}/${id}.rsa`, shell: true });
} }
await prisma.wordpress.update({ await prisma.wordpress.update({
@ -856,9 +868,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
try { try {
const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` }); const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
if (isRunning) { if (isRunning) {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`,
shell: true
}) })
} }
} catch (error) { } } catch (error) { }
@ -902,9 +915,9 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
`${hostkeyDir}/${id}.sh`, `${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/` `#!/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 asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`); await executeCommand({ command: `chmod +x ${hostkeyDir}/${id}.sh` });
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose)); await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
}) })
@ -921,9 +934,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
data: { ftpPublicPort: null } data: { ftpPublicPort: null }
}); });
try { try {
await executeDockerCmd({ await executeCommand({
dockerId: destinationDocker.id, dockerId: destinationDocker.id,
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp` command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`,
shell: true
}) })
} catch (error) { } catch (error) {
@ -937,8 +951,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
return errorHandler({ status, message }) return errorHandler({ status, message })
} finally { } finally {
try { try {
await asyncExecShell( await executeCommand({
`rm -fr ${hostkeyDir}/${id}-docker-compose.yml ${hostkeyDir}/${id}.ed25519 ${hostkeyDir}/${id}.ed25519.pub ${hostkeyDir}/${id}.rsa ${hostkeyDir}/${id}.rsa.pub ${hostkeyDir}/${id}.sh` 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) { } } catch (error) { }

View File

@ -2,7 +2,7 @@ import { promises as dns } from 'dns';
import { X509Certificate } from 'node:crypto'; import { X509Certificate } from 'node:crypto';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import { asyncExecShell, checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common'; import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, executeCommand, getDomain, isDev, isDNSValid, isDomainConfigured, listSettings, prisma, sentryDSN, version } from '../../../../lib/common';
import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types'; import { AddDefaultRegistry, CheckDNS, CheckDomain, DeleteDomain, OnlyIdInBody, SaveSettings, SaveSSHKey, SetDefaultRegistry } from './types';
@ -182,7 +182,7 @@ export async function deleteCertificates(request: FastifyRequest<OnlyIdInBody>,
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.body; const { id } = request.body;
await asyncExecShell(`docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`) await executeCommand({ command: `docker exec coolify-proxy sh -c 'rm -f /etc/traefik/acme/custom/${id}-key.pem /etc/traefik/acme/custom/${id}-cert.pem'`, shell: true })
await prisma.certificate.deleteMany({ where: { id, teamId } }) await prisma.certificate.deleteMany({ where: { id, teamId } })
return reply.code(201).send() return reply.code(201).send()
} catch ({ status, message }) { } catch ({ status, message }) {

View File

@ -1,5 +1,5 @@
import { FastifyRequest } from "fastify"; import { FastifyRequest } from "fastify";
import { errorHandler, getDomain, isDev, prisma, executeDockerCmd, fixType } from "../../../lib/common"; import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common";
import { getTemplates } from "../../../lib/services"; import { getTemplates } from "../../../lib/services";
import { OnlyId } from "../../../types"; import { OnlyId } from "../../../types";
@ -263,10 +263,12 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const runningContainers = {} const runningContainers = {}
applications.forEach((app) => dockerIds.add(app.destinationDocker.id)); applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
for (const dockerId of dockerIds) { for (const dockerId of dockerIds) {
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` }) const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
const containersArray = container.trim().split('\n'); if (container) {
if (containersArray.length > 0) { const containersArray = container.trim().split('\n');
runningContainers[dockerId] = containersArray if (containersArray.length > 0) {
runningContainers[dockerId] = containersArray
}
} }
} }
for (const application of applications) { for (const application of applications) {
@ -332,7 +334,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) }
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) } traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) }
if (previews) { if (previews) {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` }) const { stdout } = await executeCommand({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
const containers = stdout const containers = stdout
.trim() .trim()
.split('\n') .split('\n')
@ -359,7 +361,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const runningContainers = {} const runningContainers = {}
services.forEach((app) => dockerIds.add(app.destinationDocker.id)); services.forEach((app) => dockerIds.add(app.destinationDocker.id));
for (const dockerId of dockerIds) { for (const dockerId of dockerIds) {
const { stdout: container } = await executeDockerCmd({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` }) const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` })
const containersArray = container.trim().split('\n'); const containersArray = container.trim().split('\n');
if (containersArray.length > 0) { if (containersArray.length > 0) {
runningContainers[dockerId] = containersArray runningContainers[dockerId] = containersArray

View File

@ -56,6 +56,7 @@ importers:
is-port-reachable: 4.0.0 is-port-reachable: 4.0.0
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
minimist: ^1.2.7
node-forge: 1.3.1 node-forge: 1.3.1
node-os-utils: 1.3.7 node-os-utils: 1.3.7
nodemon: 2.0.20 nodemon: 2.0.20
@ -66,6 +67,7 @@ importers:
public-ip: 6.0.1 public-ip: 6.0.1
pump: 3.0.0 pump: 3.0.0
rimraf: 3.0.2 rimraf: 3.0.2
shell-quote: ^1.7.4
socket.io: 4.5.3 socket.io: 4.5.3
ssh-config: 4.1.6 ssh-config: 4.1.6
strip-ansi: 7.0.1 strip-ansi: 7.0.1
@ -108,6 +110,7 @@ importers:
is-port-reachable: 4.0.0 is-port-reachable: 4.0.0
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
minimist: 1.2.7
node-forge: 1.3.1 node-forge: 1.3.1
node-os-utils: 1.3.7 node-os-utils: 1.3.7
p-all: 4.0.0 p-all: 4.0.0
@ -115,6 +118,7 @@ importers:
prisma: 4.6.1 prisma: 4.6.1
public-ip: 6.0.1 public-ip: 6.0.1
pump: 3.0.0 pump: 3.0.0
shell-quote: 1.7.4
socket.io: 4.5.3 socket.io: 4.5.3
ssh-config: 4.1.6 ssh-config: 4.1.6
strip-ansi: 7.0.1 strip-ansi: 7.0.1
@ -5599,6 +5603,9 @@ packages:
/minimist/1.2.6: /minimist/1.2.6:
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
/minimist/1.2.7:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
/mkdirp-classic/0.5.3: /mkdirp-classic/0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: false dev: false
@ -6702,6 +6709,10 @@ packages:
resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
dev: true dev: true
/shell-quote/1.7.4:
resolution: {integrity: sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw==}
dev: false
/side-channel/1.0.4: /side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies: dependencies:
@ -7310,7 +7321,7 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dependencies: dependencies:
json5: 2.2.1 json5: 2.2.1
minimist: 1.2.6 minimist: 1.2.7
strip-bom: 3.0.0 strip-bom: 3.0.0
/tslib/1.14.1: /tslib/1.14.1: