Merge branch 'next' into edge-db

This commit is contained in:
Ikechukwu Eze
2022-07-31 19:54:30 +02:00
committed by GitHub
91 changed files with 3998 additions and 2151 deletions

View File

@@ -16,11 +16,11 @@
"dependencies": {
"@breejs/ts-worker": "2.0.0",
"@fastify/autoload": "5.1.0",
"@fastify/cookie": "7.1.0",
"@fastify/cookie": "7.3.1",
"@fastify/cors": "8.0.0",
"@fastify/env": "4.0.0",
"@fastify/jwt": "6.3.1",
"@fastify/static": "6.4.0",
"@fastify/static": "6.4.1",
"@iarna/toml": "2.2.5",
"@prisma/client": "3.15.2",
"axios": "0.27.2",
@@ -29,30 +29,33 @@
"cabin": "9.1.2",
"compare-versions": "4.1.3",
"cuid": "2.1.8",
"dayjs": "1.11.3",
"dayjs": "1.11.4",
"dockerode": "3.3.2",
"dotenv-extended": "2.9.0",
"fastify": "4.2.1",
"fastify": "4.3.0",
"fastify-plugin": "4.0.0",
"generate-password": "1.7.0",
"get-port": "6.1.2",
"got": "12.1.0",
"is-ip": "4.0.0",
"got": "12.2.0",
"is-ip": "5.0.0",
"is-port-reachable": "4.0.0",
"js-yaml": "4.1.0",
"jsonwebtoken": "8.5.1",
"node-forge": "1.3.1",
"node-os-utils": "1.3.7",
"p-queue": "7.2.0",
"public-ip": "6.0.1",
"ssh-config": "4.1.6",
"strip-ansi": "7.0.1",
"unique-names-generator": "4.7.1"
},
"devDependencies": {
"@types/node": "18.0.4",
"@types/node": "18.6.1",
"@types/node-os-utils": "1.3.0",
"@typescript-eslint/eslint-plugin": "5.30.6",
"@typescript-eslint/parser": "5.30.6",
"esbuild": "0.14.49",
"eslint": "8.19.0",
"@typescript-eslint/eslint-plugin": "5.31.0",
"@typescript-eslint/parser": "5.31.0",
"esbuild": "0.14.50",
"eslint": "8.20.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.2.1",
"nodemon": "2.0.19",

View File

@@ -0,0 +1,20 @@
-- CreateTable
CREATE TABLE "Moodle" (
"id" TEXT NOT NULL PRIMARY KEY,
"serviceId" TEXT NOT NULL,
"defaultUsername" TEXT NOT NULL,
"defaultPassword" TEXT NOT NULL,
"defaultEmail" TEXT NOT NULL,
"mariadbUser" TEXT NOT NULL,
"mariadbPassword" TEXT NOT NULL,
"mariadbRootUser" TEXT NOT NULL,
"mariadbRootUserPassword" TEXT NOT NULL,
"mariadbDatabase" TEXT NOT NULL,
"mariadbPublicPort" INTEGER,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Moodle_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Moodle_serviceId_key" ON "Moodle"("serviceId");

View File

@@ -0,0 +1,21 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_DestinationDocker" (
"id" TEXT NOT NULL PRIMARY KEY,
"network" TEXT NOT NULL,
"name" TEXT NOT NULL,
"engine" TEXT,
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
"remoteIpAddress" TEXT,
"remoteUser" TEXT,
"remotePort" INTEGER,
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "updatedAt" FROM "DestinationDocker";
DROP TABLE "DestinationDocker";
ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,33 @@
-- CreateTable
CREATE TABLE "SshKey" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"privateKey" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_DestinationDocker" (
"id" TEXT NOT NULL PRIMARY KEY,
"network" TEXT NOT NULL,
"name" TEXT NOT NULL,
"engine" TEXT,
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
"remoteIpAddress" TEXT,
"remoteUser" TEXT,
"remotePort" INTEGER,
"remoteVerified" BOOLEAN NOT NULL DEFAULT false,
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"sshKeyId" TEXT,
CONSTRAINT "DestinationDocker_sshKeyId_fkey" FOREIGN KEY ("sshKeyId") REFERENCES "SshKey" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt" FROM "DestinationDocker";
DROP TABLE "DestinationDocker";
ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "ipv4" TEXT;
ALTER TABLE "Setting" ADD COLUMN "ipv6" TEXT;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Setting" ADD COLUMN "arch" TEXT;

View File

@@ -0,0 +1,16 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_SshKey" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"privateKey" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
"teamId" TEXT,
CONSTRAINT "SshKey_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_SshKey" ("createdAt", "id", "name", "privateKey", "updatedAt") SELECT "createdAt", "id", "name", "privateKey", "updatedAt" FROM "SshKey";
DROP TABLE "SshKey";
ALTER TABLE "new_SshKey" RENAME TO "SshKey";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@@ -23,6 +23,9 @@ model Setting {
isTraefikUsed Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ipv4 String?
ipv6 String?
arch String?
}
model User {
@@ -30,39 +33,40 @@ model User {
email String @unique
type String
password String?
teams Team[]
permission Permission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
permission Permission[]
teams Team[]
}
model Permission {
id String @id @default(cuid())
user User @relation(fields: [userId], references: [id])
userId String
team Team @relation(fields: [teamId], references: [id])
teamId String
permission String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
team Team @relation(fields: [teamId], references: [id])
user User @relation(fields: [userId], references: [id])
}
model Team {
id String @id @default(cuid())
users User[]
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
databaseId String?
serviceId String?
permissions Permission[]
sshKey SshKey[]
applications Application[]
database Database[]
destinationDocker DestinationDocker[]
gitSources GitSource[]
gitHubApps GithubApp[]
gitLabApps GitlabApp[]
destinationDocker DestinationDocker[]
permissions Permission[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
database Database[] @relation(references: [id])
databaseId String?
service Service[] @relation(references: [id])
serviceId String?
service Service[]
users User[]
}
model TeamInvitation {
@@ -101,21 +105,20 @@ model Application {
denoOptions String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
settings ApplicationSettings?
teams Team[]
destinationDockerId String?
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
gitSourceId String?
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
secrets Secret[]
persistentStorage ApplicationPersistentStorage[]
baseImage String?
baseBuildImage String?
gitSource GitSource? @relation(fields: [gitSourceId], references: [id])
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
persistentStorage ApplicationPersistentStorage[]
settings ApplicationSettings?
secrets Secret[]
teams Team[]
}
model ApplicationSettings {
id String @id @default(cuid())
application Application @relation(fields: [applicationId], references: [id])
applicationId String @unique
dualCerts Boolean @default(false)
debug Boolean @default(false)
@@ -123,26 +126,27 @@ model ApplicationSettings {
autodeploy Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
}
model ApplicationPersistentStorage {
id String @id @default(cuid())
application Application @relation(fields: [applicationId], references: [id])
applicationId String
path String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
@@unique([applicationId, path])
}
model ServicePersistentStorage {
id String @id @default(cuid())
service Service @relation(fields: [serviceId], references: [id])
serviceId String
path String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
@@unique([serviceId, path])
}
@@ -155,8 +159,8 @@ model Secret {
isBuildSecret Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id])
applicationId String
application Application @relation(fields: [applicationId], references: [id])
@@unique([name, applicationId, isPRMRSecret])
}
@@ -167,8 +171,8 @@ model ServiceSecret {
value String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
serviceId String
service Service @relation(fields: [serviceId], references: [id])
@@unique([name, serviceId])
}
@@ -200,21 +204,37 @@ model DestinationDocker {
id String @id @default(cuid())
network String @unique
name String
engine String
engine String?
remoteEngine Boolean @default(false)
remoteIpAddress String?
remoteUser String?
remotePort Int?
remoteVerified Boolean @default(false)
isCoolifyProxyUsed Boolean? @default(false)
teams Team[]
application Application[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sshKeyId String?
sshKey SshKey? @relation(fields: [sshKeyId], references: [id])
application Application[]
database Database[]
service Service[]
teams Team[]
}
model SshKey {
id String @id @default(cuid())
name String
privateKey String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teamId String?
team Team? @relation(fields: [teamId], references: [id])
destinationDocker DestinationDocker[]
}
model GitSource {
id String @id @default(cuid())
name String
teams Team[]
type String?
apiUrl String?
htmlUrl String?
@@ -223,16 +243,16 @@ model GitSource {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
githubAppId String? @unique
githubApp GithubApp? @relation(fields: [githubAppId], references: [id])
application Application[]
gitlabAppId String? @unique
gitlabApp GitlabApp? @relation(fields: [gitlabAppId], references: [id])
githubApp GithubApp? @relation(fields: [githubAppId], references: [id])
application Application[]
teams Team[]
}
model GithubApp {
id String @id @default(cuid())
name String? @unique
teams Team[]
appId Int?
installationId Int?
clientId String?
@@ -242,13 +262,13 @@ model GithubApp {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
gitSource GitSource?
teams Team[]
}
model GitlabApp {
id String @id @default(cuid())
oauthId Int @unique
groupName String? @unique
teams Team[]
deployKeyId Int?
privateSshKey String?
publicSshKey String?
@@ -258,6 +278,7 @@ model GitlabApp {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
gitSource GitSource?
teams Team[]
}
model Database {
@@ -271,22 +292,22 @@ model Database {
dbUserPassword String?
rootUser String?
rootUserPassword String?
settings DatabaseSettings?
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
destinationDockerId String?
teams Team[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
settings DatabaseSettings?
teams Team[]
}
model DatabaseSettings {
id String @id @default(cuid())
database Database @relation(fields: [databaseId], references: [id])
databaseId String @unique
isPublic Boolean @default(false)
appendOnly Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
database Database @relation(fields: [databaseId], references: [id])
}
model Service {
@@ -297,23 +318,23 @@ model Service {
dualCerts Boolean @default(false)
type String?
version String?
teams Team[]
destinationDockerId String?
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
plausibleAnalytics PlausibleAnalytics?
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
fider Fider?
ghost Ghost?
hasura Hasura?
meiliSearch MeiliSearch?
minio Minio?
moodle Moodle?
plausibleAnalytics PlausibleAnalytics?
persistentStorage ServicePersistentStorage[]
serviceSecret ServiceSecret[]
umami Umami?
vscodeserver Vscodeserver?
wordpress Wordpress?
ghost Ghost?
serviceSecret ServiceSecret[]
meiliSearch MeiliSearch?
persistentStorage ServicePersistentStorage[]
umami Umami?
hasura Hasura?
fider Fider?
moodle Moodle?
teams Team[]
}
model PlausibleAnalytics {
@@ -328,9 +349,9 @@ model PlausibleAnalytics {
secretKeyBase String?
scriptName String @default("plausible.js")
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Minio {
@@ -340,18 +361,18 @@ model Minio {
publicPort Int?
apiFqdn String?
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Vscodeserver {
id String @id @default(cuid())
password String
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Wordpress {
@@ -374,9 +395,9 @@ model Wordpress {
ftpHostKey String?
ftpHostKeyPrivate String?
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Ghost {
@@ -390,18 +411,18 @@ model Ghost {
mariadbDatabase String?
mariadbPublicPort Int?
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model MeiliSearch {
id String @id @default(cuid())
masterKey String
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Umami {
@@ -413,9 +434,9 @@ model Umami {
postgresqlPublicPort Int?
umamiAdminPassword String
hashSalt String
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Hasura {
@@ -426,9 +447,9 @@ model Hasura {
postgresqlDatabase String
postgresqlPublicPort Int?
graphQLAdminPassword String
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Fider {
@@ -448,9 +469,9 @@ model Fider {
emailSmtpUser String?
emailSmtpPassword String?
emailSmtpEnableStartTls Boolean @default(false)
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}
model Moodle {
@@ -465,7 +486,7 @@ model Moodle {
mariadbRootUserPassword String
mariadbDatabase String
mariadbPublicPort Int?
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
service Service @relation(fields: [serviceId], references: [id])
}

View File

@@ -24,7 +24,8 @@ async function main() {
data: {
isRegistrationEnabled: true,
proxyPassword: encrypt(generatePassword()),
proxyUser: cuid()
proxyUser: cuid(),
arch: process.arch
}
});
} else {

View File

@@ -5,7 +5,7 @@ import env from '@fastify/env';
import cookie from '@fastify/cookie';
import path, { join } from 'path';
import autoLoad from '@fastify/autoload';
import { asyncExecShell, isDev, prisma } from './lib/common';
import { asyncExecShell, isDev, listSettings, prisma } from './lib/common';
import { scheduler } from './lib/scheduler';
declare module 'fastify' {
@@ -101,10 +101,10 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
process.exit(1);
}
console.log(`Coolify's API is listening on ${host}:${port}`);
await initServer()
await initServer();
await scheduler.start('deployApplication');
await scheduler.start('cleanupStorage');
await scheduler.start('checkProxies')
await scheduler.start('checkProxies');
// Check if no build is running
@@ -130,12 +130,37 @@ fastify.listen({ port, host }, async (err: any, address: any) => {
if (!scheduler.workers.has('deployApplication')) await scheduler.start('deployApplication');
}
});
await getArch();
await getIPAddress();
});
async function getIPAddress() {
const { publicIpv4, publicIpv6 } = await import('public-ip')
try {
const settings = await listSettings();
if (!settings.ipv4) {
const ipv4 = await publicIpv4({ timeout: 2000 })
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
}
if (!settings.ipv6) {
const ipv6 = await publicIpv6({ timeout: 2000 })
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
}
} catch (error) { }
}
async function initServer() {
try {
await asyncExecShell(`docker network create --attachable coolify`);
} catch (error) { }
}
async function getArch() {
try {
const settings = await prisma.setting.findFirst({})
if (settings && !settings.arch) {
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
}
} catch (error) { }
}

View File

@@ -1,24 +1,24 @@
import { parentPort } from 'node:worker_threads';
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, asyncExecShell } from '../lib/common';
import { checkContainer, getEngine } from '../lib/docker';
import { prisma, startTraefikTCPProxy, generateDatabaseConfiguration, startTraefikProxy, executeDockerCmd } from '../lib/common';
import { checkContainer } from '../lib/docker';
(async () => {
if (parentPort) {
// Coolify Proxy
// Coolify Proxy local
const engine = '/var/run/docker.sock';
const localDocker = await prisma.destinationDocker.findFirst({
where: { engine, network: 'coolify' }
});
if (localDocker && localDocker.isCoolifyProxyUsed) {
// Remove HAProxy
const found = await checkContainer(engine, 'coolify-haproxy');
const host = getEngine(engine);
const found = await checkContainer({ dockerId: localDocker.id, container: 'coolify-haproxy' });
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`
);
await executeDockerCmd({
dockerId: localDocker.id,
command: `docker stop -t 0 coolify-haproxy && docker rm coolify-haproxy`
})
}
await startTraefikProxy(engine);
await startTraefikProxy(localDocker.id);
}
@@ -32,12 +32,14 @@ import { checkContainer, getEngine } from '../lib/docker';
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
const { privatePort } = generateDatabaseConfiguration(database);
// Remove HAProxy
const found = await checkContainer(engine, `haproxy-for-${publicPort}`);
const host = getEngine(engine);
const found = await checkContainer({
dockerId: localDocker.id, container: `haproxy-for-${publicPort}`
});
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}`
);
await executeDockerCmd({
dockerId: localDocker.id,
command: `docker stop -t 0 haproxy-for-${publicPort} && docker rm haproxy-for-${publicPort}`
})
}
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
@@ -52,12 +54,12 @@ import { checkContainer, getEngine } from '../lib/docker';
const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
// Remove HAProxy
const found = await checkContainer(engine, `haproxy-for-${ftpPublicPort}`);
const host = getEngine(engine);
const found = await checkContainer({ dockerId: localDocker.id, container: `haproxy-for-${ftpPublicPort}` });
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker stop -t 0 haproxy-for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort} `
);
await executeDockerCmd({
dockerId: localDocker.id,
command: `docker stop -t 0 haproxy -for-${ftpPublicPort} && docker rm haproxy-for-${ftpPublicPort}`
})
}
await startTraefikTCPProxy(destinationDocker, id, ftpPublicPort, 22, 'wordpressftp');
}
@@ -73,12 +75,12 @@ import { checkContainer, getEngine } from '../lib/docker';
const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
// Remove HAProxy
const found = await checkContainer(engine, `${id}-${publicPort}`);
const host = getEngine(engine);
const found = await checkContainer({ dockerId: localDocker.id, container: `${id}-${publicPort}` });
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort}`
);
await executeDockerCmd({
dockerId: localDocker.id,
command: `docker stop -t 0 ${id}-${publicPort} && docker rm ${id}-${publicPort} `
})
}
await startTraefikTCPProxy(destinationDocker, id, publicPort, 9000);
}

View File

@@ -1,20 +1,20 @@
import { parentPort } from 'node:worker_threads';
import { asyncExecShell, cleanupDockerStorage, isDev, prisma, version } from '../lib/common';
import { getEngine } from '../lib/docker';
import { asyncExecShell, cleanupDockerStorage, executeDockerCmd, isDev, prisma, version } from '../lib/common';
(async () => {
if (parentPort) {
const destinationDockers = await prisma.destinationDocker.findMany();
const engines = [...new Set(destinationDockers.map(({ engine }) => engine))];
for (const engine of engines) {
let enginesDone = new Set()
for (const destination of destinationDockers) {
if (enginesDone.has(destination.engine) || enginesDone.has(destination.remoteIpAddress)) return
if (destination.engine) enginesDone.add(destination.engine)
if (destination.remoteIpAddress) enginesDone.add(destination.remoteIpAddress)
let lowDiskSpace = false;
const host = getEngine(engine);
try {
let stdout = null
if (!isDev) {
const output = await asyncExecShell(
`DOCKER_HOST=${host} docker exec coolify sh -c 'df -kPT /'`
);
const output = await executeDockerCmd({ dockerId: destination.id, command: `CONTAINER=$(docker ps -lq | head -1) && docker exec $CONTAINER sh -c 'df -kPT /'` })
stdout = output.stdout;
} else {
const output = await asyncExecShell(
@@ -53,7 +53,7 @@ import { getEngine } from '../lib/docker';
} catch (error) {
console.log(error);
}
await cleanupDockerStorage(host, lowDiskSpace, false)
await cleanupDockerStorage(destination.id, lowDiskSpace, false)
}
await prisma.$disconnect();
} else process.exit(0);

View File

@@ -4,8 +4,7 @@ import fs from 'fs/promises';
import yaml from 'js-yaml';
import { copyBaseConfigurationFiles, makeLabelForStandaloneApplication, saveBuildLog, setDefaultConfiguration } from '../lib/buildPacks/common';
import { asyncExecShell, createDirectories, decrypt, getDomain, prisma } from '../lib/common';
import { dockerInstance, getEngine } from '../lib/docker';
import { createDirectories, decrypt, executeDockerCmd, getDomain, prisma } from '../lib/common';
import * as importers from '../lib/importers';
import * as buildpacks from '../lib/buildPacks';
@@ -104,9 +103,6 @@ import * as buildpacks from '../lib/buildPacks';
destinationType = 'docker';
}
if (destinationType === 'docker') {
const docker = dockerInstance({ destinationDocker });
const host = getEngine(destinationDocker.engine);
await prisma.build.update({ where: { id: buildId }, data: { status: 'running' } });
const { workdir, repodir } = await createDirectories({ repository, buildId });
const configuration = await setDefaultConfiguration(message);
@@ -185,18 +181,23 @@ import * as buildpacks from '../lib/buildPacks';
} else {
deployNeeded = true;
}
const image = await docker.engine.getImage(`${applicationId}:${tag}`);
let imageFound = false;
try {
await image.inspect();
await executeDockerCmd({
dockerId: destinationDocker.id,
command: `docker image inspect ${applicationId}:${tag}`
})
imageFound = true;
} catch (error) {
//
}
if (!imageFound || deployNeeded) {
// if (!imageFound || deployNeeded) {
if (true) {
await copyBaseConfigurationFiles(buildPack, workdir, buildId, applicationId, baseImage);
if (buildpacks[buildPack])
await buildpacks[buildPack]({
dockerId: destinationDocker.id,
buildId,
applicationId,
domain,
@@ -212,7 +213,6 @@ import * as buildpacks from '../lib/buildPacks';
commit,
tag,
workdir,
docker,
port: exposePort ? `${exposePort}:${port}` : port,
installCommand,
buildCommand,
@@ -238,8 +238,8 @@ import * as buildpacks from '../lib/buildPacks';
await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${imageId}`);
await asyncExecShell(`DOCKER_HOST=${host} docker rm ${imageId}`);
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker stop -t 0 ${imageId}` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker rm ${imageId}` })
} catch (error) {
//
}
@@ -299,7 +299,7 @@ import * as buildpacks from '../lib/buildPacks';
container_name: imageId,
volumes,
env_file: envFound ? [`${workdir}/.env`] : [],
networks: [docker.network],
networks: [destinationDocker.network],
labels,
depends_on: [],
restart: 'always',
@@ -318,16 +318,14 @@ import * as buildpacks from '../lib/buildPacks';
}
},
networks: {
[docker.network]: {
[destinationDocker.network]: {
external: true
}
},
volumes: Object.assign({}, ...composeVolumes)
};
await fs.writeFile(`${workdir}/docker-compose.yml`, yaml.dump(composeFile));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose --project-directory ${workdir} up -d`
);
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose --project-directory ${workdir} up -d` })
await saveBuildLog({ line: 'Deployment successful!', buildId, applicationId });
} catch (error) {
await saveBuildLog({ line: error, buildId, applicationId });

View File

@@ -1,7 +1,7 @@
import { asyncExecShell, base64Encode, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
import { scheduler } from "../scheduler";
import { base64Encode, executeDockerCmd, generateTimestamp, getDomain, isDev, prisma, version } from "../common";
import { promises as fs } from 'fs';
import { day } from "../dayjs";
const staticApps = ['static', 'react', 'vuejs', 'svelte', 'gatsby', 'astro', 'eleventy'];
const nodeBased = [
'react',
@@ -511,8 +511,8 @@ export async function buildImage({
applicationId,
tag,
workdir,
docker,
buildId,
dockerId,
isCache = false,
debug = false,
dockerFileLocation = '/Dockerfile'
@@ -522,6 +522,9 @@ export async function buildImage({
} else {
await saveBuildLog({ line: `Building image started.`, buildId, applicationId });
}
if (debug) {
await saveBuildLog({ line: `\n###############\nIMPORTANT: Due to some issues during implementing Remote Docker Engine, the builds logs are not streamed at the moment. You will see the full build log when the build is finished!\n###############`, buildId, applicationId });
}
if (!debug && isCache) {
await saveBuildLog({
line: `Debug turned off. To see more details, allow it in the configuration.`,
@@ -529,16 +532,61 @@ export async function buildImage({
applicationId
});
}
const stream = await docker.engine.buildImage(
{ src: ['.'], context: workdir },
{
dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
const dockerFile = isCache ? `${dockerFileLocation}-cache` : `${dockerFileLocation}`
const cache = `${applicationId}:${tag}${isCache ? '-cache' : ''}`
const { stderr } = await executeDockerCmd({ dockerId, command: `docker build --progress plain -f ${workdir}/${dockerFile} -t ${cache} ${workdir}` })
if (debug) {
const array = stderr.split('\n')
for (const line of array) {
if (line !== '\n') {
await saveBuildLog({
line: `${line.replace('\n', '')}`,
buildId,
applicationId
});
}
}
);
await streamEvents({ stream, docker, buildId, applicationId, debug });
await saveBuildLog({ line: `Building image successful!`, buildId, applicationId });
}
// await new Promise((resolve, reject) => {
// const command = spawn(`docker`, ['build', '-f', `${workdir}${dockerFile}`, '-t', `${cache}`,`${workdir}`], {
// env: {
// DOCKER_HOST: 'ssh://root@95.217.178.202',
// DOCKER_BUILDKIT: '1'
// }
// });
// command.stdout.on('data', function (data) {
// console.log('stdout: ' + data);
// });
// command.stderr.on('data', function (data) {
// console.log('stderr: ' + data);
// });
// command.on('error', function (error) {
// console.log(error)
// reject(error)
// })
// command.on('exit', function (code) {
// console.log('exit code: ' + code);
// resolve(code)
// });
// })
// console.log({ stdout, stderr })
// const stream = await docker.engine.buildImage(
// { src: ['.'], context: workdir },
// {
// dockerfile: isCache ? `${dockerFileLocation}-cache` : dockerFileLocation,
// t: `${applicationId}:${tag}${isCache ? '-cache' : ''}`
// }
// );
// await streamEvents({ stream, docker, buildId, applicationId, debug });
if (isCache) {
await saveBuildLog({ line: `Building cache image successful.`, buildId, applicationId });
} else {
await saveBuildLog({ line: `Building image successful.`, buildId, applicationId });
}
}
export async function streamEvents({ stream, docker, buildId, applicationId, debug }) {
@@ -617,18 +665,16 @@ export function makeLabelForStandaloneApplication({
export async function buildCacheImageWithNode(data, imageForBuild) {
const {
applicationId,
tag,
workdir,
docker,
buildId,
baseDirectory,
installCommand,
buildCommand,
debug,
secrets,
pullmergeRequestId
} = data;
const isPnpm = checkPnpm(installCommand, buildCommand);
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`);
@@ -659,11 +705,12 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
Dockerfile.push(`COPY .${baseDirectory || ''} ./`);
Dockerfile.push(`RUN ${buildCommand}`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
await buildImage({ ...data, isCache: true });
}
export async function buildCacheImageForLaravel(data, imageForBuild) {
const { applicationId, tag, workdir, docker, buildId, debug, secrets, pullmergeRequestId } = data;
const { workdir, buildId, secrets, pullmergeRequestId } = data;
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`);
Dockerfile.push('WORKDIR /app');
@@ -687,22 +734,16 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
Dockerfile.push(`COPY resources /app/resources`);
Dockerfile.push(`RUN yarn install && yarn production`);
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
await buildImage({ ...data, isCache: true });
}
export async function buildCacheImageWithCargo(data, imageForBuild) {
const {
applicationId,
tag,
workdir,
docker,
buildId,
baseDirectory,
installCommand,
buildCommand,
debug,
secrets
} = data;
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`);
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
@@ -717,5 +758,5 @@ export async function buildCacheImageWithCargo(data, imageForBuild) {
Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`);
Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json');
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug });
await buildImage({ ...data, isCache: true });
}

View File

@@ -1,18 +1,18 @@
import { promises as fs } from 'fs';
import { buildImage } from './common';
export default async function ({
applicationId,
debug,
tag,
workdir,
docker,
buildId,
baseDirectory,
secrets,
pullmergeRequestId,
dockerFileLocation
}) {
export default async function (data) {
let {
applicationId,
debug,
tag,
workdir,
buildId,
baseDirectory,
secrets,
pullmergeRequestId,
dockerFileLocation
} = data
try {
const file = `${workdir}${dockerFileLocation}`;
let dockerFileOut = `${workdir}`;
@@ -45,7 +45,7 @@ export default async function ({
}
await fs.writeFile(`${dockerFileOut}${dockerFileLocation}`, Dockerfile.join('\n'));
await buildImage({ applicationId, tag, workdir, docker, buildId, debug, dockerFileLocation });
await buildImage(data);
} catch (error) {
throw error;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,33 +1,43 @@
import { asyncExecShell } from './common';
import Dockerode from 'dockerode';
export function getEngine(engine: string): string {
return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine;
}
export function dockerInstance({ destinationDocker }): { engine: Dockerode; network: string } {
return {
engine: new Dockerode({
socketPath: destinationDocker.engine
}),
network: destinationDocker.network
};
}
import { executeDockerCmd } from './common';
export async function checkContainer(engine: string, container: string, remove = false): Promise<boolean> {
const host = getEngine(engine);
export function formatLabelsOnDocker(data) {
return data.trim().split('\n').map(a => JSON.parse(a)).map((container) => {
const labels = container.Labels.split(',')
let jsonLabels = {}
labels.forEach(l => {
const name = l.split('=')[0]
const value = l.split('=')[1]
jsonLabels = { ...jsonLabels, ...{ [name]: value } }
})
container.Labels = jsonLabels;
return container
})
}
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<boolean> {
let containerFound = false;
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST="${host}" docker inspect --format '{{json .State}}' ${container}`
);
const { stdout } = await executeDockerCmd({
dockerId,
command:
`docker inspect --format '{{json .State}}' ${container}`
});
const parsedStdout = JSON.parse(stdout);
const status = parsedStdout.Status;
const isRunning = status === 'running';
if (status === 'created') {
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
await executeDockerCmd({
dockerId,
command:
`docker rm ${container}`
});
}
if (remove && status === 'exited') {
await asyncExecShell(`DOCKER_HOST="${host}" docker rm ${container}`);
await executeDockerCmd({
dockerId,
command:
`docker rm ${container}`
});
}
if (isRunning) {
containerFound = true;
@@ -38,13 +48,10 @@ export async function checkContainer(engine: string, container: string, remove =
return containerFound;
}
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {
let isExited = false;
const host = getEngine(engine);
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST="${host}" docker inspect -f '{{.State.Status}}' ${containerName}`
);
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect -f '{{.State.Status}}' ${containerName}` })
if (stdout.trim() === 'exited') {
isExited = true;
}
@@ -57,19 +64,17 @@ export async function isContainerExited(engine: string, containerName: string):
export async function removeContainer({
id,
engine
dockerId
}: {
id: string;
engine: string;
dockerId: string;
}): Promise<void> {
const host = getEngine(engine);
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
);
const { stdout } = await executeDockerCmd({ dockerId, command: `docker inspect --format '{{json .State}}' ${id}` })
if (JSON.parse(stdout).Running) {
await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${id}`);
await asyncExecShell(`DOCKER_HOST=${host} docker rm ${id}`);
await executeDockerCmd({ dockerId, command: `docker stop -t 0 ${id}` })
await executeDockerCmd({ dockerId, command: `docker rm ${id}` })
}
} catch (error) {
console.log(error);

View File

@@ -5,12 +5,12 @@ import axios from 'axios';
import { FastifyReply } from 'fastify';
import { day } from '../../../../lib/dayjs';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, dockerInstance, getEngine, isContainerExited, removeContainer } from '../../../../lib/docker';
import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, formatLabelsOnDocker, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler';
import type { FastifyRequest } from 'fastify';
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication } from './types';
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication, CheckDomain, StopPreviewApplication } from './types';
import { OnlyId } from '../../../../types';
export async function listApplications(request: FastifyRequest) {
@@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) {
const { teamId } = request.user
const applications = await prisma.application.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true }
include: { teams: true, destinationDocker: true }
});
const settings = await prisma.setting.findFirst()
return {
@@ -57,7 +57,28 @@ export async function getImages(request: FastifyRequest<GetImages>) {
}
return { baseImage, baseBuildImage, baseBuildImages, baseImages, publishDirectory, port }
return { baseBuildImage, baseBuildImages, publishDirectory, port }
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const { teamId } = request.user
let isRunning = false;
let isExited = false;
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
isExited = await isContainerExited(application.destinationDocker.id, id);
}
return {
isQueueActive: scheduler.workers.has('deployApplication'),
isRunning,
isExited,
};
} catch ({ status, message }) {
return errorHandler({ status, message })
}
@@ -68,17 +89,9 @@ export async function getApplication(request: FastifyRequest<OnlyId>) {
const { id } = request.params
const { teamId } = request.user
const appId = process.env['COOLIFY_APP_ID'];
let isRunning = false;
let isExited = false;
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId && application.destinationDocker?.engine) {
isRunning = await checkContainer(application.destinationDocker.engine, id);
isExited = await isContainerExited(application.destinationDocker.engine, id);
}
return {
isQueueActive: scheduler.workers.has('deployApplication'),
isRunning,
isExited,
application,
appId
};
@@ -279,16 +292,35 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
}
}
export async function stopPreviewApplication(request: FastifyRequest<StopPreviewApplication>, reply: FastifyReply) {
try {
const { id } = request.params
const { pullmergeRequestId } = request.body
const { teamId } = request.user
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
const container = `${id}-${pullmergeRequestId}`
const { id: dockerId } = application.destinationDocker;
const found = await checkContainer({ dockerId, container });
if (found) {
await removeContainer({ id: container, dockerId: application.destinationDocker.id });
}
}
return reply.code(201).send();
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try {
const { id } = request.params
const { teamId } = request.user
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId && application.destinationDocker?.engine) {
const { engine } = application.destinationDocker;
const found = await checkContainer(engine, id);
if (application?.destinationDockerId) {
const { id: dockerId } = application.destinationDocker;
const found = await checkContainer({ dockerId, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: application.destinationDocker.id });
}
}
return reply.code(201).send();
@@ -304,17 +336,17 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
where: { id },
include: { destinationDocker: true }
});
if (application?.destinationDockerId && application.destinationDocker?.engine && application.destinationDocker?.network) {
const host = getEngine(application.destinationDocker.engine);
const { stdout: containers } = await asyncExecShell(
`DOCKER_HOST=${host} docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
);
if (application?.destinationDockerId && application.destinationDocker?.network) {
const { stdout: containers } = await executeDockerCmd({
dockerId: application.destinationDocker.id,
command: `docker ps -a --filter network=${application.destinationDocker.network} --filter name=${id} --format '{{json .}}'`
})
if (containers) {
const containersArray = containers.trim().split('\n');
for (const container of containersArray) {
const containerObj = JSON.parse(container);
const id = containerObj.ID;
await removeContainer({ id, engine: application.destinationDocker.engine });
await removeContainer({ id, dockerId: application.destinationDocker.id });
}
}
}
@@ -333,33 +365,48 @@ export async function deleteApplication(request: FastifyRequest<DeleteApplicatio
return errorHandler({ status, message })
}
}
export async function checkDomain(request: FastifyRequest<CheckDomain>) {
try {
const { id } = request.params
const { domain } = request.query
const { fqdn, settings: { dualCerts } } = await prisma.application.findUnique({ where: { id }, include: { settings: true } })
return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function checkDNS(request: FastifyRequest<CheckDNS>) {
try {
const { id } = request.params
let { exposePort, fqdn, forceSave, dualCerts } = request.body
fqdn = fqdn.toLowerCase();
if (fqdn) fqdn = fqdn.toLowerCase();
if (exposePort) exposePort = Number(exposePort);
const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
const found = await isDomainConfigured({ id, fqdn });
const found = await isDomainConfigured({ id, fqdn, dockerId });
if (found) {
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
}
if (exposePort) {
exposePort = Number(exposePort);
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
const { default: getPort } = await import('get-port');
const publicPort = await getPort({ port: exposePort });
if (publicPort !== exposePort) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
if (isDNSCheckEnabled && !isDev && !forceSave) {
return await checkDomainsIsValidInDNS({ hostname: request.hostname.split(':')[0], fqdn, dualCerts });
let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress;
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
}
return {}
} catch ({ status, message }) {
@@ -375,7 +422,7 @@ export async function getUsage(request) {
const application: any = await getApplicationFromDB(id, teamId);
if (application.destinationDockerId) {
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]);
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.id, id)]);
}
return {
usage
@@ -701,21 +748,20 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
secret.value = decrypt(secret.value);
return secret;
});
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
const destinationDocker = await prisma.destinationDocker.findFirst({
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
});
const docker = dockerInstance({ destinationDocker });
const listContainers = await docker.engine.listContainers({
filters: { network: [destinationDocker.network], name: [id] }
});
const containers = listContainers.filter((container) => {
return (
container.Labels['coolify.configuration'] &&
container.Labels['coolify.type'] === 'standalone-application'
);
});
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 .}}"` })
if (stdout === '') {
return {
containers: [],
applicationSecrets: [],
PRMRSecrets: []
}
}
const containers = formatLabelsOnDocker(stdout).filter(container => container.Labels['coolify.configuration'] && container.Labels['coolify.type'] === 'standalone-application')
const jsonContainers = containers
.map((container) =>
JSON.parse(Buffer.from(container.Labels['coolify.configuration'], 'base64').toString())
@@ -733,50 +779,46 @@ export async function getPreviews(request: FastifyRequest<OnlyId>) {
})
}
} catch ({ status, message }) {
console.log({ status, message })
return errorHandler({ status, message })
}
}
export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
try {
const { id } = request.params
const { id } = request.params;
let { since = 0 } = request.query
if (since !== 0) {
since = day(since).unix();
}
const { destinationDockerId, destinationDocker } = await prisma.application.findUnique({
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.application.findUnique({
where: { id },
include: { destinationDocker: true }
});
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try {
const container = await docker.engine.getContainer(id);
if (container) {
const { default: ansi } = await import('strip-ansi')
const logs = (
await container.logs({
stdout: true,
stderr: true,
timestamps: true,
since,
tail: 5000
})
)
.toString()
.split('\n')
.map((l) => ansi(l.slice(8)))
.filter((a) => a);
// const found = await checkContainer({ dockerId, container: id })
// if (found) {
const { default: ansi } = await import('strip-ansi')
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout)
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
return { logs: sortedLogs }
// }
} catch (error) {
const { statusCode } = error;
if (statusCode === 404) {
return {
logs
logs: []
};
}
} catch (error) {
return {
logs: []
};
}
}
return {
message: 'No logs found.'
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}

View File

@@ -1,8 +1,8 @@
import { FastifyPluginAsync } from 'fastify';
import { OnlyId } from '../../../../types';
import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers';
import { cancelDeployment, checkDNS, checkDomain, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getApplicationStatus, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication, stopPreviewApplication } from './handlers';
import type { CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage } from './types';
import type { CancelDeployment, CheckDNS, CheckDomain, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, StopPreviewApplication } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
@@ -17,9 +17,14 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply));
fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply));
fastify.get<OnlyId>('/:id/status', async (request) => await getApplicationStatus(request));
fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
fastify.post<StopPreviewApplication>('/:id/stop/preview', async (request, reply) => await stopPreviewApplication(request, reply));
fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply));
fastify.get<CheckDomain>('/:id/check', async (request) => await checkDomain(request));
fastify.post<CheckDNS>('/:id/check', async (request) => await checkDNS(request));
fastify.get<OnlyId>('/:id/secrets', async (request) => await getSecrets(request));

View File

@@ -30,6 +30,9 @@ export interface SaveApplicationSettings extends OnlyId {
export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; };
}
export interface CheckDomain extends OnlyId {
Querystring: { domain: string; };
}
export interface CheckDNS extends OnlyId {
Querystring: { domain: string; };
Body: {
@@ -115,3 +118,9 @@ export interface DeployApplication extends OnlyId {
branch: string
}
}
export interface StopPreviewApplication extends OnlyId {
Body: {
pullmergeRequestId: string | null,
}
}

View File

@@ -1,10 +1,13 @@
import { FastifyPluginAsync } from 'fastify';
import { errorHandler, version } from '../../../../lib/common';
import { errorHandler, listSettings, version } from '../../../../lib/common';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/', async () => {
const settings = await listSettings()
try {
return {
ipv4: settings.ipv4,
ipv6: settings.ipv6,
version,
whiteLabeled: process.env.COOLIFY_WHITE_LABELED === 'true',
whiteLabeledIcon: process.env.COOLIFY_WHITE_LABELED_ICON,

View File

@@ -3,24 +3,20 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import yaml from 'js-yaml';
import fs from 'fs/promises';
import { asyncExecShell, ComposeFile, createDirectories, decrypt, encrypt, errorHandler, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTcpProxy, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { dockerInstance, getEngine } from '../../../../lib/docker';
import { ComposeFile, createDirectories, decrypt, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import { day } from '../../../../lib/dayjs';
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
import { SaveDatabaseType } from './types';
export async function listDatabases(request: FastifyRequest) {
try {
const teamId = request.user.teamId;
let databases = []
if (teamId === '0') {
databases = await prisma.database.findMany({ include: { teams: true } });
} else {
databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId } } },
include: { teams: true }
});
}
const databases = await prisma.database.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true, destinationDocker: true }
});
return {
databases
}
@@ -56,6 +52,36 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
return errorHandler({ status, message })
}
}
export async function getDatabaseStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params;
const teamId = request.user.teamId;
let isRunning = false;
const database = await prisma.database.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { destinationDocker: true, settings: true }
});
const { destinationDockerId, destinationDocker } = database;
if (destinationDockerId) {
try {
const { stdout } = await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker inspect --format '{{json .State}}' ${id}` })
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
return {
isRunning
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getDatabase(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params;
@@ -69,29 +95,11 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
}
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const { destinationDockerId, destinationDocker } = database;
let isRunning = false;
if (destinationDockerId) {
const host = getEngine(destinationDocker.engine);
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
);
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
const configuration = generateDatabaseConfiguration(database);
const settings = await listSettings();
return {
privatePort: configuration?.privatePort,
database,
isRunning,
versions: await getDatabaseVersions(database.type),
settings
};
@@ -164,16 +172,15 @@ export async function saveDatabaseDestination(request: FastifyRequest<SaveDataba
const {
destinationDockerId,
destinationDocker: { engine },
destinationDocker: { engine, id: dockerId },
version,
type
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
if (destinationDockerId) {
const host = getEngine(engine);
if (type && version) {
const baseImage = getDatabaseImage(type);
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
executeDockerCmd({ dockerId, command: `docker pull ${baseImage}:${version}` })
}
}
return reply.code(201).send({})
@@ -194,7 +201,7 @@ export async function getDatabaseUsage(request: FastifyRequest<OnlyId>) {
if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
if (database.destinationDockerId) {
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.engine, id)]);
[usage] = await Promise.all([getContainerUsage(database.destinationDocker.id, id)]);
}
return {
usage
@@ -225,7 +232,6 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
generateDatabaseConfiguration(database);
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const volumeName = volume.split(':')[0];
const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
@@ -267,13 +273,13 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${volumeName}`);
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker volume create ${volumeName}` })
} catch (error) {
console.log(error);
}
try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
if (isPublic) await startTcpProxy(destinationDocker, id, publicPort, privatePort);
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
return {};
} catch (error) {
throw {
@@ -311,39 +317,27 @@ export async function stopDatabase(request: FastifyRequest<OnlyId>) {
}
export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
let { since = 0 } = request.query
if (since !== 0) {
since = day(since).unix();
}
const { destinationDockerId, destinationDocker } = await prisma.database.findUnique({
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.database.findUnique({
where: { id },
include: { destinationDocker: true }
});
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try {
const container = await docker.engine.getContainer(id);
if (container) {
const { default: ansi } = await import('strip-ansi')
const logs = (
await container.logs({
stdout: true,
stderr: true,
timestamps: true,
since,
tail: 5000
})
)
.toString()
.split('\n')
.map((l) => ansi(l.slice(8)))
.filter((a) => a);
return {
logs
};
}
// const found = await checkContainer({ dockerId, container: id })
// if (found) {
const { default: ansi } = await import('strip-ansi')
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout)
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
return { logs: sortedLogs }
// }
} catch (error) {
const { statusCode } = error;
if (statusCode === 404) {
@@ -432,8 +426,10 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
const teamId = request.user.teamId;
const { id } = request.params;
const { isPublic, appendOnly = true } = request.body;
const publicPort = await getFreePort();
const settings = await listSettings();
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
const publicPort = await getFreePublicPort(id, dockerId);
await prisma.database.update({
where: { id },
data: {
@@ -453,11 +449,7 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
if (destinationDockerId) {
if (isPublic) {
await prisma.database.update({ where: { id }, data: { publicPort } });
if (settings.isTraefikUsed) {
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
} else {
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
}
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
} else {
await prisma.database.update({ where: { id }, data: { publicPort: null } });
await stopTcpHttpProxy(id, destinationDocker, oldPublicPort);

View File

@@ -1,5 +1,5 @@
import { FastifyPluginAsync } from 'fastify';
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
import type { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
import type { SaveDatabaseType } from './types';
@@ -15,6 +15,8 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getDatabaseStatus(request));
fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request));
fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request));

View File

@@ -1,14 +1,19 @@
import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import { asyncExecShell, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker';
import sshConfig from 'ssh-config'
import fs from 'fs/promises'
import os from 'os';
import { asyncExecShell, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types';
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
export async function listDestinations(request: FastifyRequest) {
export async function listDestinations(request: FastifyRequest<ListDestinations>) {
try {
const teamId = request.user.teamId;
const { onlyVerified = false } = request.query
let destinations = []
if (teamId === '0') {
destinations = await prisma.destinationDocker.findMany({ include: { teams: true } });
@@ -18,6 +23,9 @@ export async function listDestinations(request: FastifyRequest) {
include: { teams: true }
});
}
if (onlyVerified) {
destinations = destinations.filter(destination => destination.engine || (destination.remoteEngine && destination.remoteVerified))
}
return {
destinations
}
@@ -44,7 +52,8 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
const { id } = request.params
const teamId = request.user?.teamId;
const destination = await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } }
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { sshKey: true }
});
if (!destination && id !== 'new') {
throw { status: 404, message: `Destination not found.` };
@@ -52,23 +61,8 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
const settings = await listSettings();
let payload = {
destination,
settings,
state: false
settings
};
if (destination?.remoteEngine) {
// const { stdout } = await asyncExecShell(
// `ssh -p ${destination.port} ${destination.user}@${destination.ipAddress} "docker ps -a"`
// );
// console.log(stdout)
// const engine = await generateRemoteEngine(destination);
// // await saveSshKey(destination);
// payload.state = await checkContainer(engine, 'coolify-haproxy');
} else {
const containerName = 'coolify-proxy';
payload.state =
destination?.engine && (await checkContainer(destination.engine, containerName));
}
return {
...payload
};
@@ -79,68 +73,68 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
}
export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
try {
const { id } = request.params
let { name, network, engine, isCoolifyProxyUsed } = request.body
const teamId = request.user.teamId;
if (id === 'new') {
const host = getEngine(engine);
const docker = dockerInstance({ destinationDocker: { engine, network } });
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
if (found.length === 0) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
}
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
});
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
const destination = destinations.find((destination) => destination.network === network);
const { id } = request.params
if (destinations.length > 0) {
const proxyConfigured = destinations.find(
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
);
if (proxyConfigured) {
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
let { name, network, engine, isCoolifyProxyUsed, remoteIpAddress, remoteUser, remotePort } = request.body
if (id === 'new') {
console.log(engine)
if (engine) {
const { stdout } = await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network ls --filter 'name=^${network}$' --format '{{json .}}'`);
if (stdout === '') {
await asyncExecShell(`DOCKER_HOST=unix:///var/run/docker.sock docker network create --attachable ${network}`);
}
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
}
if (isCoolifyProxyUsed) {
const settings = await prisma.setting.findFirst();
if (settings?.isTraefikUsed) {
await startTraefikProxy(engine);
} else {
await startCoolifyProxy(engine);
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
});
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
const destination = destinations.find((destination) => destination.network === network);
if (destinations.length > 0) {
const proxyConfigured = destinations.find(
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
);
if (proxyConfigured) {
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
}
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
}
if (isCoolifyProxyUsed) {
await startTraefikProxy(destination.id);
}
return reply.code(201).send({ id: destination.id });
} else {
const destination = await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress, remoteUser, remotePort }
});
return reply.code(201).send({ id: destination.id })
}
return reply.code(201).send({ id: destination.id });
} else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send();
}
} catch ({ status, message }) {
console.log({ status, message })
return errorHandler({ status, message })
}
}
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const destination = await prisma.destinationDocker.delete({ where: { id } });
if (destination.isCoolifyProxyUsed) {
const host = getEngine(destination.engine);
const { network } = destination;
const settings = await prisma.setting.findFirst();
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy';
const { stdout: found } = await asyncExecShell(
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'`
);
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}`
);
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
const { network, remoteVerified, engine, isCoolifyProxyUsed } = await prisma.destinationDocker.findUnique({ where: { id } });
if (isCoolifyProxyUsed) {
if (engine || remoteVerified) {
const { stdout: found } = await executeDockerCmd({
dockerId: id,
command: `docker ps -a --filter network=${network} --filter name=coolify-proxy --format '{{.}}'`
})
if (found) {
await executeDockerCmd({ dockerId: id, command: `docker network disconnect ${network} coolify-proxy` })
await executeDockerCmd({ dockerId: id, command: `docker network rm ${network}` })
}
}
}
await prisma.destinationDocker.delete({ where: { id } });
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -163,34 +157,105 @@ export async function saveDestinationSettings(request: FastifyRequest<SaveDestin
}
}
export async function startProxy(request: FastifyRequest<Proxy>) {
const { engine } = request.body;
const { id } = request.params
try {
await startTraefikProxy(engine);
await startTraefikProxy(id);
return {}
} catch ({ status, message }) {
await stopTraefikProxy(engine);
console.log({ status, message })
await stopTraefikProxy(id);
return errorHandler({ status, message })
}
}
export async function stopProxy(request: FastifyRequest<Proxy>) {
const { engine } = request.body;
const { id } = request.params
try {
await stopTraefikProxy(engine);
await stopTraefikProxy(id);
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function restartProxy(request: FastifyRequest<Proxy>) {
const { engine } = request.body;
const { id } = request.params
try {
await stopTraefikProxy(engine);
await startTraefikProxy(engine);
await prisma.destinationDocker.updateMany({
where: { engine },
await stopTraefikProxy(id);
await startTraefikProxy(id);
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: true }
});
return {}
} catch ({ status, message }) {
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: false }
});
return errorHandler({ status, message })
}
}
export async function assignSSHKey(request: FastifyRequest) {
try {
const { id: sshKeyId } = request.body;
const { id } = request.params;
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function verifyRemoteDockerEngine(request: FastifyRequest, reply: FastifyReply) {
try {
const { id } = request.params;
const homedir = os.homedir();
const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } })
await fs.writeFile(`/tmp/id_rsa_verification_${id}`, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 })
const host = `ssh://${remoteUser}@${remoteIpAddress}`
const config = sshConfig.parse('')
const found = config.find({ Host: remoteIpAddress })
if (!found) {
config.append({
Host: remoteIpAddress,
Port: remotePort.toString(),
User: remoteUser,
IdentityFile: `/tmp/id_rsa_verification_${id}`,
StrictHostKeyChecking: 'no'
})
}
try {
await fs.stat(`${homedir}/.ssh/`)
} catch (error) {
await fs.mkdir(`${homedir}/.ssh/`)
}
await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
if (!stdout) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy' })
return {
isRunning
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}

View File

@@ -1,24 +1,29 @@
import { FastifyPluginAsync } from 'fastify';
import { checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy } from './handlers';
import { assignSSHKey, checkDestination, deleteDestination, getDestination, getDestinationStatus, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
import type { OnlyId } from '../../../../types';
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
import type { CheckDestination, ListDestinations, NewDestination, Proxy, SaveDestinationSettings } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify()
})
fastify.get('/', async (request) => await listDestinations(request));
fastify.get<ListDestinations>('/', async (request) => await listDestinations(request));
fastify.post<CheckDestination>('/check', async (request) => await checkDestination(request));
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getDestinationStatus(request));
fastify.post<SaveDestinationSettings>('/:id/settings', async (request, reply) => await saveDestinationSettings(request));
fastify.post<Proxy>('/:id/start', async (request, reply) => await startProxy(request));
fastify.post<Proxy>('/:id/stop', async (request, reply) => await stopProxy(request));
fastify.post<Proxy>('/:id/restart', async (request, reply) => await restartProxy(request));
fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
fastify.post<Proxy>('/:id/start', async (request,) => await startProxy(request));
fastify.post<Proxy>('/:id/stop', async (request) => await stopProxy(request));
fastify.post<Proxy>('/:id/restart', async (request) => await restartProxy(request));
fastify.post('/:id/configuration/sshKey', async (request) => await assignSSHKey(request));
fastify.post('/:id/verify', async (request, reply) => await verifyRemoteDockerEngine(request, reply));
};
export default root;

View File

@@ -1,5 +1,10 @@
import { OnlyId } from "../../../../types"
export interface ListDestinations {
Querystring: {
onlyVerified: string
}
}
export interface CheckDestination {
Body: {
network: string
@@ -20,7 +25,5 @@ export interface SaveDestinationSettings extends OnlyId {
}
}
export interface Proxy extends OnlyId {
Body: {
engine: string
}
}

View File

@@ -17,7 +17,8 @@ export async function hashPassword(password: string): Promise<string> {
export async function cleanupManually() {
try {
await cleanupDockerStorage('unix:///var/run/docker.sock', true, true)
const destination = await prisma.destinationDocker.findFirst({ where: { engine: '/var/run/docker.sock' } })
await cleanupDockerStorage(destination.id, true, true)
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -154,7 +155,6 @@ export async function login(request: FastifyRequest<Login>, reply: FastifyReply)
}
if (userFound) {
if (userFound.type === 'email') {
// TODO: Review this one
if (userFound.password === 'RESETME') {
const hashedPassword = await hashPassword(password);
if (userFound.updatedAt < new Date(Date.now() - 1000 * 60 * 10)) {

View File

@@ -273,11 +273,15 @@ export async function inviteToTeam(request: FastifyRequest<InviteToTeam>, reply:
const { email, permission, teamId, teamName } = request.body;
const userFound = await prisma.user.findUnique({ where: { email } });
if (!userFound) {
throw `No user found with '${email}' email address.`
throw {
message: `No user found with '${email}' email address.`
};
}
const uid = userFound.id;
if (uid === userId) {
throw `Invitation to yourself? Whaaaaat?`
if (uid === userId) {
throw {
message: `Invitation to yourself? Whaaaaat?`
};
}
const alreadyInTeam = await prisma.team.findFirst({
where: { id: teamId, users: { some: { id: uid } } }

View File

@@ -2,13 +2,13 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises';
import yaml from 'js-yaml';
import bcrypt from 'bcryptjs';
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions } from '../../../../lib/common';
import { prisma, uniqueName, asyncExecShell, getServiceImage, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePublicPort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions, executeDockerCmd, listSettings, getFreeExposedPort, checkDomainsIsValidInDNS } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs';
import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker';
import { checkContainer, isContainerExited, removeContainer } from '../../../../lib/docker';
import cuid from 'cuid';
import type { OnlyId } from '../../../../types';
import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
// async function startServiceNew(request: FastifyRequest<OnlyId>) {
// try {
@@ -145,15 +145,10 @@ import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteSer
export async function listServices(request: FastifyRequest) {
try {
const teamId = request.user.teamId;
let services = []
if (teamId === '0') {
services = await prisma.service.findMany({ include: { teams: true } });
} else {
services = await prisma.service.findMany({
where: { teams: { some: { id: teamId } } },
include: { teams: true }
});
}
const services = await prisma.service.findMany({
where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { teams: true, destinationDocker: true }
});
return {
services
}
@@ -172,43 +167,41 @@ export async function newService(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message })
}
}
export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
let isRunning = false;
let isExited = false
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, settings } = service;
if (destinationDockerId) {
isRunning = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
isExited = await isContainerExited(service.destinationDocker.id, id);
}
return {
isRunning,
isExited,
settings
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function getService(request: FastifyRequest<OnlyId>) {
try {
const teamId = request.user.teamId;
const { id } = request.params;
const service = await getServiceFromDB({ id, teamId });
const settings = await listSettings()
if (!service) {
throw { status: 404, message: 'Service not found.' }
}
const { destinationDockerId, destinationDocker, type, version, settings } = service;
let isRunning = false;
if (destinationDockerId) {
const host = getEngine(destinationDocker.engine);
const docker = dockerInstance({ destinationDocker });
const baseImage = getServiceImage(type);
const images = getServiceImages(type);
docker.engine.pull(`${baseImage}:${version}`);
if (images?.length > 0) {
for (const image of images) {
docker.engine.pull(`${image}:latest`);
}
}
try {
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
);
if (JSON.parse(stdout).Running) {
isRunning = true;
}
} catch (error) {
//
}
}
return {
isRunning,
service,
settings
}
@@ -282,7 +275,7 @@ export async function getServiceUsage(request: FastifyRequest<OnlyId>) {
const service = await getServiceFromDB({ id, teamId });
if (service.destinationDockerId) {
[usage] = await Promise.all([getContainerUsage(service.destinationDocker.engine, id)]);
[usage] = await Promise.all([getContainerUsage(service.destinationDocker.id, id)]);
}
return {
usage
@@ -299,33 +292,22 @@ export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
if (since !== 0) {
since = day(since).unix();
}
const { destinationDockerId, destinationDocker } = await prisma.service.findUnique({
const { destinationDockerId, destinationDocker: { id: dockerId } } = await prisma.service.findUnique({
where: { id },
include: { destinationDocker: true }
});
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
try {
const container = await docker.engine.getContainer(id);
if (container) {
const { default: ansi } = await import('strip-ansi')
const logs = (
await container.logs({
stdout: true,
stderr: true,
timestamps: true,
since,
tail: 5000
})
)
.toString()
.split('\n')
.map((l) => ansi(l.slice(8)))
.filter((a) => a);
return {
logs
};
}
// const found = await checkContainer({ dockerId, container: id })
// if (found) {
const { default: ansi } = await import('strip-ansi')
const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` })
const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a);
const logs = stripLogsStderr.concat(stripLogsStdout)
const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1))
return { logs: sortedLogs }
// }
} catch (error) {
const { statusCode } = error;
if (statusCode === 404) {
@@ -364,40 +346,57 @@ export async function saveServiceSettings(request: FastifyRequest<SaveServiceSet
return errorHandler({ status, message })
}
}
export async function checkServiceDomain(request: FastifyRequest<CheckServiceDomain>) {
try {
const { id } = request.params
const { domain } = request.query
const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } })
return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function checkService(request: FastifyRequest<CheckService>) {
try {
const { id } = request.params;
let { fqdn, exposePort, otherFqdns } = request.body;
let { fqdn, exposePort, forceSave, otherFqdns, dualCerts } = request.body;
if (fqdn) fqdn = fqdn.toLowerCase();
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
if (exposePort) exposePort = Number(exposePort);
let found = await isDomainConfigured({ id, fqdn });
const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } })
const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
let found = await isDomainConfigured({ id, fqdn, dockerId });
if (found) {
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
}
if (otherFqdns && otherFqdns.length > 0) {
for (const ofqdn of otherFqdns) {
found = await isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true });
found = await isDomainConfigured({ id, fqdn: ofqdn, dockerId });
if (found) {
throw { status: 500, message: `Domain ${getDomain(ofqdn).replace('www.', '')} is already in use!` }
}
}
}
if (exposePort) {
const { default: getPort } = await import('get-port');
exposePort = Number(exposePort);
if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
}
const publicPort = await getPort({ port: exposePort });
if (publicPort !== exposePort) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
if (configuredPort !== exposePort) {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` }
}
}
}
if (isDNSCheckEnabled && !isDev && !forceSave) {
let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress;
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
}
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -742,7 +741,6 @@ async function startPlausibleAnalyticsService(request: FastifyRequest<ServiceSta
});
}
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('plausibleanalytics');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -859,10 +857,8 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -877,17 +873,17 @@ async function stopPlausibleAnalyticsService(request: FastifyRequest<ServiceStar
if (destinationDockerId) {
const engine = destinationDocker.engine;
let found = await checkContainer(engine, id);
let found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
found = await checkContainer(engine, `${id}-postgresql`);
found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
if (found) {
await removeContainer({ id: `${id}-postgresql`, engine });
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
}
found = await checkContainer(engine, `${id}-clickhouse`);
found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-clickhouse` });
if (found) {
await removeContainer({ id: `${id}-clickhouse`, engine });
await removeContainer({ id: `${id}-clickhouse`, dockerId: destinationDocker.id });
}
}
@@ -905,7 +901,6 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('nocodb');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -956,8 +951,8 @@ async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -970,10 +965,9 @@ async function stopNocodbService(request: FastifyRequest<ServiceStartStop>) {
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
}
return {}
@@ -999,10 +993,10 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('minio');
const publicPort = await getFreePort();
const { service: { destinationDocker: { id: dockerId } } } = await prisma.minio.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } })
const publicPort = await getFreePublicPort(id, dockerId);
const consolePort = 9001;
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1058,8 +1052,8 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
return {}
} catch ({ status, message }) {
@@ -1071,13 +1065,12 @@ async function stopMinioService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
await prisma.minio.update({ where: { serviceId: id }, data: { publicPort: null } })
if (destinationDockerId) {
const engine = destinationDocker.engine;
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
}
return {}
@@ -1103,7 +1096,6 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('vscodeserver');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1175,16 +1167,16 @@ async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
const changePermissionOn = persistentStorage.map((p) => p.path);
if (changePermissionOn.length > 0) {
await asyncExecShell(
`DOCKER_HOST=${host} docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join(
await executeDockerCmd({
dockerId: destinationDocker.id, command: `docker exec -u root ${id} chown -R 1000:1000 ${changePermissionOn.join(
' '
)}`
);
})
}
return {}
} catch ({ status, message }) {
@@ -1196,12 +1188,11 @@ async function stopVscodeService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
}
return {}
@@ -1236,7 +1227,6 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const image = getServiceImage(type);
const port = getServiceMainPort('wordpress');
@@ -1328,8 +1318,10 @@ async function startWordpressService(request: FastifyRequest<ServiceStartStop>)
}
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1346,28 +1338,27 @@ async function stopWordpressService(request: FastifyRequest<ServiceStartStop>) {
wordpress: { ftpEnabled }
} = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-mysql`);
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mysql` });
if (found) {
await removeContainer({ id: `${id}-mysql`, engine });
await removeContainer({ id: `${id}-mysql`, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
}
try {
if (ftpEnabled) {
const found = await checkContainer(engine, `${id}-ftp`);
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
if (found) {
await removeContainer({ id: `${id}-ftp`, engine });
await removeContainer({ id: `${id}-ftp`, dockerId: destinationDocker.id });
}
await prisma.wordpress.update({
where: { serviceId: id },
@@ -1393,7 +1384,6 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('vaultwarden');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1444,8 +1434,10 @@ async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1456,14 +1448,12 @@ async function stopVaultwardenService(request: FastifyRequest<ServiceStartStop>)
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -1483,7 +1473,6 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('languagetool');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1536,8 +1525,9 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1548,14 +1538,12 @@ async function stopLanguageToolService(request: FastifyRequest<ServiceStartStop>
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -1575,7 +1563,6 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('n8n');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1628,8 +1615,10 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1640,14 +1629,12 @@ async function stopN8nService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -1667,7 +1654,6 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('uptimekuma');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1719,8 +1705,9 @@ async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>)
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1731,14 +1718,12 @@ async function stopUptimekumaService(request: FastifyRequest<ServiceStartStop>)
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -1774,7 +1759,6 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
@@ -1871,8 +1855,9 @@ async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1883,18 +1868,16 @@ async function stopGhostService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
let found = await checkContainer(engine, id);
let found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
found = await checkContainer(engine, `${id}-mariadb`);
found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mariadb` });
if (found) {
await removeContainer({ id: `${id}-mariadb`, engine });
await removeContainer({ id: `${id}-mariadb`, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -1917,7 +1900,6 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('meilisearch');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -1972,8 +1954,10 @@ async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -1984,14 +1968,12 @@ async function stopMeilisearchService(request: FastifyRequest<ServiceStartStop>)
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -2024,7 +2006,6 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('umami');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -2191,8 +2172,10 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -2203,22 +2186,20 @@ async function stopUmamiService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-postgresql`);
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
if (found) {
await removeContainer({ id: `${id}-postgresql`, engine });
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -2245,7 +2226,6 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
hasura: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('hasura');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -2327,8 +2307,9 @@ async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -2339,22 +2320,20 @@ async function stopHasuraService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-postgresql`);
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
if (found) {
await removeContainer({ id: `${id}-postgresql`, engine });
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -2396,7 +2375,6 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('fider');
const { workdir } = await createDirectories({ repository: type, buildId: id });
@@ -2489,8 +2467,8 @@ async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
@@ -2502,22 +2480,20 @@ async function stopFiderService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-postgresql`);
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-postgresql` });
if (found) {
await removeContainer({ id: `${id}-postgresql`, engine });
await removeContainer({ id: `${id}-postgresql`, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -2554,12 +2530,10 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('moodle');
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const domain = getDomain(fqdn);
const config = {
moodle: {
image: `${image}:${version}`,
@@ -2652,8 +2626,10 @@ async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
//await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} pull` })
await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up --build -d` })
return {}
} catch ({ status, message }) {
@@ -2665,22 +2641,20 @@ async function stopMoodleService(request: FastifyRequest<ServiceStartStop>) {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
const { destinationDockerId, destinationDocker } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
const found = await checkContainer({ dockerId: destinationDocker.id, container: id });
if (found) {
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-mariadb`);
const found = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-mariadb` });
if (found) {
await removeContainer({ id: `${id}-mariadb`, engine });
await removeContainer({ id: `${id}-mariadb`, dockerId: destinationDocker.id });
}
} catch (error) {
console.error(error);
@@ -2703,14 +2677,10 @@ export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, re
plausibleAnalytics: { postgresqlUser, postgresqlPassword, postgresqlDatabase }
} = await getServiceFromDB({ id, teamId });
if (destinationDockerId) {
const docker = dockerInstance({ destinationDocker });
const container = await docker.engine.getContainer(id);
const command = await container.exec({
Cmd: [
`psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"`
]
});
await command.start();
await executeDockerCmd({
dockerId: destinationDocker.id,
command: `docker exec ${id} 'psql -H postgresql://${postgresqlUser}:${postgresqlPassword}@localhost:5432/${postgresqlDatabase} -c "UPDATE users SET email_verified = true;"'`
})
return await reply.code(201).send()
}
throw { status: 500, message: 'Could not activate users.' }
@@ -2722,7 +2692,10 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
const { id } = request.params
const { ftpEnabled } = request.body;
const publicPort = await getFreePort();
const { service: { destinationDocker: { id: dockerId } } } = await prisma.wordpress.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } })
const publicPort = await getFreePublicPort(id, dockerId);
let ftpUser = cuid();
let ftpPassword = generatePassword();
@@ -2742,7 +2715,6 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
ftpHostKeyPrivate
} = data;
const { network, engine } = destinationDocker;
const host = getEngine(engine);
if (ftpEnabled) {
if (user) ftpUser = user;
if (savedPassword) ftpPassword = decrypt(savedPassword);
@@ -2789,7 +2761,7 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
});
try {
const isRunning = await checkContainer(engine, `${id}-ftp`);
const isRunning = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
if (isRunning) {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
@@ -2841,9 +2813,11 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
);
await asyncExecShell(`chmod +x ${hostkeyDir}/${id}.sh`);
await fs.writeFile(`${hostkeyDir}/${id}-docker-compose.yml`, yaml.dump(compose));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
);
await executeDockerCmd({
dockerId: destinationDocker.id,
command: `docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
})
}
return reply.code(201).send({
publicPort,
@@ -2856,9 +2830,11 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
data: { ftpPublicPort: null }
});
try {
await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
);
await executeDockerCmd({
dockerId: destinationDocker.id,
command: `docker stop -t 0 ${id}-ftp && docker rm ${id}-ftp`
})
} catch (error) {
//
}

View File

@@ -3,12 +3,14 @@ import {
activatePlausibleUsers,
activateWordpressFtp,
checkService,
checkServiceDomain,
deleteService,
deleteServiceSecret,
deleteServiceStorage,
getService,
getServiceLogs,
getServiceSecrets,
getServiceStatus,
getServiceStorages,
getServiceType,
getServiceUsage,
@@ -28,7 +30,7 @@ import {
} from './handlers';
import type { OnlyId } from '../../../../types';
import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
import type { ActivateWordpressFtp, CheckService, CheckServiceDomain, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
@@ -41,6 +43,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request));
fastify.get<OnlyId>('/:id/status', async (request) => await getServiceStatus(request));
fastify.get<CheckServiceDomain>('/:id/check', async (request) => await checkServiceDomain(request));
fastify.post<CheckService>('/:id/check', async (request) => await checkService(request));
fastify.post<SaveServiceSettings>('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply));

View File

@@ -25,9 +25,16 @@ export interface SaveServiceSettings extends OnlyId {
dualCerts: boolean
}
}
export interface CheckServiceDomain extends OnlyId {
Querystring: {
domain: string
}
}
export interface CheckService extends OnlyId {
Body: {
fqdn: string,
forceSave: boolean,
dualCerts: boolean,
exposePort: number,
otherFqdns: Array<string>
}

View File

@@ -1,15 +1,24 @@
import { promises as dns } from 'dns';
import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkDomainsIsValidInDNS, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types';
import { checkDomainsIsValidInDNS, decrypt, encrypt, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
export async function listAllSettings(request: FastifyRequest) {
try {
const teamId = request.user.teamId;
const settings = await listSettings();
const sshKeys = await prisma.sshKey.findMany({ where: { team: { id: teamId } } })
const unencryptedKeys = []
if (sshKeys.length > 0) {
for (const key of sshKeys) {
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
}
}
return {
settings
settings,
sshKeys: unencryptedKeys
}
} catch ({ status, message }) {
return errorHandler({ status, message })
@@ -68,7 +77,8 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
throw "Domain already configured";
}
if (isDNSCheckEnabled && !forceSave) {
return await checkDomainsIsValidInDNS({ hostname: request.hostname.split(':')[0], fqdn, dualCerts });
const hostname = request.hostname.split(':')[0]
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
}
return {};
} catch ({ status, message }) {
@@ -83,4 +93,31 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
try {
const teamId = request.user.teamId;
const { privateKey, name } = request.body;
const found = await prisma.sshKey.findMany({ where: { name } })
if (found.length > 0) {
throw {
message: "Name already used. Choose another one please."
}
}
const encryptedSSHKey = encrypt(privateKey)
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey, team: { connect: { id: teamId } } } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function deleteSSHKey(request: FastifyRequest<DeleteSSHKey>, reply: FastifyReply) {
try {
const { id } = request.body;
await prisma.sshKey.delete({ where: { id } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@@ -1,6 +1,6 @@
import { FastifyPluginAsync } from 'fastify';
import { checkDNS, checkDomain, deleteDomain, listAllSettings, saveSettings } from './handlers';
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types';
import { checkDNS, checkDomain, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
@@ -13,6 +13,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
fastify.delete<DeleteSSHKey>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
};
export default root;

View File

@@ -28,4 +28,15 @@ export interface CheckDNS {
Params: {
domain: string,
}
}
export interface SaveSSHKey {
Body: {
privateKey: string,
name: string
}
}
export interface DeleteSSHKey {
Body: {
id: string
}
}

View File

@@ -75,18 +75,18 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
if (!allowedGithubEvents.includes(githubEvent)) {
throw { status: 500, message: 'Event not allowed.' }
}
let repository, projectId, branch;
let projectId, branch;
const body = request.body
if (githubEvent === 'push') {
repository = body.repository;
projectId = repository.id;
branch = body.ref.split('/')[2];
projectId = body.repository.id;
branch = body.ref.includes('/') ? body.ref.split('/')[2] : body.ref;
} else if (githubEvent === 'pull_request') {
repository = body.pull_request.head.repo;
projectId = repository.id;
branch = body.pull_request.head.ref.split('/')[2];
projectId = body.pull_request.base.repo.id;
branch = body.pull_request.base.ref.includes('/') ? body.pull_request.base.ref.split('/')[2] : body.pull_request.base.ref;
}
if (!projectId || !branch) {
throw { status: 500, message: 'Cannot parse projectId or branch from the webhook?!' }
}
const applicationFound = await getApplicationFromDBWebhook(projectId, branch);
if (applicationFound) {
const webhookSecret = applicationFound.gitSource.githubApp.webhookSecret || null;
@@ -154,7 +154,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
} else if (githubEvent === 'pull_request') {
const pullmergeRequestId = body.number;
const pullmergeRequestAction = body.action;
const sourceBranch = body.pull_request.head.ref;
const sourceBranch = body.pull_request.head.ref.includes('/') ? body.pull_request.head.ref.split('/')[2] : body.pull_request.head.ref;
if (!allowedActions.includes(pullmergeRequestAction)) {
throw { status: 500, message: 'Action not allowed.' }
}
@@ -162,8 +162,10 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
if (applicationFound.settings.previews) {
if (applicationFound.destinationDockerId) {
const isRunning = await checkContainer(
applicationFound.destinationDocker.engine,
applicationFound.id
{
dockerId: applicationFound.destinationDocker.id,
container: applicationFound.id
}
);
if (!isRunning) {
throw { status: 500, message: 'Application not running.' }
@@ -204,8 +206,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
} else if (pullmergeRequestAction === 'closed') {
if (applicationFound.destinationDockerId) {
const id = `${applicationFound.id}-${pullmergeRequestId}`;
const engine = applicationFound.destinationDocker.engine;
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
}
return {
message: 'Removed preview. Thank you!'

View File

@@ -8,12 +8,22 @@ export interface GitHubEvents {
Body: {
number: string,
action: string,
repository: string,
repository: {
id: string,
},
ref: string,
pull_request: {
base: {
ref: string,
repo: {
id: string,
}
},
head: {
ref: string,
repo: string
repo: {
id: string,
}
}
}
}

View File

@@ -34,7 +34,6 @@ export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLab
}
return reply.redirect(`/webhooks/success?token=${data.access_token}`)
} catch ({ status, message, ...other }) {
console.log(other)
return errorHandler({ status, message })
}
}
@@ -117,8 +116,10 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
if (applicationFound.settings.previews) {
if (applicationFound.destinationDockerId) {
const isRunning = await checkContainer(
applicationFound.destinationDocker.engine,
applicationFound.id
{
dockerId: applicationFound.destinationDocker.id,
container: applicationFound.id
}
);
if (!isRunning) {
throw { status: 500, message: 'Application not running.' }
@@ -164,7 +165,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
if (applicationFound.destinationDockerId) {
const id = `${applicationFound.id}-${pullmergeRequestId}`;
const engine = applicationFound.destinationDocker.engine;
await removeContainer({ id, engine });
await removeContainer({ id, dockerId: applicationFound.destinationDocker.id });
}
return {
message: 'Removed preview. Thank you!'

View File

@@ -1,6 +1,5 @@
import { FastifyRequest } from "fastify";
import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma, supportedServiceTypesAndVersions } from "../../../lib/common";
import { getEngine } from "../../../lib/docker";
import { errorHandler, getDomain, isDev, prisma, supportedServiceTypesAndVersions, include, executeDockerCmd } from "../../../lib/common";
import { TraefikOtherConfiguration } from "./types";
function configureMiddleware(
@@ -167,6 +166,7 @@ export async function traefikConfiguration(request, reply) {
}
};
const applications = await prisma.application.findMany({
where: { destinationDocker: { remoteEngine: false } },
include: { destinationDocker: true, settings: true }
});
const data = {
@@ -184,7 +184,7 @@ export async function traefikConfiguration(request, reply) {
settings: { previews, dualCerts }
} = application;
if (destinationDockerId) {
const { engine, network } = destinationDocker;
const { network, id: dockerId } = destinationDocker;
const isRunning = true;
if (fqdn) {
const domain = getDomain(fqdn);
@@ -205,10 +205,7 @@ export async function traefikConfiguration(request, reply) {
});
}
if (previews) {
const host = getEngine(engine);
const { stdout } = await asyncExecShell(
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"`
);
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
const containers = stdout
.trim()
.split('\n')
@@ -235,7 +232,11 @@ export async function traefikConfiguration(request, reply) {
}
}
}
const services = await listServicesWithIncludes();
const services: any = await prisma.service.findMany({
where: { destinationDocker: { remoteEngine: false } },
include,
orderBy: { createdAt: 'desc' },
});
for (const service of services) {
const {
@@ -248,7 +249,6 @@ export async function traefikConfiguration(request, reply) {
plausibleAnalytics
} = service;
if (destinationDockerId) {
const { engine } = destinationDocker;
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const port = found.ports.main;
@@ -487,4 +487,219 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
console.log(status, message);
return errorHandler({ status, message })
}
}
export async function remoteTraefikConfiguration(request: FastifyRequest) {
const { id } = request.params
try {
const traefik = {
http: {
routers: {},
services: {},
middlewares: {
'redirect-to-https': {
redirectscheme: {
scheme: 'https'
}
},
'redirect-to-http': {
redirectscheme: {
scheme: 'http'
}
},
'redirect-to-non-www': {
redirectregex: {
regex: '^https?://www\\.(.+)',
replacement: 'http://${1}'
}
},
'redirect-to-www': {
redirectregex: {
regex: '^https?://(?:www\\.)?(.+)',
replacement: 'http://www.${1}'
}
}
}
}
};
const applications = await prisma.application.findMany({
where: { destinationDocker: { id } },
include: { destinationDocker: true, settings: true }
});
const data = {
applications: [],
services: [],
coolify: []
};
for (const application of applications) {
const {
fqdn,
id,
port,
destinationDocker,
destinationDockerId,
settings: { previews, dualCerts }
} = application;
if (destinationDockerId) {
const { id: dockerId, network } = destinationDocker;
const isRunning = true;
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
data.applications.push({
id,
container: id,
port: port || 3000,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts
});
}
if (previews) {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
const containers = stdout
.trim()
.split('\n')
.filter((a) => a)
.map((c) => c.replace(/"/g, ''));
if (containers.length > 0) {
for (const container of containers) {
const previewDomain = `${container.split('-')[1]}.${domain}`;
const nakedDomain = previewDomain.replace(/^www\./, '');
data.applications.push({
id: container,
container,
port: port || 3000,
domain: previewDomain,
isRunning,
nakedDomain,
isHttps,
isWWW,
isDualCerts: dualCerts
});
}
}
}
}
}
}
const services: any = await prisma.service.findMany({
where: { destinationDocker: { id } },
include,
orderBy: { createdAt: 'desc' }
});
for (const service of services) {
const {
fqdn,
id,
type,
destinationDocker,
destinationDockerId,
dualCerts,
plausibleAnalytics
} = service;
if (destinationDockerId) {
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const port = found.ports.main;
const publicPort = service[type]?.publicPort;
const isRunning = true;
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
// Plausible Analytics custom script
let scriptName = false;
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
scriptName = plausibleAnalytics.scriptName;
}
let container = id;
let otherDomain = null;
let otherNakedDomain = null;
let otherIsHttps = null;
let otherIsWWW = null;
if (type === 'minio' && service.minio.apiFqdn) {
otherDomain = getDomain(service.minio.apiFqdn);
otherNakedDomain = otherDomain.replace(/^www\./, '');
otherIsHttps = service.minio.apiFqdn.startsWith('https://');
otherIsWWW = service.minio.apiFqdn.includes('www.');
}
data.services.push({
id,
container,
type,
otherDomain,
otherNakedDomain,
otherIsHttps,
otherIsWWW,
port,
publicPort,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts,
scriptName
});
}
}
}
}
}
for (const application of data.applications) {
configureMiddleware(application, traefik);
}
for (const service of data.services) {
const { id, scriptName } = service;
configureMiddleware(service, traefik);
if (service.type === 'minio') {
service.id = id + '-minio';
service.container = id;
service.domain = service.otherDomain;
service.nakedDomain = service.otherNakedDomain;
service.isHttps = service.otherIsHttps;
service.isWWW = service.otherIsWWW;
service.port = 9000;
configureMiddleware(service, traefik);
}
if (scriptName) {
traefik.http.middlewares[`${id}-redir`] = {
replacepathregex: {
regex: `/js/${scriptName}`,
replacement: '/js/plausible.js'
}
};
}
}
for (const coolify of data.coolify) {
configureMiddleware(coolify, traefik);
}
if (Object.keys(traefik.http.routers).length === 0) {
traefik.http.routers = null;
}
if (Object.keys(traefik.http.services).length === 0) {
traefik.http.services = null;
}
return {
...traefik
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@@ -1,10 +1,12 @@
import { FastifyPluginAsync } from 'fastify';
import { traefikConfiguration, traefikOtherConfiguration } from './handlers';
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
import { TraefikOtherConfiguration } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
fastify.get('/remote/:id', async (request) => remoteTraefikConfiguration(request));
};
export default root;

View File

@@ -36,3 +36,4 @@ export interface SaveDatabaseSettings extends OnlyId {
}