WIP: Traefik

This commit is contained in:
Andras Bacsai 2022-05-12 16:53:22 +02:00
parent c095cb58b3
commit ae5d90eb47
22 changed files with 721 additions and 216 deletions

View File

@ -0,0 +1,23 @@
version: '3.5'
services:
${ID}:
container_name: proxy-for-${PORT}
image: traefik:v2.6
command:
- --api.insecure=true
- --entrypoints.web.address=:${PORT}
- --providers.docker=false
- --providers.docker.exposedbydefault=false
- --providers.http.endpoint=http://host.docker.internal:3000/traefik.json?id=${ID}
- --providers.http.pollTimeout=5s
- --log.level=error
ports:
- '${PORT}:${PORT}'
networks:
- ${NETWORK}
networks:
net:
external: false
name: ${NETWORK}

View File

@ -6,6 +6,7 @@ services:
command: command:
- --api.insecure=true - --api.insecure=true
- --entrypoints.web.address=:80 - --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --providers.docker=false - --providers.docker=false
- --providers.docker.exposedbydefault=false - --providers.docker.exposedbydefault=false
- --providers.http.endpoint=http://host.docker.internal:3000/traefik.json - --providers.http.endpoint=http://host.docker.internal:3000/traefik.json

View File

@ -1,24 +0,0 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Setting" (
"id" TEXT NOT NULL PRIMARY KEY,
"fqdn" TEXT,
"isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false,
"dualCerts" BOOLEAN NOT NULL DEFAULT false,
"minPort" INTEGER NOT NULL DEFAULT 9000,
"maxPort" INTEGER NOT NULL DEFAULT 9100,
"proxyPassword" TEXT NOT NULL,
"proxyUser" TEXT NOT NULL,
"proxyHash" TEXT,
"isAutoUpdateEnabled" BOOLEAN NOT NULL DEFAULT false,
"isDNSCheckEnabled" BOOLEAN NOT NULL DEFAULT true,
"disableHaproxy" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Setting" ("createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "dualCerts", "fqdn", "id", "isAutoUpdateEnabled", "isDNSCheckEnabled", "isRegistrationEnabled", "maxPort", "minPort", "proxyHash", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting";
DROP TABLE "Setting";
ALTER TABLE "new_Setting" RENAME TO "Setting";
CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -20,7 +20,7 @@ model Setting {
proxyHash String? proxyHash String?
isAutoUpdateEnabled Boolean @default(false) isAutoUpdateEnabled Boolean @default(false)
isDNSCheckEnabled Boolean @default(true) isDNSCheckEnabled Boolean @default(true)
disableHaproxy Boolean @default(false) isTraefikUsed Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }

View File

@ -1,6 +1,6 @@
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { dockerInstance } from '$lib/docker'; import { dockerInstance } from '$lib/docker';
import { startCoolifyProxy } from '$lib/haproxy'; import { startCoolifyProxy, startTraefikProxy } from '$lib/haproxy';
import { getDatabaseImage } from '.'; import { getDatabaseImage } from '.';
import { prisma } from './common'; import { prisma } from './common';
import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client'; import type { DestinationDocker, Service, Application, Prisma } from '@prisma/client';
@ -125,7 +125,14 @@ export async function newLocalDestination({
} }
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } }); await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
} }
if (isCoolifyProxyUsed) await startCoolifyProxy(engine); if (isCoolifyProxyUsed) {
const settings = await prisma.setting.findFirst();
if (settings?.isTraefikUsed) {
await startTraefikProxy(engine);
} else {
await startCoolifyProxy(engine);
}
}
return destination.id; return destination.id;
} }
export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> { export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>): Promise<void> {
@ -133,12 +140,14 @@ export async function removeDestination({ id }: Pick<DestinationDocker, 'id'>):
if (destination.isCoolifyProxyUsed) { if (destination.isCoolifyProxyUsed) {
const host = getEngine(destination.engine); const host = getEngine(destination.engine);
const { network } = destination; const { network } = destination;
const settings = await prisma.setting.findFirst();
const containerName = settings.isTraefikUsed ? 'coolify-proxy' : 'coolify-haproxy';
const { stdout: found } = await asyncExecShell( const { stdout: found } = await asyncExecShell(
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'` `DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=${containerName} --format '{{.}}'`
); );
if (found) { if (found) {
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy` `DOCKER_HOST="${host}" docker network disconnect ${network} ${containerName}`
); );
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`); await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
} }

View File

@ -3,12 +3,17 @@ import { asyncExecShell, getEngine } from '$lib/common';
import got, { type Got, type Response } from 'got'; import got, { type Got, type Response } from 'got';
import * as db from '$lib/database'; import * as db from '$lib/database';
import type { DestinationDocker } from '@prisma/client'; import type { DestinationDocker } from '@prisma/client';
import fs from 'fs/promises';
import yaml from 'js-yaml';
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImage = `coolify-haproxy-alpine:latest`;
export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`;
export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`; export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`;
export const defaultTraefikImage = `traefik:v2.6`;
const coolifyEndpoint = dev
? 'http://host.docker.internal:3000/traefik.json'
: 'http://coolify:3000/traefik.json';
export async function haproxyInstance(): Promise<Got> { export async function haproxyInstance(): Promise<Got> {
const { proxyPassword } = await db.listSettings(); const { proxyPassword } = await db.listSettings();
@ -99,11 +104,17 @@ export async function checkHAProxy(haproxy?: Got): Promise<void> {
export async function stopTcpHttpProxy( export async function stopTcpHttpProxy(
destinationDocker: DestinationDocker, destinationDocker: DestinationDocker,
publicPort: number publicPort: number,
forceName: string = null
): Promise<{ stdout: string; stderr: string } | Error> { ): Promise<{ stdout: string; stderr: string } | Error> {
const { engine } = destinationDocker; const { engine } = destinationDocker;
const host = getEngine(engine); const host = getEngine(engine);
const containerName = `haproxy-for-${publicPort}`; const settings = await db.listSettings();
let containerName = `proxy-for-${publicPort}`;
if (!settings.isTraefikUsed) {
containerName = `haproxy-for-${publicPort}`;
}
if (forceName) containerName = forceName;
const found = await checkContainer(engine, containerName); const found = await checkContainer(engine, containerName);
try { try {
if (found) { if (found) {
@ -115,6 +126,67 @@ export async function stopTcpHttpProxy(
return error; return error;
} }
} }
export async function startTraefikTCPProxy(
destinationDocker: DestinationDocker,
id: string,
publicPort: number,
privatePort: number,
volume?: string
): Promise<{ stdout: string; stderr: string } | Error> {
const { network, engine } = destinationDocker;
const host = getEngine(engine);
const containerName = `proxy-for-${publicPort}`;
const found = await checkContainer(engine, containerName, true);
const foundDependentContainer = await checkContainer(engine, id, true);
try {
if (foundDependentContainer && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
const tcpProxy = {
version: '3.5',
services: {
[id]: {
container_name: `proxy-for-${publicPort}`,
image: 'traefik:v2.6',
command: [
`--entrypoints.tcp.address=:${publicPort}`,
`--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`,
'--providers.http.pollTimeout=2s',
'--log.level=debug'
],
ports: [`${publicPort}:${publicPort}`],
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`],
volumes: ['/var/run/docker.sock:/var/run/docker.sock'],
networks: [network]
}
},
networks: {
[network]: {
external: false,
name: network
}
}
};
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d`
);
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
}
if (!foundDependentContainer && found) {
return await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
);
}
} catch (error) {
console.log(error);
return error;
}
}
export async function startTcpProxy( export async function startTcpProxy(
destinationDocker: DestinationDocker, destinationDocker: DestinationDocker,
id: string, id: string,
@ -151,6 +223,65 @@ export async function startTcpProxy(
} }
} }
export async function startTraefikHTTPProxy(
destinationDocker: DestinationDocker,
id: string,
publicPort: number,
privatePort: number
): Promise<{ stdout: string; stderr: string } | Error> {
const { network, engine } = destinationDocker;
const host = getEngine(engine);
const containerName = `proxy-for-${publicPort}`;
const found = await checkContainer(engine, containerName, true);
const foundDependentContainer = await checkContainer(engine, id, true);
try {
if (foundDependentContainer && !found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
const tcpProxy = {
version: '3.5',
services: {
[id]: {
container_name: `proxy-for-${publicPort}`,
image: 'traefik:v2.6',
command: [
`--entrypoints.http.address=:${publicPort}`,
`--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`,
'--providers.http.pollTimeout=2s',
'--log.level=debug'
],
ports: [`${publicPort}:${publicPort}`],
extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal:${ip}`],
volumes: ['/var/run/docker.sock:/var/run/docker.sock'],
networks: [network]
}
},
networks: {
[network]: {
external: false,
name: network
}
}
};
await fs.writeFile(`/tmp/docker-compose-${id}.yaml`, yaml.dump(tcpProxy));
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f /tmp/docker-compose-${id}.yaml up -d`
);
await fs.rm(`/tmp/docker-compose-${id}.yaml`);
}
if (!foundDependentContainer && found) {
return await asyncExecShell(
`DOCKER_HOST=${host} docker stop -t 0 ${containerName} && docker rm ${containerName}`
);
}
} catch (error) {
return error;
}
}
export async function startHttpProxy( export async function startHttpProxy(
destinationDocker: DestinationDocker, destinationDocker: DestinationDocker,
id: string, id: string,
@ -197,10 +328,29 @@ export async function startCoolifyProxy(engine: string): Promise<void> {
`DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}` `DOCKER_HOST="${host}" docker run -e HAPROXY_USERNAME=${proxyUser} -e HAPROXY_PASSWORD=${proxyPassword} --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl --network coolify-infra -p "80:80" -p "443:443" -p "8404:8404" -p "5555:5555" -p "5000:5000" --name coolify-haproxy -d coollabsio/${defaultProxyImage}`
); );
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } }); await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
} }
await configureNetworkCoolifyProxy(engine); await configureNetworkCoolifyProxy(engine);
} }
export async function startTraefikProxy(engine: string): Promise<void> {
const host = getEngine(engine);
const found = await checkContainer(engine, 'coolify-proxy', true);
const { id } = await db.listSettings();
if (!found) {
const { stdout: Config } = await asyncExecShell(
`DOCKER_HOST="${host}" docker network inspect bridge --format '{{json .IPAM.Config }}'`
);
const ip = JSON.parse(Config)[0].Gateway;
await asyncExecShell(
`DOCKER_HOST="${host}" docker run --restart always --add-host 'host.docker.internal:host-gateway' --add-host 'host.docker.internal:${ip}' -v coolify-ssl-certs:/usr/local/etc/haproxy/ssl -v /var/run/docker.sock:/var/run/docker.sock --network coolify-infra -p "80:80" -p "443:443" -p "8080:8080" --name coolify-proxy -d ${defaultTraefikImage} --api.insecure=true --entrypoints.web.address=:80 --entrypoints.websecure.address=:443 --providers.docker=false --providers.docker.exposedbydefault=false --providers.http.endpoint=${coolifyEndpoint} --providers.http.pollTimeout=5s --log.level=error`
);
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
}
await configureNetworkTraefikProxy(engine);
}
export async function isContainerExited(engine: string, containerName: string): Promise<boolean> { export async function isContainerExited(engine: string, containerName: string): Promise<boolean> {
let isExited = false; let isExited = false;
const host = getEngine(engine); const host = getEngine(engine);
@ -263,6 +413,24 @@ export async function stopCoolifyProxy(
return error; return error;
} }
} }
export async function stopTraefikProxy(
engine: string
): Promise<{ stdout: string; stderr: string } | Error> {
const host = getEngine(engine);
const found = await checkContainer(engine, 'coolify-proxy');
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: false });
const { id } = await db.prisma.setting.findFirst({});
await db.prisma.setting.update({ where: { id }, data: { proxyHash: null } });
try {
if (found) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker stop -t 0 coolify-proxy && docker rm coolify-proxy`
);
}
} catch (error) {
return error;
}
}
export async function configureNetworkCoolifyProxy(engine: string): Promise<void> { export async function configureNetworkCoolifyProxy(engine: string): Promise<void> {
const host = getEngine(engine); const host = getEngine(engine);
@ -279,3 +447,19 @@ export async function configureNetworkCoolifyProxy(engine: string): Promise<void
} }
} }
} }
export async function configureNetworkTraefikProxy(engine: string): Promise<void> {
const host = getEngine(engine);
const destinations = await db.prisma.destinationDocker.findMany({ where: { engine } });
const { stdout: networks } = await asyncExecShell(
`DOCKER_HOST="${host}" docker ps -a --filter name=coolify-proxy --format '{{json .Networks}}'`
);
const configuredNetworks = networks.replace(/"/g, '').replace('\n', '').split(',');
for (const destination of destinations) {
if (!configuredNetworks.includes(destination.network)) {
await asyncExecShell(
`DOCKER_HOST="${host}" docker network connect ${destination.network} coolify-proxy`
);
}
}
}

View File

@ -1,4 +1,4 @@
import { ErrorHandler } from '$lib/database'; import { ErrorHandler, prisma } from '$lib/database';
import { configureHAProxy } from '$lib/haproxy/configuration'; import { configureHAProxy } from '$lib/haproxy/configuration';
export default async function (): Promise<void | { export default async function (): Promise<void | {
@ -6,7 +6,10 @@ export default async function (): Promise<void | {
body: { message: string; error: string }; body: { message: string; error: string };
}> { }> {
try { try {
return await configureHAProxy(); const settings = await prisma.setting.findFirst();
if (!settings.isTraefikUsed) {
return await configureHAProxy();
}
} catch (error) { } catch (error) {
return ErrorHandler(error.response?.body || error); return ErrorHandler(error.response?.body || error);
} }

View File

@ -1,5 +1,13 @@
import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database'; import { ErrorHandler, generateDatabaseConfiguration, prisma } from '$lib/database';
import { startCoolifyProxy, startHttpProxy, startTcpProxy } from '$lib/haproxy'; import {
startCoolifyProxy,
startHttpProxy,
startTcpProxy,
startTraefikHTTPProxy,
startTraefikProxy,
startTraefikTCPProxy,
stopTcpHttpProxy
} from '$lib/haproxy';
export default async function (): Promise<void | { export default async function (): Promise<void | {
status: number; status: number;
@ -11,11 +19,14 @@ export default async function (): Promise<void | {
const localDocker = await prisma.destinationDocker.findFirst({ const localDocker = await prisma.destinationDocker.findFirst({
where: { engine: '/var/run/docker.sock' } where: { engine: '/var/run/docker.sock' }
}); });
console.log(settings.disableHaproxy); if (localDocker && localDocker.isCoolifyProxyUsed) {
if (localDocker && localDocker.isCoolifyProxyUsed && !settings.disableHaproxy) { if (settings.isTraefikUsed) {
console.log('asd'); await startTraefikProxy('/var/run/docker.sock');
await startCoolifyProxy('/var/run/docker.sock'); } else {
await startCoolifyProxy('/var/run/docker.sock');
}
} }
// TCP Proxies // TCP Proxies
const databasesWithPublicPort = await prisma.database.findMany({ const databasesWithPublicPort = await prisma.database.findMany({
where: { publicPort: { not: null } }, where: { publicPort: { not: null } },
@ -24,8 +35,16 @@ export default async function (): Promise<void | {
for (const database of databasesWithPublicPort) { for (const database of databasesWithPublicPort) {
const { destinationDockerId, destinationDocker, publicPort, id } = database; const { destinationDockerId, destinationDocker, publicPort, id } = database;
if (destinationDockerId) { if (destinationDockerId) {
const { privatePort } = generateDatabaseConfiguration(database); if (destinationDocker.isCoolifyProxyUsed) {
await startTcpProxy(destinationDocker, id, publicPort, privatePort); const { privatePort } = generateDatabaseConfiguration(database);
if (settings.isTraefikUsed) {
await stopTcpHttpProxy(destinationDocker, publicPort, `haproxy-for-${publicPort}`);
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
} else {
await stopTcpHttpProxy(destinationDocker, publicPort, `proxy-for-${publicPort}`);
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
}
}
} }
} }
const wordpressWithFtp = await prisma.wordpress.findMany({ const wordpressWithFtp = await prisma.wordpress.findMany({
@ -36,7 +55,15 @@ export default async function (): Promise<void | {
const { service, ftpPublicPort } = ftp; const { service, ftpPublicPort } = ftp;
const { destinationDockerId, destinationDocker, id } = service; const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId) { if (destinationDockerId) {
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22); if (destinationDocker.isCoolifyProxyUsed) {
if (settings.isTraefikUsed) {
await stopTcpHttpProxy(destinationDocker, ftpPublicPort, `${id}-ftp`);
await startTraefikTCPProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
} else {
await stopTcpHttpProxy(destinationDocker, ftpPublicPort, `${id}-ftp`);
await startTcpProxy(destinationDocker, `${id}-ftp`, ftpPublicPort, 22);
}
}
} }
} }
@ -49,7 +76,15 @@ export default async function (): Promise<void | {
const { service, publicPort } = minio; const { service, publicPort } = minio;
const { destinationDockerId, destinationDocker, id } = service; const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId) { if (destinationDockerId) {
await startHttpProxy(destinationDocker, id, publicPort, 9000); if (destinationDocker.isCoolifyProxyUsed) {
if (settings.isTraefikUsed) {
await stopTcpHttpProxy(destinationDocker, publicPort, `haproxy-for-${publicPort}`);
await startTraefikHTTPProxy(destinationDocker, id, publicPort, 9000);
} else {
await stopTcpHttpProxy(destinationDocker, publicPort, `proxy-for-${publicPort}`);
await startHttpProxy(destinationDocker, id, publicPort, 9000);
}
}
} }
} }
} catch (error) { } catch (error) {

View File

@ -12,3 +12,5 @@ export const features: Readable<{ latestVersion: string; beta: boolean }> = read
beta: browser && window.localStorage.getItem('beta') === 'true', beta: browser && window.localStorage.getItem('beta') === 'true',
latestVersion: browser && window.localStorage.getItem('latestVersion') latestVersion: browser && window.localStorage.getItem('latestVersion')
}); });
export const isTraefikUsed: Writable<boolean> = writable(false);

View File

@ -34,6 +34,7 @@
</script> </script>
<script> <script>
export let settings;
import '../tailwind.css'; import '../tailwind.css';
import { SvelteToast, toast } from '@zerodevx/svelte-toast'; import { SvelteToast, toast } from '@zerodevx/svelte-toast';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
@ -42,9 +43,11 @@
import { asyncSleep } from '$lib/components/common'; import { asyncSleep } from '$lib/components/common';
import { del, get, post } from '$lib/api'; import { del, get, post } from '$lib/api';
import { dev } from '$app/env'; import { dev } from '$app/env';
import { features } from '$lib/store'; import { features, isTraefikUsed } from '$lib/store';
let isUpdateAvailable = false;
$isTraefikUsed = settings?.isTraefikUsed || false;
let isUpdateAvailable = false;
let updateStatus = { let updateStatus = {
found: false, found: false,
loading: false, loading: false,
@ -124,13 +127,6 @@
return errorNotification(error); return errorNotification(error);
} }
} }
async function migrateToTraefik() {
try {
await post(`/update.json`, { type: 'migrateToTraefik' });
} catch ({ error }) {
return errorNotification(error);
}
}
</script> </script>
<svelte:head> <svelte:head>
@ -522,13 +518,15 @@
>Powered by <a href="https://coolify.io" target="_blank">Coolify</a></span >Powered by <a href="https://coolify.io" target="_blank">Coolify</a></span
> >
{/if} {/if}
<span class="fixed bottom-[20px] right-[10px] z-50 m-2 px-4 text-xs "> {#if !$isTraefikUsed}
<button on:click={migrateToTraefik} <span class="fixed bottom-[20px] right-[10px] z-50 m-2 px-4 text-xs ">
>New proxy is available! <br />It is based on Traefik! Why?<br /><br />Haproxy uses a lots of <a href="/settings"
unnecessary memory. The update will cause a small interruption, but everything should go back ><button class="bg-coollabs hover:bg-coollabs-100"
to normal in a few seconds!</button >New proxy is available! <br />Click here to get more details</button
> ></a
</span> >
</span>
{/if}
{/if} {/if}
<main> <main>
<slot /> <slot />

View File

@ -49,6 +49,7 @@ export const get: RequestHandler = async (event) => {
where: { userId }, where: { userId },
include: { team: { include: { _count: { select: { users: true } } } } } include: { team: { include: { _count: { select: { users: true } } } } }
}); });
const settings = await db.prisma.setting.findFirst();
return { return {
body: { body: {
teams, teams,
@ -57,7 +58,8 @@ export const get: RequestHandler = async (event) => {
destinationsCount, destinationsCount,
teamsCount, teamsCount,
databasesCount, databasesCount,
servicesCount servicesCount,
settings
} }
}; };
} catch (error) { } catch (error) {

View File

@ -1,7 +1,7 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database'; import { generateDatabaseConfiguration, ErrorHandler, getFreePort } from '$lib/database';
import { startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; import { startTcpProxy, startTraefikTCPProxy, stopTcpHttpProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@ -13,6 +13,7 @@ export const post: RequestHandler = async (event) => {
const publicPort = await getFreePort(); const publicPort = await getFreePort();
try { try {
const settings = await db.listSettings();
await db.setDatabase({ id, isPublic, appendOnly }); await db.setDatabase({ id, isPublic, appendOnly });
const database = await db.getDatabase({ id, teamId }); const database = await db.getDatabase({ id, teamId });
const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database; const { destinationDockerId, destinationDocker, publicPort: oldPublicPort } = database;
@ -21,7 +22,11 @@ export const post: RequestHandler = async (event) => {
if (destinationDockerId) { if (destinationDockerId) {
if (isPublic) { if (isPublic) {
await db.prisma.database.update({ where: { id }, data: { publicPort } }); await db.prisma.database.update({ where: { id }, data: { publicPort } });
await startTcpProxy(destinationDocker, id, publicPort, privatePort); if (settings.isTraefikUsed) {
await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
} else {
await startTcpProxy(destinationDocker, id, publicPort, privatePort);
}
} else { } else {
await db.prisma.database.update({ where: { id }, data: { publicPort: null } }); await db.prisma.database.update({ where: { id }, data: { publicPort: null } });
await stopTcpHttpProxy(destinationDocker, oldPublicPort); await stopTcpHttpProxy(destinationDocker, oldPublicPort);

View File

@ -26,8 +26,12 @@ export const get: RequestHandler = async (event) => {
// // await saveSshKey(destination); // // await saveSshKey(destination);
// payload.state = await checkContainer(engine, 'coolify-haproxy'); // payload.state = await checkContainer(engine, 'coolify-haproxy');
} else { } else {
let containerName = 'coolify-proxy';
if (!settings.isTraefikUsed) {
containerName = 'coolify-haproxy';
}
payload.state = payload.state =
destination?.engine && (await checkContainer(destination.engine, 'coolify-haproxy')); destination?.engine && (await checkContainer(destination.engine, containerName));
} }
return { return {
status: 200, status: 200,

View File

@ -1,7 +1,12 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; import {
startCoolifyProxy,
startTraefikProxy,
stopCoolifyProxy,
stopTraefikProxy
} from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@ -11,9 +16,16 @@ export const post: RequestHandler = async (event) => {
const { engine } = await event.request.json(); const { engine } = await event.request.json();
try { try {
await stopCoolifyProxy(engine); const settings = await db.prisma.setting.findFirst({});
await startCoolifyProxy(engine); if (settings?.isTraefikUsed) {
await stopTraefikProxy(engine);
await startTraefikProxy(engine);
} else {
await stopCoolifyProxy(engine);
await startCoolifyProxy(engine);
}
await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true }); await db.setDestinationSettings({ engine, isCoolifyProxyUsed: true });
return { return {
status: 200 status: 200
}; };

View File

@ -1,6 +1,12 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { startCoolifyProxy, stopCoolifyProxy } from '$lib/haproxy'; import * as db from '$lib/database';
import {
startCoolifyProxy,
startTraefikProxy,
stopCoolifyProxy,
stopTraefikProxy
} from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@ -8,14 +14,24 @@ export const post: RequestHandler = async (event) => {
if (status === 401) return { status, body }; if (status === 401) return { status, body };
const { engine } = await event.request.json(); const { engine } = await event.request.json();
const settings = await db.prisma.setting.findFirst({});
try { try {
await startCoolifyProxy(engine); if (settings?.isTraefikUsed) {
await startTraefikProxy(engine);
} else {
await startCoolifyProxy(engine);
}
return { return {
status: 200 status: 200
}; };
} catch (error) { } catch (error) {
await stopCoolifyProxy(engine); if (settings?.isTraefikUsed) {
await stopTraefikProxy(engine);
} else {
await stopCoolifyProxy(engine);
}
return ErrorHandler(error); return ErrorHandler(error);
} }
}; };

View File

@ -1,6 +1,7 @@
import { getUserDetails } from '$lib/common'; import { getUserDetails } from '$lib/common';
import { ErrorHandler } from '$lib/database'; import { ErrorHandler } from '$lib/database';
import { stopCoolifyProxy } from '$lib/haproxy'; import * as db from '$lib/database';
import { stopCoolifyProxy, stopTraefikProxy } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
@ -9,7 +10,13 @@ export const post: RequestHandler = async (event) => {
const { engine } = await event.request.json(); const { engine } = await event.request.json();
try { try {
await stopCoolifyProxy(engine); const settings = await db.prisma.setting.findFirst({});
if (settings?.isTraefikUsed) {
await stopTraefikProxy(engine);
} else {
await stopCoolifyProxy(engine);
}
return { return {
status: 200 status: 200
}; };

View File

@ -195,7 +195,6 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
} }
}; };
const composeFileDestination = `${workdir}/docker-compose.yaml`; const composeFileDestination = `${workdir}/docker-compose.yaml`;
console.log(JSON.stringify(composeFile, null, 2));
await fs.writeFile(composeFileDestination, yaml.dump(composeFile)); 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} pull`);
await asyncExecShell( await asyncExecShell(

View File

@ -3,7 +3,12 @@ import { asyncExecShell, getEngine, getUserDetails } from '$lib/common';
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { ErrorHandler, generatePassword, getFreePort } from '$lib/database'; import { ErrorHandler, generatePassword, getFreePort } from '$lib/database';
import { checkContainer, startTcpProxy, stopTcpHttpProxy } from '$lib/haproxy'; import {
checkContainer,
startTcpProxy,
startTraefikTCPProxy,
stopTcpHttpProxy
} from '$lib/haproxy';
import type { ComposeFile } from '$lib/types/composeFile'; import type { ComposeFile } from '$lib/types/composeFile';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import cuid from 'cuid'; import cuid from 'cuid';
@ -142,8 +147,12 @@ export const post: RequestHandler = async (event) => {
await asyncExecShell( await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d` `DOCKER_HOST=${host} docker compose -f ${hostkeyDir}/${id}-docker-compose.yml up -d`
); );
const settings = await db.prisma.setting.findFirst();
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22); if (settings.isTraefikUsed) {
await startTraefikTCPProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
} else {
await startTcpProxy(destinationDocker, `${id}-ftp`, publicPort, 22);
}
} else { } else {
await db.prisma.wordpress.update({ await db.prisma.wordpress.update({
where: { serviceId: id }, where: { serviceId: id },

View File

@ -72,8 +72,7 @@ export const post: RequestHandler = async (event) => {
minPort, minPort,
maxPort, maxPort,
isAutoUpdateEnabled, isAutoUpdateEnabled,
isDNSCheckEnabled, isDNSCheckEnabled
forceSave
} = await event.request.json(); } = await event.request.json();
try { try {
const { id } = await db.listSettings(); const { id } = await db.listSettings();

View File

@ -37,12 +37,13 @@
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { features } from '$lib/store'; import { features, isTraefikUsed } from '$lib/store';
let isRegistrationEnabled = settings.isRegistrationEnabled; let isRegistrationEnabled = settings.isRegistrationEnabled;
let dualCerts = settings.dualCerts; let dualCerts = settings.dualCerts;
let isAutoUpdateEnabled = settings.isAutoUpdateEnabled; let isAutoUpdateEnabled = settings.isAutoUpdateEnabled;
let isDNSCheckEnabled = settings.isDNSCheckEnabled; let isDNSCheckEnabled = settings.isDNSCheckEnabled;
$isTraefikUsed = settings.isTraefikUsed;
let minPort = settings.minPort; let minPort = settings.minPort;
let maxPort = settings.maxPort; let maxPort = settings.maxPort;
@ -55,7 +56,8 @@
let isFqdnSet = !!settings.fqdn; let isFqdnSet = !!settings.fqdn;
let loading = { let loading = {
save: false, save: false,
remove: false remove: false,
proxyMigration: false
}; };
async function removeFqdn() { async function removeFqdn() {
@ -86,6 +88,7 @@
if (name === 'isDNSCheckEnabled') { if (name === 'isDNSCheckEnabled') {
isDNSCheckEnabled = !isDNSCheckEnabled; isDNSCheckEnabled = !isDNSCheckEnabled;
} }
await post(`/settings.json`, { await post(`/settings.json`, {
isRegistrationEnabled, isRegistrationEnabled,
dualCerts, dualCerts,
@ -156,6 +159,20 @@
function resetView() { function resetView() {
forceSave = false; forceSave = false;
} }
async function migrateProxy(to) {
if (loading.proxyMigration) return;
try {
loading.proxyMigration = true;
await post(`/update.json`, { type: to });
const data = await get(`/settings.json`);
$isTraefikUsed = data.settings.isTraefikUsed;
return toast.push('Proxy migration completed.');
} catch ({ error }) {
return errorNotification(error);
} finally {
loading.proxyMigration = false;
}
}
</script> </script>
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
@ -192,6 +209,26 @@
</div> </div>
<div class="grid grid-flow-row gap-2 px-10"> <div class="grid grid-flow-row gap-2 px-10">
<!-- <Language /> --> <!-- <Language /> -->
<div class="grid grid-cols-2 items-center">
<div class="flex items-center py-4 pr-8">
<div class="flex w-96 flex-col">
<div class="text-xs font-bold text-stone-100 md:text-base">New Proxy Available!</div>
<Explainer
text="We are changing to <span class='text-sky-500 font-bold'>Traefik</span> as <span class='text-red-500 font-bold'>HAProxy</span> had several problems and uses a LOT of unnecessary memory (<span class='text-sky-500 font-bold'>~20MB</span> vs <span class='text-red-500 font-bold'>~200MB</span>).<br><br>You can switch back to HAProxy if something is not working and <span class='text-yellow-500 font-bold'>please let us know</span>!"
/>
</div>
</div>
<button
disabled={loading.proxyMigration}
class="bg-green-600 text-white hover:bg-green-500"
on:click={() => migrateProxy($isTraefikUsed ? 'haproxy' : 'traefik')}
>{loading.proxyMigration
? 'Migrating...'
: $isTraefikUsed
? 'Switch back to HAProxy'
: 'Migrate to Traefik'}</button
>
</div>
<div class="grid grid-cols-2 items-start"> <div class="grid grid-cols-2 items-start">
<div class="flex-col"> <div class="flex-col">
<div class="pt-2 text-base font-bold text-stone-100"> <div class="pt-2 text-base font-bold text-stone-100">

View File

@ -1,152 +1,308 @@
import { dev } from '$app/env';
import { asyncExecShell, getDomain, getEngine } from '$lib/common'; import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { supportedServiceTypesAndVersions } from '$lib/components/common';
import * as db from '$lib/database'; import * as db from '$lib/database';
import { listServicesWithIncludes } from '$lib/database';
import { checkContainer } from '$lib/haproxy'; import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const get = async () => { export const get: RequestHandler = async (event) => {
const applications = await db.prisma.application.findMany({ const id = event.url.searchParams.get('id');
include: { destinationDocker: true, settings: true } if (id) {
}); const privatePort = event.url.searchParams.get('privatePort');
const data = { const publicPort = event.url.searchParams.get('publicPort');
applications: [], const type = event.url.searchParams.get('type');
services: [], let traefik = {};
coolify: [] if (publicPort) {
}; traefik = {
for (const application of applications) { [type]: {
const { routers: {
fqdn, [id]: {
id, entrypoints: [type],
port, rule: `HostSNI(\`*\`)`,
destinationDocker, service: id
destinationDockerId, }
settings: { previews }, },
updatedAt services: {
} = application; [id]: {
if (destinationDockerId) { loadbalancer: {
const { engine, network } = destinationDocker; servers: []
const isRunning = await checkContainer(engine, id); }
if (fqdn) { }
const domain = getDomain(fqdn); }
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
if (isRunning) {
data.applications.push({
id,
port: port || 3000,
domain,
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
updatedAt: updatedAt.getTime()
});
} }
if (previews) { };
const host = getEngine(engine); }
const { stdout } = await asyncExecShell( if (type === 'tcp') {
`DOCKER_HOST=${host} docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` });
); } else {
const containers = stdout traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` });
.trim() }
.split('\n') return {
.filter((a) => a) status: 200,
.map((c) => c.replace(/"/g, '')); body: {
if (containers.length > 0) { ...traefik
for (const container of containers) { }
const previewDomain = `${container.split('-')[1]}.${domain}`; };
data.applications.push({ } else {
id: container, const applications = await db.prisma.application.findMany({
port: port || 3000, include: { destinationDocker: true, settings: true }
domain: previewDomain, });
const data = {
applications: [],
services: [],
coolify: []
};
for (const application of applications) {
const {
fqdn,
id,
port,
destinationDocker,
destinationDockerId,
settings: { previews },
updatedAt
} = application;
if (destinationDockerId) {
const { engine, network } = destinationDocker;
const isRunning = await checkContainer(engine, id);
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
if (isRunning) {
data.applications.push({
id,
port: port || 3000,
domain,
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
updatedAt: updatedAt.getTime()
});
}
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 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}`;
data.applications.push({
id: container,
port: port || 3000,
domain: previewDomain,
isRunning,
isHttps,
redirectValue,
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain,
updatedAt: updatedAt.getTime()
});
}
}
}
}
}
}
const services = await listServicesWithIncludes();
for (const service of services) {
const {
fqdn,
id,
type,
destinationDocker,
destinationDockerId,
updatedAt,
plausibleAnalytics
} = service;
if (destinationDockerId) {
const { engine } = destinationDocker;
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
if (found) {
const port = found.ports.main;
const publicPort = service[type]?.publicPort;
const isRunning = await checkContainer(engine, id);
if (fqdn) {
const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
if (isRunning) {
// Plausible Analytics custom script
let scriptName = false;
if (
type === 'plausibleanalytics' &&
plausibleAnalytics.scriptName !== 'plausible.js'
) {
scriptName = plausibleAnalytics.scriptName;
}
data.services.push({
id,
port,
publicPort,
domain,
isRunning, isRunning,
isHttps, isHttps,
redirectValue, redirectValue,
redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
updatedAt: updatedAt.getTime() updatedAt: updatedAt.getTime(),
scriptName
}); });
} }
} }
} }
} }
} }
}
const traefik = { const { fqdn } = await db.prisma.setting.findFirst();
http: { if (fqdn) {
routers: {}, const domain = getDomain(fqdn);
services: {} const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
data.coolify.push({
id: dev ? 'host.docker.internal' : 'coolify',
port: 3000,
domain,
isHttps,
redirectValue,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
});
} }
}; const traefik = {
for (const application of data.applications) { http: {
const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt } = application; routers: {},
traefik.http.routers[id] = { services: {}
entrypoints: ['web'], }
rule: `Host(\`${domain}\`)`,
service: id
}; };
traefik.http.services[id] = { for (const application of data.applications) {
loadbalancer: { const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt } = application;
servers: [ traefik.http.routers[id] = {
{ entrypoints: ['web'],
url: `http://${id}:${port}` rule: `Host(\`${domain}\`)`,
service: id
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${id}:${port}`
}
]
}
};
}
for (const application of data.services) {
const { id, port, domain, isHttps, redirectValue, redirectTo, updatedAt, scriptName } =
application;
traefik.http.routers[id] = {
entrypoints: ['web'],
rule: `Host(\`${domain}\`)`,
service: id
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${id}:${port}`
}
]
}
};
if (scriptName) {
if (!traefik.http.middlewares) traefik.http.middlewares = {};
traefik.http.middlewares[`${id}-redir`] = {
replacepathregex: {
regex: `/js/${scriptName}`,
replacement: '/js/plausible.js'
} }
] };
traefik.http.routers[id].middlewares = [`${id}-redir`];
}
}
for (const application of data.coolify) {
const { domain, id, port } = application;
traefik.http.routers['coolify'] = {
entrypoints: ['web'],
rule: `Host(\`${domain}\`)`,
service: id
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${id}:${port}`
}
]
}
};
}
return {
status: 200,
body: {
...traefik
// "http": {
// "routers": {
// "coolify": {
// "entrypoints": [
// "web"
// ],
// "middlewares": [
// "coolify-hc"
// ],
// "rule": "Host(`staging.coolify.io`)",
// "service": "coolify"
// },
// "static.example.coolify.io": {
// "entrypoints": [
// "web"
// ],
// "rule": "Host(`static.example.coolify.io`)",
// "service": "static.example.coolify.io"
// }
// },
// "services": {
// "coolify": {
// "loadbalancer": {
// "servers": [
// {
// "url": "http://coolify:3000"
// }
// ]
// }
// },
// "static.example.coolify.io": {
// "loadbalancer": {
// "servers": [
// {
// "url": "http://cl32p06f58068518cs3thg6vbc7:80"
// }
// ]
// }
// }
// },
// "middlewares": {
// "coolify-hc": {
// "replacepathregex": {
// "regex": "/dead.json",
// "replacement": "/undead.json"
// }
// }
// }
// }
} }
}; };
} }
return {
status: 200,
body: {
...traefik
// "http": {
// "routers": {
// "coolify": {
// "entrypoints": [
// "web"
// ],
// "middlewares": [
// "coolify-hc"
// ],
// "rule": "Host(`staging.coolify.io`)",
// "service": "coolify"
// },
// "static.example.coolify.io": {
// "entrypoints": [
// "web"
// ],
// "rule": "Host(`static.example.coolify.io`)",
// "service": "static.example.coolify.io"
// }
// },
// "services": {
// "coolify": {
// "loadbalancer": {
// "servers": [
// {
// "url": "http://coolify:3000"
// }
// ]
// }
// },
// "static.example.coolify.io": {
// "loadbalancer": {
// "servers": [
// {
// "url": "http://cl32p06f58068518cs3thg6vbc7:80"
// }
// ]
// }
// }
// },
// "middlewares": {
// "coolify-hc": {
// "replacepathregex": {
// "regex": "/dead.json",
// "replacement": "/undead.json"
// }
// }
// }
// }
}
};
}; };

View File

@ -6,6 +6,12 @@ import * as db from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import compare from 'compare-versions'; import compare from 'compare-versions';
import got from 'got'; import got from 'got';
import {
checkContainer,
configureNetworkTraefikProxy,
startCoolifyProxy,
startTraefikProxy
} from '$lib/haproxy';
export const get: RequestHandler = async (request) => { export const get: RequestHandler = async (request) => {
try { try {
@ -34,14 +40,14 @@ export const get: RequestHandler = async (request) => {
export const post: RequestHandler = async (event) => { export const post: RequestHandler = async (event) => {
const { type, latestVersion } = await event.request.json(); const { type, latestVersion } = await event.request.json();
const settings = await db.prisma.setting.findFirst();
if (type === 'update') { if (type === 'update') {
try { try {
if (!dev) { if (!dev) {
const { isAutoUpdateEnabled } = await db.prisma.setting.findFirst();
await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`); await asyncExecShell(`docker pull coollabsio/coolify:${latestVersion}`);
await asyncExecShell(`env | grep COOLIFY > .env`); await asyncExecShell(`env | grep COOLIFY > .env`);
await asyncExecShell( await asyncExecShell(
`sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${isAutoUpdateEnabled}' .env` `sed -i '/COOLIFY_AUTO_UPDATE=/c\COOLIFY_AUTO_UPDATE=${settings.isAutoUpdateEnabled}' .env`
); );
await asyncExecShell( await asyncExecShell(
`docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"` `docker run --rm -tid --env-file .env -v /var/run/docker.sock:/var/run/docker.sock -v coolify-db coollabsio/coolify:${latestVersion} /bin/sh -c "env | grep COOLIFY > .env && echo 'TAG=${latestVersion}' >> .env && docker stop -t 0 coolify coolify-redis && docker rm coolify coolify-redis && docker compose up -d --force-recreate"`
@ -61,15 +67,37 @@ export const post: RequestHandler = async (event) => {
} catch (error) { } catch (error) {
return ErrorHandler(error); return ErrorHandler(error);
} }
} else if (type === 'migrateToTraefik') { } else if (type === 'traefik') {
try { try {
const settings = await db.prisma.setting.findFirst({}); const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
if (found) {
await asyncExecShell(`docker stop -t 0 coolify-haproxy`);
await asyncExecShell(`docker rm coolify-haproxy`);
}
await startTraefikProxy('/var/run/docker.sock');
await db.prisma.setting.update({ await db.prisma.setting.update({
where: { id: settings.id }, where: { id: settings.id },
data: { disableHaproxy: true } data: { isTraefikUsed: true }
});
return {
status: 200,
body: {}
};
} catch (error) {
return ErrorHandler(error);
}
} else if (type === 'haproxy') {
try {
const found = await checkContainer('/var/run/docker.sock', 'coolify-proxy');
if (found) {
await asyncExecShell(`docker stop -t 0 coolify-proxy`);
await asyncExecShell(`docker rm coolify-proxy`);
}
await startCoolifyProxy('/var/run/docker.sock');
await db.prisma.setting.update({
where: { id: settings.id },
data: { isTraefikUsed: false }
}); });
await asyncExecShell(`docker stop -t 0 coolify-haproxy`);
await asyncExecShell(`docker rm coolify-haproxy`);
return { return {
status: 200, status: 200,
body: {} body: {}