diff --git a/package.json b/package.json index 549e5e7fe..616f39f29 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.0.21", + "version": "2.0.22", "license": "AGPL-3.0", "scripts": { "dev": "docker-compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", diff --git a/src/lib/common.ts b/src/lib/common.ts index 3ec0c923c..c36196113 100644 --- a/src/lib/common.ts +++ b/src/lib/common.ts @@ -103,9 +103,14 @@ export const getUserDetails = async (event, isAdminRequired = true) => { }; export function getEngine(engine) { - return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : `tcp://${engine}:2375`; + return engine === '/var/run/docker.sock' ? 'unix:///var/run/docker.sock' : engine; } +// export async function saveSshKey(destination) { +// return await asyncExecShell( +// `echo '${destination.sshPrivateKey}' > /tmp/id_rsa_${destination.id} && chmod 600 /tmp/id_rsa_${destination.id}` +// ); +// } export async function removeContainer(id, engine) { const host = getEngine(engine); try { diff --git a/src/lib/components/common.ts b/src/lib/components/common.ts index fd887526a..687819c6d 100644 --- a/src/lib/components/common.ts +++ b/src/lib/components/common.ts @@ -15,3 +15,6 @@ export const notNodeDeployments = ['php', 'docker', 'rust']; export function getDomain(domain) { return domain?.replace('https://', '').replace('http://', ''); } +export function generateRemoteEngine(destination) { + return `ssh://${destination.user}@${destination.ipAddress}:${destination.port}`; +} diff --git a/src/lib/components/svg/applications/Astro.svelte b/src/lib/components/svg/applications/Astro.svelte new file mode 100644 index 000000000..df8d0804f --- /dev/null +++ b/src/lib/components/svg/applications/Astro.svelte @@ -0,0 +1,21 @@ + + + + diff --git a/src/lib/components/svg/applications/Eleventy.svelte b/src/lib/components/svg/applications/Eleventy.svelte new file mode 100644 index 000000000..691a10520 --- /dev/null +++ b/src/lib/components/svg/applications/Eleventy.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index ffcafbecd..b2d7ceacd 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -119,7 +119,8 @@ export async function getApplicationWebhook({ projectId, branch }) { } export async function getApplicationById({ id }) { const body = await prisma.application.findFirst({ - where: { id } + where: { id }, + include: { destinationDocker: true } }); return { ...body }; diff --git a/src/lib/database/destinations.ts b/src/lib/database/destinations.ts index b566becb4..3f3aadec5 100644 --- a/src/lib/database/destinations.ts +++ b/src/lib/database/destinations.ts @@ -1,4 +1,5 @@ import { asyncExecShell, getEngine } from '$lib/common'; +import { decrypt, encrypt } from '$lib/crypto'; import { dockerInstance } from '$lib/docker'; import { startCoolifyProxy } from '$lib/haproxy'; import { getDatabaseImage } from '.'; @@ -47,7 +48,36 @@ export async function updateDestination({ id, name, engine, network }) { return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } }); } -export async function newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) { +export async function newRemoteDestination({ + name, + teamId, + engine, + network, + isCoolifyProxyUsed, + remoteEngine, + ipAddress, + user, + port, + sshPrivateKey +}) { + const encryptedPrivateKey = encrypt(sshPrivateKey); + const destination = await prisma.destinationDocker.create({ + data: { + name, + teams: { connect: { id: teamId } }, + engine, + network, + isCoolifyProxyUsed, + remoteEngine, + ipAddress, + user, + port, + sshPrivateKey: encryptedPrivateKey + } + }); + return destination.id; +} +export async function newLocalDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) { const host = getEngine(engine); const docker = dockerInstance({ destinationDocker: { engine, network } }); const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } }); @@ -94,9 +124,13 @@ export async function removeDestination({ id }) { } export async function getDestination({ id, teamId }) { - return await prisma.destinationDocker.findFirst({ + let destination = await prisma.destinationDocker.findFirst({ where: { id, teams: { some: { id: teamId } } } }); + if (destination.remoteEngine) { + destination.sshPrivateKey = decrypt(destination.sshPrivateKey); + } + return destination; } export async function getDestinationByApplicationId({ id, teamId }) { return await prisma.destinationDocker.findFirst({ diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 4e14be487..09d7badd7 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -187,6 +187,59 @@ export async function reloadHaproxy(engine) { const host = getEngine(engine); return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`); } +export async function checkProxyConfigurations() { + const haproxy = await haproxyInstance(); + await checkHAProxy(haproxy); + try { + const stats: any = await haproxy.get(`v2/services/haproxy/stats/native`).json(); + for (const stat of stats[0].stats) { + if (stat.stats.status === 'DOWN' && stat.type === 'server') { + const { + name, + backend_name: backendName, + stats: { lastchg } + } = stat; + const application = await db.getApplicationById(name); + if (!application) { + const transactionId = await getNextTransactionId(); + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${backendName}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + return await completeTransaction(transactionId); + } + const found = await checkContainer(application.destinationDocker.engine, name); + if (!found) { + const transactionId = await getNextTransactionId(); + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${backendName}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + return await completeTransaction(transactionId); + } + if (lastchg > 120) { + const transactionId = await getNextTransactionId(); + await haproxy + .delete(`v2/services/haproxy/configuration/backends/${backendName}`, { + searchParams: { + transaction_id: transactionId + } + }) + .json(); + await completeTransaction(transactionId); + } + } + } + } catch (error) { + console.log(error); + } +} export async function configureProxyForApplication({ domain, imageId, applicationId, port }) { const haproxy = await haproxyInstance(); await checkHAProxy(haproxy); diff --git a/src/lib/queues/builder.ts b/src/lib/queues/builder.ts index c145619be..2bff0ba83 100644 --- a/src/lib/queues/builder.ts +++ b/src/lib/queues/builder.ts @@ -4,7 +4,12 @@ import * as buildpacks from '../buildPacks'; import * as importers from '../importers'; import { dockerInstance } from '../docker'; import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common'; -import { configureProxyForApplication, reloadHaproxy, setWwwRedirection } from '../haproxy'; +import { + checkProxyConfigurations, + configureProxyForApplication, + reloadHaproxy, + setWwwRedirection +} from '../haproxy'; import * as db from '$lib/database'; import { decrypt } from '$lib/crypto'; import { sentry } from '$lib/common'; @@ -253,6 +258,7 @@ export default async function (job) { try { if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) { saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId }); + await checkProxyConfigurations(); await configureProxyForApplication({ domain, imageId, applicationId, port }); if (isHttps) await letsEncrypt({ domain, id: applicationId }); await setWwwRedirection(fqdn); diff --git a/src/lib/queues/proxy.ts b/src/lib/queues/proxy.ts index 5f7a8a189..074ac55ab 100644 --- a/src/lib/queues/proxy.ts +++ b/src/lib/queues/proxy.ts @@ -3,6 +3,7 @@ import { getApplicationById, prisma, supportedServiceTypesAndVersions } from '$l import { dockerInstance } from '$lib/docker'; import { checkContainer, + checkProxyConfigurations, configureCoolifyProxyOn, configureProxyForApplication, configureSimpleServiceProxyOn, @@ -13,13 +14,22 @@ import { startHttpProxy } from '$lib/haproxy'; import * as db from '$lib/database'; +// import { generateRemoteEngine } from '$lib/components/common'; export default async function () { + try { + await checkProxyConfigurations(); + } catch (error) { + console.log(error); + } try { // Check destination containers and configure proxy if needed const destinationDockers = await prisma.destinationDocker.findMany({}); for (const destination of destinationDockers) { if (destination.isCoolifyProxyUsed) { + // if (destination.remoteEngine) { + // const engine = generateRemoteEngine(destination); + // } const docker = dockerInstance({ destinationDocker: destination }); const containers = await docker.engine.listContainers(); const configurations = containers.filter( diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index 093f81d73..eee6acc52 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -30,7 +30,6 @@ @@ -45,6 +47,10 @@ {:else if buildPack === 'docker'} + {:else if buildPack === 'astro'} + + {:else if buildPack === 'eleventy'} + {/if}
{application.name}
diff --git a/src/routes/destinations/[id]/_LocalDocker.svelte b/src/routes/destinations/[id]/_LocalDocker.svelte index 76a36787a..287f544ef 100644 --- a/src/routes/destinations/[id]/_LocalDocker.svelte +++ b/src/routes/destinations/[id]/_LocalDocker.svelte @@ -4,7 +4,7 @@ export let state; import { toast } from '@zerodevx/svelte-toast'; - import { page } from '$app/stores'; + import { page, session } from '$app/stores'; import Setting from '$lib/components/Setting.svelte'; import { errorNotification } from '$lib/form'; import { post } from '$lib/api'; @@ -125,27 +125,35 @@
Configuration
- - + {#if $session.isAdmin} + + + {/if}
- +
diff --git a/src/routes/destinations/[id]/_RemoteDocker.svelte b/src/routes/destinations/[id]/_RemoteDocker.svelte new file mode 100644 index 000000000..9c13cf465 --- /dev/null +++ b/src/routes/destinations/[id]/_RemoteDocker.svelte @@ -0,0 +1,225 @@ + + + +
+
Configuration
+ {#if $session.isAdmin} + + + {/if} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
${ + cannotDisable + ? 'You cannot disable this proxy as FQDN is configured for Coolify.' + : '' + }`} + /> +
+ + + + diff --git a/src/routes/destinations/[id]/index.json.ts b/src/routes/destinations/[id]/index.json.ts index 9d8804a67..e88c29825 100644 --- a/src/routes/destinations/[id]/index.json.ts +++ b/src/routes/destinations/[id]/index.json.ts @@ -1,4 +1,5 @@ -import { asyncExecShell, getEngine, getTeam, getUserDetails } from '$lib/common'; +import { asyncExecShell, getUserDetails } from '$lib/common'; +import { generateRemoteEngine } from '$lib/components/common'; import * as db from '$lib/database'; import { ErrorHandler } from '$lib/database'; import { checkContainer } from '$lib/haproxy'; @@ -12,15 +13,26 @@ export const get: RequestHandler = async (event) => { try { const destination = await db.getDestination({ id, teamId }); const settings = await db.listSettings(); - const state = - destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy')); + let payload = { + destination, + settings, + state: false + }; + 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 { + payload.state = + destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy')); + } return { status: 200, - body: { - destination, - settings, - state - } + body: { ...payload } }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/destinations/[id]/index.svelte b/src/routes/destinations/[id]/index.svelte index a5b2996c8..68f529b54 100644 --- a/src/routes/destinations/[id]/index.svelte +++ b/src/routes/destinations/[id]/index.svelte @@ -35,6 +35,7 @@ import type Prisma from '@prisma/client'; import LocalDocker from './_LocalDocker.svelte'; + import RemoteDocker from './_RemoteDocker.svelte';
@@ -42,6 +43,11 @@ > {destination.name}
+
- + {#if destination.remoteEngine} + + {:else} + + {/if}
diff --git a/src/routes/new/destination/_Docker.svelte b/src/routes/new/destination/_LocalDocker.svelte similarity index 75% rename from src/routes/new/destination/_Docker.svelte rename to src/routes/new/destination/_LocalDocker.svelte index 47a070049..79a2c81b0 100644 --- a/src/routes/new/destination/_Docker.svelte +++ b/src/routes/new/destination/_LocalDocker.svelte @@ -51,26 +51,7 @@ placeholder="eg: /var/run/docker.sock" bind:value={payload.engine} /> -
-
diff --git a/src/routes/new/destination/_RemoteDocker.svelte b/src/routes/new/destination/_RemoteDocker.svelte new file mode 100644 index 000000000..780c66019 --- /dev/null +++ b/src/routes/new/destination/_RemoteDocker.svelte @@ -0,0 +1,90 @@ + + +
+
+
+
Configuration
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +