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 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'; | ||||
|  | ||||
| const mainTraefikEndpoint = dev | ||||
| 	? '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> { | ||||
| 	const { proxyPassword } = await db.listSettings(); | ||||
| @@ -154,7 +159,7 @@ export async function startTraefikTCPProxy( | ||||
| 						image: 'traefik:v2.6', | ||||
| 						command: [ | ||||
| 							`--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', | ||||
| 							'--log.level=error' | ||||
| 						], | ||||
| @@ -250,7 +255,7 @@ export async function startTraefikHTTPProxy( | ||||
| 						image: 'traefik:v2.6', | ||||
| 						command: [ | ||||
| 							`--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', | ||||
| 							'--log.level=error' | ||||
| 						], | ||||
| @@ -359,7 +364,7 @@ export async function startTraefikProxy(engine: string): Promise<void> { | ||||
| 			--entrypoints.websecure.address=:443 \ | ||||
| 			--providers.docker=true \ | ||||
| 			--providers.docker.exposedbydefault=false \ | ||||
| 			--providers.http.endpoint=${coolifyEndpoint} \ | ||||
| 			--providers.http.endpoint=${mainTraefikEndpoint} \ | ||||
| 			--providers.http.pollTimeout=5s \ | ||||
| 			--certificatesresolvers.letsencrypt.acme.httpchallenge=true \ | ||||
| 			--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
	 Andras Bacsai
					Andras Bacsai