fix: verify and configure remote docker engines

This commit is contained in:
Andras Bacsai 2022-10-04 15:01:47 +02:00
parent a3af21275a
commit bd27afe0da
4 changed files with 86 additions and 45 deletions
apps
api/src
index.ts
routes/api/v1
destinations
servers
ui/src/routes/destinations/[id]

@ -10,6 +10,7 @@ import { asyncExecShell, createRemoteEngineConfiguration, getDomain, isDev, list
import { scheduler } from './lib/scheduler'; import { scheduler } from './lib/scheduler';
import { compareVersions } from 'compare-versions'; import { compareVersions } from 'compare-versions';
import Graceful from '@ladjs/graceful' import Graceful from '@ladjs/graceful'
import { verifyRemoteDockerEngineFn } from './routes/api/v1/destinations/handlers';
declare module 'fastify' { declare module 'fastify' {
interface FastifyInstance { interface FastifyInstance {
config: { config: {
@ -27,7 +28,8 @@ declare module 'fastify' {
const port = isDev ? 3001 : 3000; const port = isDev ? 3001 : 3000;
const host = '0.0.0.0'; const host = '0.0.0.0';
prisma.setting.findFirst().then(async (settings) => { (async () => {
const settings = prisma.setting.findFirst()
const fastify = Fastify({ const fastify = Fastify({
logger: settings?.isAPIDebuggingEnabled || false, logger: settings?.isAPIDebuggingEnabled || false,
trustProxy: true trustProxy: true
@ -117,11 +119,8 @@ prisma.setting.findFirst().then(async (settings) => {
// console.log('not allowed', request.headers.host) // console.log('not allowed', request.headers.host)
} }
}) })
fastify.listen({ port, host }, async (err: any, address: any) => { try {
if (err) { await fastify.listen({ port, host })
console.error(err);
process.exit(1);
}
console.log(`Coolify's API is listening on ${host}:${port}`); console.log(`Coolify's API is listening on ${host}:${port}`);
await initServer(); await initServer();
@ -162,19 +161,28 @@ prisma.setting.findFirst().then(async (settings) => {
getIPAddress(), getIPAddress(),
configureRemoteDockers(), configureRemoteDockers(),
]) ])
}); } catch (error) {
}) console.error(error);
process.exit(1);
}
})();
async function getIPAddress() { async function getIPAddress() {
const { publicIpv4, publicIpv6 } = await import('public-ip') const { publicIpv4, publicIpv6 } = await import('public-ip')
try { try {
const settings = await listSettings(); const settings = await listSettings();
if (!settings.ipv4) { if (!settings.ipv4) {
console.log(`Getting public IPv4 address...`);
const ipv4 = await publicIpv4({ timeout: 2000 }) const ipv4 = await publicIpv4({ timeout: 2000 })
await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } }) await prisma.setting.update({ where: { id: settings.id }, data: { ipv4 } })
} }
if (!settings.ipv6) { if (!settings.ipv6) {
console.log(`Getting public IPv6 address...`);
const ipv6 = await publicIpv6({ timeout: 2000 }) const ipv6 = await publicIpv6({ timeout: 2000 })
await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } }) await prisma.setting.update({ where: { id: settings.id }, data: { ipv6 } })
} }
@ -183,6 +191,7 @@ async function getIPAddress() {
} }
async function initServer() { async function initServer() {
try { try {
console.log(`Initializing server...`);
await asyncExecShell(`docker network create --attachable coolify`); await asyncExecShell(`docker network create --attachable coolify`);
} catch (error) { } } catch (error) { }
try { try {
@ -196,6 +205,7 @@ async function getArch() {
try { try {
const settings = await prisma.setting.findFirst({}) const settings = await prisma.setting.findFirst({})
if (settings && !settings.arch) { if (settings && !settings.arch) {
console.log(`Getting architecture...`);
await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } }) await prisma.setting.update({ where: { id: settings.id }, data: { arch: process.arch } })
} }
} catch (error) { } } catch (error) { }
@ -207,9 +217,13 @@ async function configureRemoteDockers() {
where: { remoteVerified: true, remoteEngine: true } where: { remoteVerified: true, remoteEngine: true }
}); });
if (remoteDocker.length > 0) { if (remoteDocker.length > 0) {
console.log(`Verifying Remote Docker Engines...`);
for (const docker of remoteDocker) { for (const docker of remoteDocker) {
await createRemoteEngineConfiguration(docker.id) console.log('Verifying:', docker.remoteIpAddress)
await verifyRemoteDockerEngineFn(docker.id);
} }
} }
} catch (error) { } } catch (error) {
console.log(error)
}
} }

@ -4,7 +4,7 @@ import sshConfig from 'ssh-config'
import fs from 'fs/promises' import fs from 'fs/promises'
import os from 'os'; import os from 'os';
import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import { asyncExecShell, createRemoteEngineConfiguration, decrypt, errorHandler, executeDockerCmd, executeSSHCmd, listSettings, prisma, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer } from '../../../../lib/docker'; import { checkContainer } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
@ -202,25 +202,44 @@ export async function assignSSHKey(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) { export async function verifyRemoteDockerEngineFn(id: string) {
await createRemoteEngineConfiguration(id);
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
const host = `ssh://${remoteUser}@${remoteIpAddress}`
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}`);
}
const { stdout: coolifyNetwork } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`);
if (!coolifyNetwork) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
}
if (isCoolifyProxyUsed) await startTraefikProxy(id);
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
try { try {
const { id } = request.params; let daemonJsonParsed = JSON.parse(daemonJson);
await createRemoteEngineConfiguration(id); if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } }) daemonJsonParsed['live-restore'] = true
const host = `ssh://${remoteUser}@${remoteIpAddress}` await executeSSHCmd({ dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`); await executeSSHCmd({ dockerId: id, command: `systemctl restart docker` });
if (!stdout) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
} }
const { stdout: coolifyNetwork } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=coolify-infra' --no-trunc --format "{{json .}}"`); } catch (error) {
if (!coolifyNetwork) { const daemonJsonParsed = {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`); "live-restore": true
} }
if (isCoolifyProxyUsed) await startTraefikProxy(id); console.log(JSON.stringify(daemonJsonParsed))
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } }) await executeSSHCmd({ dockerId: id, command: `echo '${JSON.stringify(daemonJsonParsed)}' > /etc/docker/daemon.json` });
await executeSSHCmd({ dockerId: id, command: `systemctl restart docker` });
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
}
export async function verifyRemoteDockerEngine(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
const { id } = request.params;
try {
await verifyRemoteDockerEngineFn(id);
return reply.code(201).send() return reply.code(201).send()
} catch ({ status, message }) { } catch ({ status, message }) {
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: false } })
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }

@ -8,7 +8,16 @@ export async function listServers(request: FastifyRequest) {
try { try {
const userId = request.user.userId; const userId = request.user.userId;
const teamId = request.user.teamId; const teamId = request.user.teamId;
const servers = await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } }}, distinct: ['remoteIpAddress', 'engine'] }) let servers = await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, distinct: ['remoteIpAddress', 'engine'] })
servers = servers.filter((server) => {
if (server.remoteEngine) {
if (server.remoteVerified) {
return server
}
} else {
return server
}
})
return { return {
servers servers
} }
@ -78,7 +87,7 @@ export async function showUsage(request: FastifyRequest) {
freeMemPercentage: (parsed.totalMemoryKB - parsed.usedMemoryKB) / parsed.totalMemoryKB * 100 freeMemPercentage: (parsed.totalMemoryKB - parsed.usedMemoryKB) / parsed.totalMemoryKB * 100
}, },
cpu: { cpu: {
load: [0,0,0], load: [0, 0, 0],
usage: cpuUsage, usage: cpuUsage,
count: cpus count: cpus
}, },

@ -175,24 +175,23 @@
disabled={loading.save} disabled={loading.save}
>{$t('forms.save')} >{$t('forms.save')}
</button> </button>
{#if !destination.remoteVerified} <button
<button disabled={loading.verify}
disabled={loading.verify} class="btn btn-sm"
class="btn btn-sm" class:loading={loading.verify}
class:loading={loading.verify} on:click|preventDefault|stopPropagation={verifyRemoteDocker}
on:click|preventDefault|stopPropagation={verifyRemoteDocker} >{!destination.remoteVerified
>Verify Remote Docker Engine</button ? 'Verify Remote Docker Engine'
> : 'Check Remote Docker Engine'}</button
{:else} >
<button
class="btn btn-sm" <button
class:loading={loading.restart} class="btn btn-sm"
class:bg-error={!loading.restart} class:loading={loading.restart}
disabled={loading.restart} class:bg-error={!loading.restart}
on:click|preventDefault={forceRestartProxy} disabled={loading.restart}
>{$t('destination.force_restart_proxy')}</button on:click|preventDefault={forceRestartProxy}>{$t('destination.force_restart_proxy')}</button
> >
{/if}
{/if} {/if}
</div> </div>
<div class="grid grid-cols-2 items-center px-10 "> <div class="grid grid-cols-2 items-center px-10 ">