feat: remote docker engine init
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
"dependencies": {
|
||||
"@breejs/ts-worker": "2.0.0",
|
||||
"@fastify/autoload": "5.1.0",
|
||||
"@fastify/cookie": "7.1.0",
|
||||
"@fastify/cookie": "7.2.0",
|
||||
"@fastify/cors": "8.0.0",
|
||||
"@fastify/env": "4.0.0",
|
||||
"@fastify/jwt": "6.3.1",
|
||||
@@ -43,16 +43,17 @@
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
"p-queue": "7.2.0",
|
||||
"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.0.6",
|
||||
"@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",
|
||||
"eslint": "8.20.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"nodemon": "2.0.19",
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1,36 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "SshKey" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"privateKey" TEXT NOT NULL,
|
||||
"destinationDockerId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "SshKey_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- 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
|
||||
);
|
||||
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;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "SshKey_destinationDockerId_key" ON "SshKey"("destinationDockerId");
|
||||
@@ -200,8 +200,12 @@ 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[]
|
||||
@@ -209,6 +213,17 @@ model DestinationDocker {
|
||||
updatedAt DateTime @updatedAt
|
||||
database Database[]
|
||||
service Service[]
|
||||
sshKey SshKey?
|
||||
}
|
||||
|
||||
model SshKey {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
privateKey String
|
||||
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
|
||||
destinationDockerId String? @unique
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model GitSource {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { FastifyReply } from 'fastify';
|
||||
import { asyncExecShell, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||
import sshConfig from 'ssh-config'
|
||||
import fs from 'fs/promises'
|
||||
import { asyncExecShell, decrypt, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
|
||||
import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
@@ -44,7 +46,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.` };
|
||||
@@ -80,39 +83,51 @@ 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
|
||||
let { name, network, engine, isCoolifyProxyUsed, ipAddress, user, port, sshPrivateKey } = 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);
|
||||
if (engine) {
|
||||
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);
|
||||
|
||||
if (destinations.length > 0) {
|
||||
const proxyConfigured = destinations.find(
|
||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||
);
|
||||
if (proxyConfigured) {
|
||||
isCoolifyProxyUsed = !!proxyConfigured.isCoolifyProxyUsed;
|
||||
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 } });
|
||||
}
|
||||
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);
|
||||
if (isCoolifyProxyUsed) {
|
||||
const settings = await prisma.setting.findFirst();
|
||||
if (settings?.isTraefikUsed) {
|
||||
await startTraefikProxy(engine);
|
||||
} else {
|
||||
await startCoolifyProxy(engine);
|
||||
}
|
||||
}
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
}
|
||||
return reply.code(201).send({ id: destination.id });
|
||||
if (ipAddress) {
|
||||
await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress: ipAddress, remoteUser: user, remotePort: port }
|
||||
});
|
||||
return reply.code(201).send()
|
||||
|
||||
}
|
||||
throw {
|
||||
message: `Cannot save Docker Engine.`
|
||||
};
|
||||
} else {
|
||||
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||
return reply.code(201).send();
|
||||
@@ -120,6 +135,8 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
|
||||
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
} finally {
|
||||
await fs.rm('./id_rsa')
|
||||
}
|
||||
}
|
||||
export async function deleteDestination(request: FastifyRequest<OnlyId>) {
|
||||
@@ -195,3 +212,45 @@ export async function restartProxy(request: FastifyRequest<Proxy>) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
}
|
||||
|
||||
export async function assignSSHKey(request: FastifyRequest) {
|
||||
try {
|
||||
const { id: sshKeyId } = request.body;
|
||||
const { id } = request.params;
|
||||
console.log({ id, sshKeyId })
|
||||
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 { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } })
|
||||
await fs.writeFile('./id_rsa', 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: '/workspace/coolify/apps/api/id_rsa',
|
||||
StrictHostKeyChecking: 'no'
|
||||
})
|
||||
}
|
||||
await fs.writeFile('/home/gitpod/.ssh/config', sshConfig.stringify(config))
|
||||
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
|
||||
console.log({ stdout })
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy } from './handlers';
|
||||
import { assignSSHKey, checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
|
||||
|
||||
import type { OnlyId } from '../../../../types';
|
||||
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
|
||||
@@ -15,10 +15,14 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
|
||||
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(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;
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
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 settings = await listSettings();
|
||||
const sshKeys = await prisma.sshKey.findMany()
|
||||
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 })
|
||||
@@ -83,4 +91,30 @@ 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 { 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 } })
|
||||
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 })
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -28,4 +28,15 @@ export interface CheckDNS {
|
||||
Params: {
|
||||
domain: string,
|
||||
}
|
||||
}
|
||||
export interface SaveSSHKey {
|
||||
Body: {
|
||||
privateKey: string,
|
||||
name: string
|
||||
}
|
||||
}
|
||||
export interface DeleteSSHKey {
|
||||
Body: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user