import { FastifyRequest } from "fastify"; import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma, supportedServiceTypesAndVersions, include } from "../../../lib/common"; import { getEngine } from "../../../lib/docker"; import { TraefikOtherConfiguration } from "./types"; function configureMiddleware( { id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type }, traefik ) { if (isHttps) { traefik.http.routers[id] = { entrypoints: ['web'], rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, service: `${id}`, middlewares: ['redirect-to-https'] }; traefik.http.services[id] = { loadbalancer: { servers: [ { url: `http://${container}:${port}` } ] } }; if (isDualCerts) { traefik.http.routers[`${id}-secure`] = { entrypoints: ['websecure'], rule: `Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)`, service: `${id}`, tls: { certresolver: 'letsencrypt' }, middlewares: [] }; } else { if (isWWW) { traefik.http.routers[`${id}-secure-www`] = { entrypoints: ['websecure'], rule: `Host(\`www.${nakedDomain}\`)`, service: `${id}`, tls: { certresolver: 'letsencrypt' }, middlewares: [] }; traefik.http.routers[`${id}-secure`] = { entrypoints: ['websecure'], rule: `Host(\`${nakedDomain}\`)`, service: `${id}`, tls: { domains: { main: `${domain}` } }, middlewares: ['redirect-to-www'] }; traefik.http.routers[`${id}`].middlewares.push('redirect-to-www'); } else { traefik.http.routers[`${id}-secure-www`] = { entrypoints: ['websecure'], rule: `Host(\`www.${nakedDomain}\`)`, service: `${id}`, tls: { domains: { main: `${domain}` } }, middlewares: ['redirect-to-non-www'] }; traefik.http.routers[`${id}-secure`] = { entrypoints: ['websecure'], rule: `Host(\`${domain}\`)`, service: `${id}`, tls: { certresolver: 'letsencrypt' }, middlewares: [] }; traefik.http.routers[`${id}`].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://${container}:${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'); } } } if (type === 'plausibleanalytics' && scriptName && scriptName !== 'plausible.js') { if (!traefik.http.routers[`${id}`].middlewares.includes(`${id}-redir`)) { traefik.http.routers[`${id}`].middlewares.push(`${id}-redir`); } if (!traefik.http.routers[`${id}-secure`].middlewares.includes(`${id}-redir`)) { traefik.http.routers[`${id}-secure`].middlewares.push(`${id}-redir`); } } } export async function traefikConfiguration(request, reply) { try { 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}' } } } } }; const applications = await prisma.application.findMany({ where: { destinationDocker: { remoteEngine: false } }, 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 = true; 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, container: 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}`; const nakedDomain = previewDomain.replace(/^www\./, ''); data.applications.push({ id: container, container, port: port || 3000, domain: previewDomain, isRunning, nakedDomain, isHttps, isWWW, isDualCerts: dualCerts }); } } } } } } const services: any = await prisma.service.findMany({ where: { destinationDocker: { remoteEngine: false } }, include, orderBy: { createdAt: 'desc' }, }); for (const service of services) { const { fqdn, id, type, destinationDocker, destinationDockerId, 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 = true; 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; } let container = id; let otherDomain = null; let otherNakedDomain = null; let otherIsHttps = null; let otherIsWWW = null; if (type === 'minio' && service.minio.apiFqdn) { otherDomain = getDomain(service.minio.apiFqdn); otherNakedDomain = otherDomain.replace(/^www\./, ''); otherIsHttps = service.minio.apiFqdn.startsWith('https://'); otherIsWWW = service.minio.apiFqdn.includes('www.'); } data.services.push({ id, container, type, otherDomain, otherNakedDomain, otherIsHttps, otherIsWWW, port, publicPort, domain, nakedDomain, isRunning, isHttps, isWWW, isDualCerts: dualCerts, scriptName }); } } } } } const { fqdn, dualCerts } = await 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: isDev ? 'host.docker.internal' : 'coolify', container: isDev ? 'host.docker.internal' : 'coolify', port: 3000, domain, nakedDomain, isHttps, isWWW, isDualCerts: dualCerts }); } for (const application of data.applications) { configureMiddleware(application, traefik); } for (const service of data.services) { const { id, scriptName } = service; configureMiddleware(service, traefik); if (service.type === 'minio') { service.id = id + '-minio'; service.container = id; service.domain = service.otherDomain; service.nakedDomain = service.otherNakedDomain; service.isHttps = service.otherIsHttps; service.isWWW = service.otherIsWWW; service.port = 9000; configureMiddleware(service, traefik); } if (scriptName) { traefik.http.middlewares[`${id}-redir`] = { replacepathregex: { regex: `/js/${scriptName}`, replacement: '/js/plausible.js' } }; } } for (const coolify of data.coolify) { configureMiddleware(coolify, traefik); } if (Object.keys(traefik.http.routers).length === 0) { traefik.http.routers = null; } if (Object.keys(traefik.http.services).length === 0) { traefik.http.services = null; } return { ...traefik } } catch ({ status, message }) { return errorHandler({ status, message }) } } export async function traefikOtherConfiguration(request: FastifyRequest) { try { const { id } = request.query if (id) { const { privatePort, publicPort, type, address = id } = request.query let traefik = {}; if (publicPort && type && privatePort) { if (type === 'tcp') { traefik = { [type]: { routers: { [id]: { entrypoints: [type], rule: `HostSNI(\`*\`)`, service: id } }, services: { [id]: { loadbalancer: { servers: [{ address: `${address}:${privatePort}` }] } } } } }; } else if (type === 'http') { const service = await prisma.service.findFirst({ where: { id }, include: { minio: true } }); if (service) { if (service.type === 'minio') { if (service?.minio?.apiFqdn) { const { minio: { apiFqdn } } = service; const domain = getDomain(apiFqdn); const isHttps = apiFqdn.startsWith('https://'); traefik = { [type]: { routers: { [id]: { entrypoints: [type], rule: `Host(\`${domain}\`)`, service: id } }, services: { [id]: { loadbalancer: { servers: [{ url: `http://${id}:${privatePort}` }] } } } } }; if (isHttps) { if (isDev) { traefik[type].routers[id].tls = { domains: { main: `${domain}` } }; } else { traefik[type].routers[id].tls = { certresolver: 'letsencrypt' }; } } } } else { if (service?.fqdn) { const domain = getDomain(service.fqdn); const isHttps = service.fqdn.startsWith('https://'); traefik = { [type]: { routers: { [id]: { entrypoints: [type], rule: `Host(\`${domain}:${privatePort}\`)`, service: id } }, services: { [id]: { loadbalancer: { servers: [{ url: `http://${id}:${privatePort}` }] } } } } }; if (isHttps) { if (isDev) { traefik[type].routers[id].tls = { domains: { main: `${domain}` } }; } else { traefik[type].routers[id].tls = { certresolver: 'letsencrypt' }; } } } } } else { throw { status: 500 } } } } else { throw { status: 500 } } return { ...traefik }; } throw { status: 500 } } catch ({ status, message }) { console.log(status, message); return errorHandler({ status, message }) } } export async function remoteTraefikConfiguration(request: FastifyRequest) { const { id } = request.params try { 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}' } } } } }; const applications = await prisma.application.findMany({ where: { destinationDocker: { id } }, 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 = true; 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, container: 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}`; const nakedDomain = previewDomain.replace(/^www\./, ''); data.applications.push({ id: container, container, port: port || 3000, domain: previewDomain, isRunning, nakedDomain, isHttps, isWWW, isDualCerts: dualCerts }); } } } } } } const services: any = await prisma.service.findMany({ where: { destinationDocker: { id } }, include, orderBy: { createdAt: 'desc' } }); for (const service of services) { const { fqdn, id, type, destinationDocker, destinationDockerId, 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 = true; 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; } let container = id; let otherDomain = null; let otherNakedDomain = null; let otherIsHttps = null; let otherIsWWW = null; if (type === 'minio' && service.minio.apiFqdn) { otherDomain = getDomain(service.minio.apiFqdn); otherNakedDomain = otherDomain.replace(/^www\./, ''); otherIsHttps = service.minio.apiFqdn.startsWith('https://'); otherIsWWW = service.minio.apiFqdn.includes('www.'); } data.services.push({ id, container, type, otherDomain, otherNakedDomain, otherIsHttps, otherIsWWW, port, publicPort, domain, nakedDomain, isRunning, isHttps, isWWW, isDualCerts: dualCerts, scriptName }); } } } } } for (const application of data.applications) { configureMiddleware(application, traefik); } for (const service of data.services) { const { id, scriptName } = service; configureMiddleware(service, traefik); if (service.type === 'minio') { service.id = id + '-minio'; service.container = id; service.domain = service.otherDomain; service.nakedDomain = service.otherNakedDomain; service.isHttps = service.otherIsHttps; service.isWWW = service.otherIsWWW; service.port = 9000; configureMiddleware(service, traefik); } if (scriptName) { traefik.http.middlewares[`${id}-redir`] = { replacepathregex: { regex: `/js/${scriptName}`, replacement: '/js/plausible.js' } }; } } for (const coolify of data.coolify) { configureMiddleware(coolify, traefik); } if (Object.keys(traefik.http.routers).length === 0) { traefik.http.routers = null; } if (Object.keys(traefik.http.services).length === 0) { traefik.http.services = null; } return { ...traefik } } catch ({ status, message }) { return errorHandler({ status, message }) } }