WIP: Traefik
This commit is contained in:
@@ -11,9 +11,14 @@ 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`;
|
export const defaultTraefikImage = `traefik:v2.6`;
|
||||||
const coolifyEndpoint = dev
|
|
||||||
? 'http://host.docker.internal:3000/traefik.json'
|
const mainTraefikEndpoint = dev
|
||||||
: 'http://coolify:3000/traefik.json';
|
? 'http://host.docker.internal:3000/webhooks/traefik/main.json'
|
||||||
|
: 'http://coolify:3000/webhooks/traefik/main.json';
|
||||||
|
|
||||||
|
const otherTraefikEndpoint = dev
|
||||||
|
? 'http://host.docker.internal:3000/webhooks/traefik/other.json'
|
||||||
|
: 'http://coolify:3000/webhooks/traefik/other.json';
|
||||||
|
|
||||||
export async function haproxyInstance(): Promise<Got> {
|
export async function haproxyInstance(): Promise<Got> {
|
||||||
const { proxyPassword } = await db.listSettings();
|
const { proxyPassword } = await db.listSettings();
|
||||||
@@ -154,7 +159,7 @@ export async function startTraefikTCPProxy(
|
|||||||
image: 'traefik:v2.6',
|
image: 'traefik:v2.6',
|
||||||
command: [
|
command: [
|
||||||
`--entrypoints.tcp.address=:${publicPort}`,
|
`--entrypoints.tcp.address=:${publicPort}`,
|
||||||
`--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`,
|
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp`,
|
||||||
'--providers.http.pollTimeout=2s',
|
'--providers.http.pollTimeout=2s',
|
||||||
'--log.level=error'
|
'--log.level=error'
|
||||||
],
|
],
|
||||||
@@ -250,7 +255,7 @@ export async function startTraefikHTTPProxy(
|
|||||||
image: 'traefik:v2.6',
|
image: 'traefik:v2.6',
|
||||||
command: [
|
command: [
|
||||||
`--entrypoints.http.address=:${publicPort}`,
|
`--entrypoints.http.address=:${publicPort}`,
|
||||||
`--providers.http.endpoint=${coolifyEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`,
|
`--providers.http.endpoint=${otherTraefikEndpoint}?id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=http`,
|
||||||
'--providers.http.pollTimeout=2s',
|
'--providers.http.pollTimeout=2s',
|
||||||
'--log.level=error'
|
'--log.level=error'
|
||||||
],
|
],
|
||||||
@@ -359,7 +364,7 @@ export async function startTraefikProxy(engine: string): Promise<void> {
|
|||||||
--entrypoints.websecure.address=:443 \
|
--entrypoints.websecure.address=:443 \
|
||||||
--providers.docker=true \
|
--providers.docker=true \
|
||||||
--providers.docker.exposedbydefault=false \
|
--providers.docker.exposedbydefault=false \
|
||||||
--providers.http.endpoint=${coolifyEndpoint} \
|
--providers.http.endpoint=${mainTraefikEndpoint} \
|
||||||
--providers.http.pollTimeout=5s \
|
--providers.http.pollTimeout=5s \
|
||||||
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
|
--certificatesresolvers.letsencrypt.acme.httpchallenge=true \
|
||||||
--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
|
--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json \
|
||||||
|
|||||||
@@ -1,437 +0,0 @@
|
|||||||
import { dev } from '$app/env';
|
|
||||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
|
||||||
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
|
||||||
import * as db from '$lib/database';
|
|
||||||
import { listServicesWithIncludes } from '$lib/database';
|
|
||||||
import { checkContainer } from '$lib/haproxy';
|
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
function generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik }) {
|
|
||||||
if (!isDualCerts) {
|
|
||||||
if (isWWW) {
|
|
||||||
if (isHttps) {
|
|
||||||
traefik.http.routers[id].middlewares?.length > 0
|
|
||||||
? traefik.http.routers[id].middlewares.push('https-redirect-non-www-to-www')
|
|
||||||
: (traefik.http.routers[id].middlewares = [
|
|
||||||
'https-redirect-non-www-to-www',
|
|
||||||
'http-to-https'
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
traefik.http.routers[id].middlewares?.length > 0
|
|
||||||
? traefik.http.routers[id].middlewares.push('http-redirect-non-www-to-www')
|
|
||||||
: (traefik.http.routers[id].middlewares = [
|
|
||||||
'http-redirect-non-www-to-www',
|
|
||||||
'https-to-http'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isHttps) {
|
|
||||||
traefik.http.routers[id].middlewares?.length > 0
|
|
||||||
? traefik.http.routers[id].middlewares.push('https-redirect-www-to-non-www')
|
|
||||||
: (traefik.http.routers[id].middlewares = [
|
|
||||||
'https-redirect-www-to-non-www',
|
|
||||||
'http-to-https'
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
traefik.http.routers[id]?.middlewares?.length > 0
|
|
||||||
? traefik.http.routers[id].middlewares.push('http-redirect-www-to-non-www')
|
|
||||||
: (traefik.http.routers[id].middlewares = ['http-redirect-www-to-non-www']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export const get: RequestHandler = async (event) => {
|
|
||||||
const id = event.url.searchParams.get('id');
|
|
||||||
if (id) {
|
|
||||||
const privatePort = event.url.searchParams.get('privatePort');
|
|
||||||
const publicPort = event.url.searchParams.get('publicPort');
|
|
||||||
const type = event.url.searchParams.get('type');
|
|
||||||
if (publicPort) {
|
|
||||||
if (type === 'tcp') {
|
|
||||||
const traefik = {
|
|
||||||
[type]: {
|
|
||||||
routers: {
|
|
||||||
[id]: {
|
|
||||||
entrypoints: [type],
|
|
||||||
rule: `HostSNI(\`*\`)`,
|
|
||||||
service: id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
services: {
|
|
||||||
[id]: {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [{ address: `${id}:${privatePort}` }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: {
|
|
||||||
['global-compress']: {
|
|
||||||
compress: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
...traefik
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if (type === 'http') {
|
|
||||||
const service = await db.prisma.service.findFirst({ where: { id } });
|
|
||||||
if (service?.fqdn) {
|
|
||||||
const domain = getDomain(service.fqdn);
|
|
||||||
const isWWW = domain.startsWith('www.');
|
|
||||||
const traefik = {
|
|
||||||
[type]: {
|
|
||||||
routers: {
|
|
||||||
[id]: {
|
|
||||||
entrypoints: [type],
|
|
||||||
rule: isWWW
|
|
||||||
? `Host(\`${domain}\`) || Host(\`www.${domain}\`)`
|
|
||||||
: `Host(\`${domain}\`)`,
|
|
||||||
service: id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
services: {
|
|
||||||
[id]: {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [{ url: `http://${id}:${privatePort}` }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
middlewares: {
|
|
||||||
['global-compress']: {
|
|
||||||
compress: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
...traefik
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status: 500
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
const applications = await db.prisma.application.findMany({
|
|
||||||
include: { destinationDocker: true, settings: true }
|
|
||||||
});
|
|
||||||
const data = {
|
|
||||||
applications: [],
|
|
||||||
services: [],
|
|
||||||
coolify: []
|
|
||||||
};
|
|
||||||
for (const application of applications) {
|
|
||||||
const {
|
|
||||||
fqdn,
|
|
||||||
id,
|
|
||||||
port,
|
|
||||||
destinationDocker,
|
|
||||||
destinationDockerId,
|
|
||||||
settings: { previews, dualCerts }
|
|
||||||
} = application;
|
|
||||||
if (destinationDockerId) {
|
|
||||||
const { engine, network } = destinationDocker;
|
|
||||||
const isRunning = await checkContainer(engine, id);
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
if (isRunning) {
|
|
||||||
data.applications.push({
|
|
||||||
id,
|
|
||||||
port: port || 3000,
|
|
||||||
domain,
|
|
||||||
nakedDomain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
isWWW,
|
|
||||||
isDualCerts: dualCerts
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
isWWW
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const services = await listServicesWithIncludes();
|
|
||||||
|
|
||||||
for (const service of services) {
|
|
||||||
const {
|
|
||||||
fqdn,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
dualCerts,
|
|
||||||
destinationDocker,
|
|
||||||
destinationDockerId,
|
|
||||||
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 nakedDomain = domain.replace(/^www\./, '');
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
if (isRunning) {
|
|
||||||
// Plausible Analytics custom script
|
|
||||||
let scriptName = false;
|
|
||||||
if (
|
|
||||||
type === 'plausibleanalytics' &&
|
|
||||||
plausibleAnalytics.scriptName !== 'plausible.js'
|
|
||||||
) {
|
|
||||||
scriptName = plausibleAnalytics.scriptName;
|
|
||||||
}
|
|
||||||
data.services.push({
|
|
||||||
id,
|
|
||||||
port,
|
|
||||||
publicPort,
|
|
||||||
domain,
|
|
||||||
nakedDomain,
|
|
||||||
isRunning,
|
|
||||||
isHttps,
|
|
||||||
isWWW,
|
|
||||||
isDualCerts: dualCerts,
|
|
||||||
scriptName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { fqdn, dualCerts } = await db.prisma.setting.findFirst();
|
|
||||||
if (fqdn) {
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
const nakedDomain = domain.replace(/^www\./, '');
|
|
||||||
const isHttps = fqdn.startsWith('https://');
|
|
||||||
const isWWW = fqdn.includes('www.');
|
|
||||||
data.coolify.push({
|
|
||||||
id: dev ? 'host.docker.internal' : 'coolify',
|
|
||||||
port: 3000,
|
|
||||||
domain,
|
|
||||||
nakedDomain,
|
|
||||||
isHttps,
|
|
||||||
isWWW,
|
|
||||||
isDualCerts: dualCerts
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const traefik = {
|
|
||||||
http: {
|
|
||||||
routers: {},
|
|
||||||
services: {},
|
|
||||||
middlewares: {
|
|
||||||
['global-compress']: {
|
|
||||||
compress: true
|
|
||||||
},
|
|
||||||
['https-redirect-non-www-to-www']: {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https://(?:www\\.)?(.+)',
|
|
||||||
replacement: 'https://www.${1}',
|
|
||||||
permanent: dev ? false : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['http-redirect-non-www-to-www']: {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^http://(?:www\\.)?(.+)',
|
|
||||||
replacement: 'http://www.${1}',
|
|
||||||
permanent: dev ? false : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['https-redirect-www-to-non-www']: {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https?://www\\.(.+)',
|
|
||||||
replacement: 'https://${1}',
|
|
||||||
permanent: dev ? false : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['http-redirect-www-to-non-www']: {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^http?://www\\.(.+)',
|
|
||||||
replacement: 'http://${1}',
|
|
||||||
permanent: dev ? false : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['http-to-https']: {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^http?://(.+)',
|
|
||||||
replacement: 'https://${1}',
|
|
||||||
permanent: dev ? false : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['https-to-http']: {
|
|
||||||
redirectregex: {
|
|
||||||
regex: '^https?://(.+)',
|
|
||||||
replacement: 'http://${1}',
|
|
||||||
permanent: dev ? false : true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
['https-http']: {
|
|
||||||
redirectscheme: {
|
|
||||||
scheme: 'http',
|
|
||||||
permanent: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
for (const application of data.applications) {
|
|
||||||
const { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts } = application;
|
|
||||||
if (isHttps) {
|
|
||||||
traefik.http.routers[id] = {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
|
||||||
middlewares: ['http-to-https'],
|
|
||||||
service: id
|
|
||||||
};
|
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: isWWW
|
|
||||||
? isDualCerts
|
|
||||||
? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
|
||||||
service: id
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
traefik.http.routers[id] = {
|
|
||||||
entrypoints: ['web'],
|
|
||||||
rule: isWWW
|
|
||||||
? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`)`,
|
|
||||||
service: id
|
|
||||||
};
|
|
||||||
traefik.http.routers[`${id}-secure`] = {
|
|
||||||
entrypoints: ['websecure'],
|
|
||||||
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
|
||||||
middlewares: ['https-http'],
|
|
||||||
service: id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
traefik.http.services[id] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${id}:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isHttps && !dev) {
|
|
||||||
traefik.http.routers[id].tls = {
|
|
||||||
certresolver: 'letsencrypt'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik });
|
|
||||||
}
|
|
||||||
for (const service of data.services) {
|
|
||||||
const { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName } = service;
|
|
||||||
|
|
||||||
traefik.http.routers[id] = {
|
|
||||||
entrypoints: isHttps ? ['web', 'websecure'] : ['web'],
|
|
||||||
rule: isWWW
|
|
||||||
? isDualCerts
|
|
||||||
? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
|
||||||
service: id
|
|
||||||
};
|
|
||||||
traefik.http.services[id] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${id}:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isHttps && !dev) {
|
|
||||||
traefik.http.routers[id].tls = {
|
|
||||||
certresolver: 'letsencrypt'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (scriptName) {
|
|
||||||
if (!traefik.http.middlewares) traefik.http.middlewares = {};
|
|
||||||
traefik.http.middlewares[`${id}-redir`] = {
|
|
||||||
replacepathregex: {
|
|
||||||
regex: `/js/${scriptName}`,
|
|
||||||
replacement: '/js/plausible.js',
|
|
||||||
permanent: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
traefik.http.routers[id].middlewares = [`${id}-redir`];
|
|
||||||
}
|
|
||||||
generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik });
|
|
||||||
}
|
|
||||||
for (const coolify of data.coolify) {
|
|
||||||
const { nakedDomain, domain, id, port, isHttps, isWWW, isDualCerts } = coolify;
|
|
||||||
traefik.http.routers['coolify'] = {
|
|
||||||
entrypoints: isHttps ? ['web', 'websecure'] : ['web'],
|
|
||||||
rule: isWWW
|
|
||||||
? isDualCerts
|
|
||||||
? `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`)`
|
|
||||||
: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
|
||||||
service: id
|
|
||||||
};
|
|
||||||
traefik.http.services[id] = {
|
|
||||||
loadbalancer: {
|
|
||||||
servers: [
|
|
||||||
{
|
|
||||||
url: `http://${id}:${port}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (isHttps && !dev) {
|
|
||||||
traefik.http.routers[id].tls = {
|
|
||||||
certresolver: 'letsencrypt'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
generateMiddleware({ id, isDualCerts, isWWW, isHttps, traefik });
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: 200,
|
|
||||||
body: {
|
|
||||||
...traefik
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
283
src/routes/webhooks/traefik/main.json.ts
Normal file
283
src/routes/webhooks/traefik/main.json.ts
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
import { dev } from '$app/env';
|
||||||
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { listServicesWithIncludes } from '$lib/database';
|
||||||
|
import { checkContainer } from '$lib/haproxy';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
const traefik = {
|
||||||
|
http: {
|
||||||
|
routers: {},
|
||||||
|
services: {},
|
||||||
|
middlewares: {
|
||||||
|
'redirect-to-https': {
|
||||||
|
redirectscheme: {
|
||||||
|
scheme: 'https'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-http': {
|
||||||
|
redirectscheme: {
|
||||||
|
scheme: 'http'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-non-www': {
|
||||||
|
redirectregex: {
|
||||||
|
regex: '^https?://www\\.(.+)',
|
||||||
|
replacement: 'http://${1}'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'redirect-to-www': {
|
||||||
|
redirectregex: {
|
||||||
|
regex: '^https?://(?:www\\.)?(.+)',
|
||||||
|
replacement: 'http://www.${1}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function configureMiddleware({ id, port, nakedDomain, isHttps, isWWW, isDualCerts }) {
|
||||||
|
if (isHttps) {
|
||||||
|
traefik.http.routers[id] = {
|
||||||
|
entrypoints: ['web'],
|
||||||
|
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
middlewares: ['redirect-to-https']
|
||||||
|
};
|
||||||
|
|
||||||
|
traefik.http.routers[`${id}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
certresolver: 'letsencrypt'
|
||||||
|
},
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
|
||||||
|
traefik.http.services[id] = {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `http://${id}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isDualCerts) {
|
||||||
|
if (isWWW) {
|
||||||
|
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
|
||||||
|
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www');
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
|
||||||
|
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[id] = {
|
||||||
|
entrypoints: ['web'],
|
||||||
|
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
middlewares: []
|
||||||
|
};
|
||||||
|
|
||||||
|
traefik.http.routers[`${id}-secure`] = {
|
||||||
|
entrypoints: ['websecure'],
|
||||||
|
rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`,
|
||||||
|
service: `${id}`,
|
||||||
|
tls: {
|
||||||
|
domains: {
|
||||||
|
main: `${nakedDomain}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
middlewares: ['redirect-to-http']
|
||||||
|
};
|
||||||
|
|
||||||
|
traefik.http.services[id] = {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `http://${id}:${port}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isDualCerts) {
|
||||||
|
if (isWWW) {
|
||||||
|
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
|
||||||
|
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www');
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
|
||||||
|
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const get: RequestHandler = async (event) => {
|
||||||
|
const applications = await db.prisma.application.findMany({
|
||||||
|
include: { destinationDocker: true, settings: true }
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
applications: [],
|
||||||
|
services: [],
|
||||||
|
coolify: []
|
||||||
|
};
|
||||||
|
for (const application of applications) {
|
||||||
|
const {
|
||||||
|
fqdn,
|
||||||
|
id,
|
||||||
|
port,
|
||||||
|
destinationDocker,
|
||||||
|
destinationDockerId,
|
||||||
|
settings: { previews, dualCerts },
|
||||||
|
updatedAt
|
||||||
|
} = application;
|
||||||
|
if (destinationDockerId) {
|
||||||
|
const { engine, network } = destinationDocker;
|
||||||
|
const isRunning = await checkContainer(engine, id);
|
||||||
|
if (fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
if (isRunning) {
|
||||||
|
data.applications.push({
|
||||||
|
id,
|
||||||
|
port: port || 3000,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
dualCerts,
|
||||||
|
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 nakedDomain = domain.replace(/^www\./, '');
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
if (isRunning) {
|
||||||
|
// Plausible Analytics custom script
|
||||||
|
let scriptName = false;
|
||||||
|
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
|
||||||
|
scriptName = plausibleAnalytics.scriptName;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.services.push({
|
||||||
|
id,
|
||||||
|
port,
|
||||||
|
publicPort,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
isRunning,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts,
|
||||||
|
scriptName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { fqdn, dualCerts } = await db.prisma.setting.findFirst();
|
||||||
|
if (fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const nakedDomain = domain.replace(/^www\./, '');
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
data.coolify.push({
|
||||||
|
id: dev ? 'host.docker.internal' : 'coolify',
|
||||||
|
port: 3000,
|
||||||
|
domain,
|
||||||
|
nakedDomain,
|
||||||
|
isHttps,
|
||||||
|
isWWW,
|
||||||
|
isDualCerts: dualCerts
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const application of data.applications) {
|
||||||
|
configureMiddleware(application);
|
||||||
|
}
|
||||||
|
for (const service of data.services) {
|
||||||
|
const { id, scriptName } = service;
|
||||||
|
configureMiddleware(service);
|
||||||
|
|
||||||
|
if (scriptName) {
|
||||||
|
traefik.http.middlewares[`${id}-redir`] = {
|
||||||
|
replacepathregex: {
|
||||||
|
regex: `/js/${scriptName}`,
|
||||||
|
replacement: '/js/plausible.js'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (traefik.http.routers[id].middlewares.length > 0) {
|
||||||
|
traefik.http.routers[id].middlewares.push(`${id}-redir`);
|
||||||
|
} else {
|
||||||
|
traefik.http.routers[id].middlewares = [`${id}-redir`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const coolify of data.coolify) {
|
||||||
|
configureMiddleware(coolify);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
...traefik
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
76
src/routes/webhooks/traefik/other.json.ts
Normal file
76
src/routes/webhooks/traefik/other.json.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { dev } from '$app/env';
|
||||||
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
|
import { supportedServiceTypesAndVersions } from '$lib/components/common';
|
||||||
|
import * as db from '$lib/database';
|
||||||
|
import { listServicesWithIncludes } from '$lib/database';
|
||||||
|
import { checkContainer } from '$lib/haproxy';
|
||||||
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
export const get: RequestHandler = async (event) => {
|
||||||
|
const id = event.url.searchParams.get('id');
|
||||||
|
if (id) {
|
||||||
|
const privatePort = event.url.searchParams.get('privatePort');
|
||||||
|
const publicPort = event.url.searchParams.get('publicPort');
|
||||||
|
const type = event.url.searchParams.get('type');
|
||||||
|
let traefik = {};
|
||||||
|
if (publicPort) {
|
||||||
|
if (type === 'tcp') {
|
||||||
|
traefik = {
|
||||||
|
[type]: {
|
||||||
|
routers: {
|
||||||
|
[id]: {
|
||||||
|
entrypoints: [type],
|
||||||
|
rule: `HostSNI(\`*\`)`,
|
||||||
|
service: id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (type === 'http') {
|
||||||
|
const service = await db.prisma.service.findFirst({ where: { id } });
|
||||||
|
if (service?.fqdn) {
|
||||||
|
const domain = getDomain(service.fqdn);
|
||||||
|
traefik = {
|
||||||
|
[type]: {
|
||||||
|
routers: {
|
||||||
|
[id]: {
|
||||||
|
entrypoints: [type],
|
||||||
|
rule: `Host(\`${domain}\`)`,
|
||||||
|
service: id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
[id]: {
|
||||||
|
loadbalancer: {
|
||||||
|
servers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === 'tcp') {
|
||||||
|
traefik[type].services[id].loadbalancer.servers.push({ address: `${id}:${privatePort}` });
|
||||||
|
} else if (type === 'http') {
|
||||||
|
traefik[type].services[id].loadbalancer.servers.push({ url: `http://${id}:${privatePort}` });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
...traefik
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 500
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user