coolify/src/lib/haproxy/configuration.ts

285 lines
7.7 KiB
TypeScript
Raw Normal View History

2022-02-28 23:08:54 +00:00
import { dev } from '$app/env';
2022-04-06 18:40:25 +00:00
import got, { type Got } from 'got';
2022-02-28 23:08:54 +00:00
import * as db from '$lib/database';
2022-04-12 08:12:46 +00:00
import mustache from 'mustache';
import crypto from 'crypto';
2022-02-28 23:08:54 +00:00
import { checkContainer, checkHAProxy } from '.';
2022-03-01 10:10:10 +00:00
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
import { supportedServiceTypesAndVersions } from '$lib/components/common';
2022-02-28 23:08:54 +00:00
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
2022-04-06 18:40:25 +00:00
const template = `program api
2022-02-28 23:08:54 +00:00
command /usr/bin/dataplaneapi -f /usr/local/etc/haproxy/dataplaneapi.hcl --userlist haproxy-dataplaneapi
no option start-on-reload
global
stats socket /var/run/api.sock user haproxy group haproxy mode 660 level admin expose-fd listeners
log stdout format raw local0 debug
defaults
mode http
log global
2022-04-12 16:21:10 +00:00
timeout http-request 120s
2022-02-28 23:08:54 +00:00
timeout connect 10s
2022-04-12 16:21:10 +00:00
timeout client 120s
timeout server 120s
2022-02-28 23:08:54 +00:00
userlist haproxy-dataplaneapi
user admin insecure-password "\${HAPROXY_PASSWORD}"
frontend http
mode http
bind :80
bind :443 ssl crt /usr/local/etc/haproxy/ssl/ alpn h2,http/1.1
acl is_certbot path_beg /.well-known/acme-challenge/
2022-03-31 18:55:58 +00:00
2022-02-28 23:08:54 +00:00
{{#applications}}
{{#isHttps}}
http-request redirect scheme https code ${
dev ? 302 : 301
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
{{/isHttps}}
http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} }
{{/applications}}
2022-03-31 18:55:58 +00:00
2022-03-01 10:10:10 +00:00
{{#services}}
{{#isHttps}}
http-request redirect scheme https code ${
dev ? 302 : 301
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
{{/isHttps}}
http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} }
{{/services}}
2022-03-31 18:55:58 +00:00
2022-03-01 10:10:10 +00:00
{{#coolify}}
{{#isHttps}}
http-request redirect scheme https code ${
dev ? 302 : 301
} if { hdr(host) -i {{domain}} } !{ ssl_fc }
{{/isHttps}}
http-request redirect location {{{redirectValue}}} code ${
dev ? 302 : 301
} if { req.hdr(host) -i {{redirectTo}} }
{{/coolify}}
2022-03-31 18:55:58 +00:00
2022-02-28 23:08:54 +00:00
use_backend backend-certbot if is_certbot
use_backend %[req.hdr(host),lower]
frontend stats
bind *:8404
stats enable
stats uri /
stats admin if TRUE
stats auth "\${HAPROXY_USERNAME}:\${HAPROXY_PASSWORD}"
backend backend-certbot
mode http
server certbot host.docker.internal:9080
{{#applications}}
2022-02-28 23:20:28 +00:00
{{#isRunning}}
2022-03-01 13:02:46 +00:00
# updatedAt={{updatedAt}}
2022-02-28 23:08:54 +00:00
backend {{domain}}
option forwardfor
2022-03-31 18:55:58 +00:00
{{#isHttps}}
http-request add-header X-Forwarded-Proto https
{{/isHttps}}
{{^isHttps}}
http-request add-header X-Forwarded-Proto http
{{/isHttps}}
http-request add-header X-Forwarded-Host %[req.hdr(host),lower]
2022-03-25 09:48:11 +00:00
server {{id}} {{id}}:{{port}}
2022-02-28 23:20:28 +00:00
{{/isRunning}}
2022-02-28 23:08:54 +00:00
{{/applications}}
2022-03-01 10:10:10 +00:00
2022-02-28 23:12:54 +00:00
{{#services}}
2022-03-01 10:10:10 +00:00
{{#isRunning}}
2022-03-01 13:02:46 +00:00
# updatedAt={{updatedAt}}
2022-02-28 23:12:54 +00:00
backend {{domain}}
option forwardfor
2022-03-31 18:55:58 +00:00
{{#isHttps}}
http-request add-header X-Forwarded-Proto https
{{/isHttps}}
{{^isHttps}}
http-request add-header X-Forwarded-Proto http
{{/isHttps}}
http-request add-header X-Forwarded-Host %[req.hdr(host),lower]
2022-03-25 09:48:11 +00:00
server {{id}} {{id}}:{{port}}
2022-03-01 10:10:10 +00:00
{{/isRunning}}
2022-02-28 23:12:54 +00:00
{{/services}}
2022-03-01 10:10:10 +00:00
{{#coolify}}
backend {{domain}}
option forwardfor
option httpchk GET /undead.json
2022-03-31 18:55:58 +00:00
{{#isHttps}}
http-request add-header X-Forwarded-Proto https
{{/isHttps}}
{{^isHttps}}
http-request add-header X-Forwarded-Proto http
{{/isHttps}}
http-request add-header X-Forwarded-Host %[req.hdr(host),lower]
2022-03-01 10:10:10 +00:00
server {{id}} {{id}}:{{port}} check fall 10
{{/coolify}}
2022-02-28 23:08:54 +00:00
`;
2022-04-06 18:40:25 +00:00
export async function haproxyInstance(): Promise<Got> {
2022-02-28 23:08:54 +00:00
const { proxyPassword } = await db.listSettings();
return got.extend({
prefixUrl: url,
username: 'admin',
password: proxyPassword
});
}
2022-04-06 18:40:25 +00:00
export async function configureHAProxy(): Promise<void> {
const haproxy = await haproxyInstance();
2022-03-31 22:07:29 +00:00
await checkHAProxy(haproxy);
2022-04-06 18:40:25 +00:00
const data = {
applications: [],
services: [],
coolify: []
};
const applications = await db.prisma.application.findMany({
include: { destinationDocker: true, settings: true }
});
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 db.prisma.service.findMany({
include: {
destinationDocker: true,
minio: true,
plausibleAnalytics: true,
vscodeserver: true,
wordpress: true,
2022-04-12 19:09:38 +00:00
ghost: true,
meiliSearch: true
2022-04-06 18:40:25 +00:00
}
});
for (const service of services) {
const { fqdn, id, type, destinationDocker, destinationDockerId, updatedAt } = service;
if (destinationDockerId) {
const { engine } = destinationDocker;
2022-04-06 23:03:13 +00:00
const found = supportedServiceTypesAndVersions.find((a) => a.name === type);
2022-04-06 18:40:25 +00:00
if (found) {
const port = found.ports.main;
const publicPort = service[type]?.publicPort;
2022-03-01 14:22:11 +00:00
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) {
2022-04-06 18:40:25 +00:00
data.services.push({
2022-03-01 14:22:11 +00:00
id,
2022-04-06 18:40:25 +00:00
port,
publicPort,
2022-03-01 14:22:11 +00:00
domain,
isRunning,
isHttps,
redirectValue,
2022-04-03 15:49:41 +00:00
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain,
2022-03-01 14:22:11 +00:00
updatedAt: updatedAt.getTime()
});
}
2022-03-01 10:10:10 +00:00
}
}
}
2022-04-06 18:40:25 +00:00
}
const { fqdn } = await db.prisma.setting.findFirst();
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]`;
data.coolify.push({
id: dev ? 'host.docker.internal' : 'coolify',
port: 3000,
domain,
isHttps,
redirectValue,
redirectTo: isWWW ? domain.replace('www.', '') : 'www.' + domain
2022-03-01 14:22:11 +00:00
});
2022-04-12 08:12:46 +00:00
}
const output = mustache.render(template, data);
const newHash = crypto.createHash('md5').update(output).digest('hex');
const { proxyHash, id } = await db.listSettings();
if (proxyHash !== newHash) {
await db.prisma.setting.update({ where: { id }, data: { proxyHash: newHash } });
await haproxy.post(`v2/services/haproxy/configuration/raw`, {
searchParams: {
skip_version: true
},
body: output,
headers: {
'Content-Type': 'text/plain'
2022-03-01 14:22:11 +00:00
}
2022-04-12 08:12:46 +00:00
});
2022-03-01 10:10:10 +00:00
}
2022-02-28 23:08:54 +00:00
}