From 9837ae359f77556f737c36f5bab27a7cf27bd1af Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 17 Jan 2023 10:35:04 +0100 Subject: [PATCH] feat: init h2c (http2/grpc) support --- .../migration.sql | 24 ++ apps/api/prisma/schema.prisma | 1 + .../routes/api/v1/applications/handlers.ts | 14 +- .../src/routes/api/v1/applications/types.ts | 230 ++++++----- .../src/routes/webhooks/traefik/handlers.ts | 373 ++++++++++++------ .../src/routes/applications/[id]/+page.svelte | 14 + .../migration.sql | 24 ++ apps/server/prisma/schema.prisma | 1 + .../src/trpc/routers/applications/index.ts | 7 +- .../src/routes/applications/[id]/index.svelte | 15 + 10 files changed, 469 insertions(+), 234 deletions(-) create mode 100644 apps/api/prisma/migrations/20230117092356_http2_protocol/migration.sql create mode 100644 apps/server/prisma/migrations/20230117092356_http2_protocol/migration.sql diff --git a/apps/api/prisma/migrations/20230117092356_http2_protocol/migration.sql b/apps/api/prisma/migrations/20230117092356_http2_protocol/migration.sql new file mode 100644 index 000000000..7b24ac820 --- /dev/null +++ b/apps/api/prisma/migrations/20230117092356_http2_protocol/migration.sql @@ -0,0 +1,24 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ApplicationSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT NOT NULL, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "debug" BOOLEAN NOT NULL DEFAULT false, + "previews" BOOLEAN NOT NULL DEFAULT false, + "autodeploy" BOOLEAN NOT NULL DEFAULT true, + "isBot" BOOLEAN NOT NULL DEFAULT false, + "isPublicRepository" BOOLEAN NOT NULL DEFAULT false, + "isDBBranching" BOOLEAN NOT NULL DEFAULT false, + "isCustomSSL" BOOLEAN NOT NULL DEFAULT false, + "isHttp2" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ApplicationSettings" ("applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt") SELECT "applicationId", "autodeploy", "createdAt", "debug", "dualCerts", "id", "isBot", "isCustomSSL", "isDBBranching", "isPublicRepository", "previews", "updatedAt" FROM "ApplicationSettings"; +DROP TABLE "ApplicationSettings"; +ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings"; +CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 1e9b75aa5..bb76d83b0 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -186,6 +186,7 @@ model ApplicationSettings { isPublicRepository Boolean @default(false) isDBBranching Boolean @default(false) isCustomSSL Boolean @default(false) + isHttp2 Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt application Application @relation(fields: [applicationId], references: [id]) diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 494bbe6f0..94cf19e95 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -503,14 +503,24 @@ export async function saveApplicationSettings( projectId, isBot, isDBBranching, - isCustomSSL + isCustomSSL, + isHttp2 } = request.body; await prisma.application.update({ where: { id }, data: { fqdn: isBot ? null : undefined, settings: { - update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } + update: { + debug, + previews, + dualCerts, + autodeploy, + isBot, + isDBBranching, + isCustomSSL, + isHttp2 + } } }, include: { destinationDocker: true } diff --git a/apps/api/src/routes/api/v1/applications/types.ts b/apps/api/src/routes/api/v1/applications/types.ts index ee8d19580..517194bd6 100644 --- a/apps/api/src/routes/api/v1/applications/types.ts +++ b/apps/api/src/routes/api/v1/applications/types.ts @@ -1,154 +1,170 @@ -import type { OnlyId } from "../../../../types"; +import type { OnlyId } from '../../../../types'; export interface SaveApplication extends OnlyId { - Body: { - name: string, - buildPack: string, - fqdn: string, - port: number, - exposePort: number, - installCommand: string, - buildCommand: string, - startCommand: string, - baseDirectory: string, - publishDirectory: string, - pythonWSGI: string, - pythonModule: string, - pythonVariable: string, - dockerFileLocation: string, - denoMainFile: string, - denoOptions: string, - baseImage: string, - gitCommitHash: string, - baseBuildImage: string, - deploymentType: string, - baseDatabaseBranch: string, - dockerComposeFile: string, - dockerComposeFileLocation: string, - dockerComposeConfiguration: string, - simpleDockerfile: string, - dockerRegistryImageName: string - } + Body: { + name: string; + buildPack: string; + fqdn: string; + port: number; + exposePort: number; + installCommand: string; + buildCommand: string; + startCommand: string; + baseDirectory: string; + publishDirectory: string; + pythonWSGI: string; + pythonModule: string; + pythonVariable: string; + dockerFileLocation: string; + denoMainFile: string; + denoOptions: string; + baseImage: string; + gitCommitHash: string; + baseBuildImage: string; + deploymentType: string; + baseDatabaseBranch: string; + dockerComposeFile: string; + dockerComposeFileLocation: string; + dockerComposeConfiguration: string; + simpleDockerfile: string; + dockerRegistryImageName: string; + }; } export interface SaveApplicationSettings extends OnlyId { - Querystring: { domain: string; }; - Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean }; + Querystring: { domain: string }; + Body: { + debug: boolean; + previews: boolean; + dualCerts: boolean; + autodeploy: boolean; + branch: string; + projectId: number; + isBot: boolean; + isDBBranching: boolean; + isCustomSSL: boolean; + isHttp2: boolean; + }; } export interface DeleteApplication extends OnlyId { - Querystring: { domain: string; }; - Body: { force: boolean } + Querystring: { domain: string }; + Body: { force: boolean }; } export interface CheckDomain extends OnlyId { - Querystring: { domain: string; }; + Querystring: { domain: string }; } export interface CheckDNS extends OnlyId { - Querystring: { domain: string; }; - Body: { - exposePort: number, - fqdn: string, - forceSave: boolean, - dualCerts: boolean - } + Querystring: { domain: string }; + Body: { + exposePort: number; + fqdn: string; + forceSave: boolean; + dualCerts: boolean; + }; } export interface DeployApplication { - Querystring: { domain: string } - Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean } + Querystring: { domain: string }; + Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean }; } export interface GetImages { - Body: { buildPack: string, deploymentType: string } + Body: { buildPack: string; deploymentType: string }; } export interface SaveApplicationSource extends OnlyId { - Body: { gitSourceId?: string | null, forPublic?: boolean, type?: string, simpleDockerfile?: string } + Body: { + gitSourceId?: string | null; + forPublic?: boolean; + type?: string; + simpleDockerfile?: string; + }; } export interface CheckRepository extends OnlyId { - Querystring: { repository: string, branch: string } + Querystring: { repository: string; branch: string }; } export interface SaveDestination extends OnlyId { - Body: { destinationId: string } + Body: { destinationId: string }; } export interface SaveSecret extends OnlyId { - Body: { - name: string, - value: string, - isBuildSecret: boolean, - previewSecret: boolean, - isNew: boolean - } + Body: { + name: string; + value: string; + isBuildSecret: boolean; + previewSecret: boolean; + isNew: boolean; + }; } export interface DeleteSecret extends OnlyId { - Body: { name: string } + Body: { name: string }; } export interface SaveStorage extends OnlyId { - Body: { - path: string, - newStorage: boolean, - storageId: string - } + Body: { + path: string; + newStorage: boolean; + storageId: string; + }; } export interface DeleteStorage extends OnlyId { - Body: { - path: string, - } + Body: { + path: string; + }; } export interface GetApplicationLogs { - Params: { - id: string, - containerId: string - } - Querystring: { - since: number, - } + Params: { + id: string; + containerId: string; + }; + Querystring: { + since: number; + }; } export interface GetBuilds extends OnlyId { - Querystring: { - buildId: string - skip: number, - } + Querystring: { + buildId: string; + skip: number; + }; } export interface GetBuildIdLogs { - Params: { - id: string, - buildId: string - }, - Querystring: { - sequence: number - } + Params: { + id: string; + buildId: string; + }; + Querystring: { + sequence: number; + }; } export interface SaveDeployKey extends OnlyId { - Body: { - deployKeyId: number - } + Body: { + deployKeyId: number; + }; } export interface CancelDeployment { - Body: { - buildId: string, - applicationId: string - } + Body: { + buildId: string; + applicationId: string; + }; } export interface DeployApplication extends OnlyId { - Body: { - pullmergeRequestId: string | null, - branch: string, - forceRebuild?: boolean - } + Body: { + pullmergeRequestId: string | null; + branch: string; + forceRebuild?: boolean; + }; } export interface StopPreviewApplication extends OnlyId { - Body: { - pullmergeRequestId: string | null, - } + Body: { + pullmergeRequestId: string | null; + }; } export interface RestartPreviewApplication { - Params: { - id: string, - pullmergeRequestId: string | null, - } + Params: { + id: string; + pullmergeRequestId: string | null; + }; } export interface RestartApplication { - Params: { - id: string, - }, - Body: { - imageId: string | null, - } -} \ No newline at end of file + Params: { + id: string; + }; + Body: { + imageId: string | null; + }; +} diff --git a/apps/api/src/routes/webhooks/traefik/handlers.ts b/apps/api/src/routes/webhooks/traefik/handlers.ts index e46857e7e..25057defc 100644 --- a/apps/api/src/routes/webhooks/traefik/handlers.ts +++ b/apps/api/src/routes/webhooks/traefik/handlers.ts @@ -1,29 +1,38 @@ -import { FastifyRequest } from "fastify"; -import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common"; -import { getTemplates } from "../../../lib/services"; -import { OnlyId } from "../../../types"; +import { FastifyRequest } from 'fastify'; +import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common'; +import { getTemplates } from '../../../lib/services'; +import { OnlyId } from '../../../types'; -function generateServices(serviceId, containerId, port) { +function generateServices(serviceId, containerId, port, isHttp2 = false) { return { [serviceId]: { loadbalancer: { servers: [ { - url: `http://${containerId}:${port}` + url: `${isHttp2 ? 'h2c' : 'http'}://${containerId}:${port}` } ] } } - } + }; } -function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, isDualCerts, isCustomSSL) { +function generateRouters( + serviceId, + domain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + isDualCerts, + isCustomSSL +) { let http: any = { entrypoints: ['web'], rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, service: `${serviceId}`, priority: 2, middlewares: [] - } + }; let https: any = { entrypoints: ['websecure'], rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, @@ -33,14 +42,14 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is certresolver: 'letsencrypt' }, middlewares: [] - } + }; let httpWWW: any = { entrypoints: ['web'], rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, service: `${serviceId}`, priority: 2, middlewares: [] - } + }; let httpsWWW: any = { entrypoints: ['websecure'], rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, @@ -50,7 +59,7 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is certresolver: 'letsencrypt' }, middlewares: [] - } + }; // 2. http + non-www only if (!isHttps && !isWWW) { https.middlewares.push('redirect-to-http'); @@ -58,19 +67,19 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is httpWWW.middlewares.push('redirect-to-non-www'); httpsWWW.middlewares.push('redirect-to-non-www'); - delete https.tls - delete httpsWWW.tls + delete https.tls; + delete httpsWWW.tls; } - // 3. http + www only + // 3. http + www only if (!isHttps && isWWW) { https.middlewares.push('redirect-to-http'); httpsWWW.middlewares.push('redirect-to-http'); http.middlewares.push('redirect-to-www'); https.middlewares.push('redirect-to-www'); - delete https.tls - delete httpsWWW.tls + delete https.tls; + delete httpsWWW.tls; } // 5. https + non-www only if (isHttps && !isWWW) { @@ -86,17 +95,17 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is httpsWWW.tls = true; } else { https.tls = true; - delete httpsWWW.tls.certresolver + delete httpsWWW.tls.certresolver; httpsWWW.tls.domains = { main: domain - } + }; } } else { if (!isDualCerts) { - delete httpsWWW.tls.certresolver + delete httpsWWW.tls.certresolver; httpsWWW.tls.domains = { main: domain - } + }; } } } @@ -114,17 +123,17 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is httpsWWW.tls = true; } else { httpsWWW.tls = true; - delete https.tls.certresolver + delete https.tls.certresolver; https.tls.domains = { main: domain - } + }; } } else { if (!isDualCerts) { - delete https.tls.certresolver + delete https.tls.certresolver; https.tls.domains = { main: domain - } + }; } } } @@ -132,8 +141,8 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is [`${serviceId}-${pathPrefix}`]: { ...http }, [`${serviceId}-${pathPrefix}-secure`]: { ...https }, [`${serviceId}-${pathPrefix}-www`]: { ...httpWWW }, - [`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }, - } + [`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW } + }; } export async function proxyConfiguration(request: FastifyRequest, remote: boolean = false) { const traefik = { @@ -174,26 +183,26 @@ export async function proxyConfiguration(request: FastifyRequest, remote const coolifySettings = await prisma.setting.findFirst(); if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) { traefik.http.routers['catchall-http'] = { - entrypoints: ["web"], - rule: "HostRegexp(`{catchall:.*}`)", - service: "noop", + entrypoints: ['web'], + rule: 'HostRegexp(`{catchall:.*}`)', + service: 'noop', priority: 1, - middlewares: ["redirect-regexp"] - } + middlewares: ['redirect-regexp'] + }; traefik.http.routers['catchall-https'] = { - entrypoints: ["websecure"], - rule: "HostRegexp(`{catchall:.*}`)", - service: "noop", + entrypoints: ['websecure'], + rule: 'HostRegexp(`{catchall:.*}`)', + service: 'noop', priority: 1, - middlewares: ["redirect-regexp"] - } + middlewares: ['redirect-regexp'] + }; traefik.http.middlewares['redirect-regexp'] = { redirectregex: { regex: '(.*)', replacement: coolifySettings.proxyDefaultRedirect, permanent: false } - } + }; traefik.http.services['noop'] = { loadBalancer: { servers: [ @@ -202,25 +211,41 @@ export async function proxyConfiguration(request: FastifyRequest, remote } ] } - } + }; } const sslpath = '/etc/traefik/acme/custom'; - let certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } }) + let certificates = await prisma.certificate.findMany({ + where: { + team: { + applications: { some: { settings: { isCustomSSL: true } } }, + destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } + } + } + }); if (remote) { - certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } }) + certificates = await prisma.certificate.findMany({ + where: { + team: { + applications: { some: { settings: { isCustomSSL: true } } }, + destinationDocker: { + some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } + } + } + } + }); } - let parsedCertificates = [] + let parsedCertificates = []; for (const certificate of certificates) { parsedCertificates.push({ certFile: `${sslpath}/${certificate.id}-cert.pem`, keyFile: `${sslpath}/${certificate.id}-key.pem` - }) + }); } if (parsedCertificates.length > 0) { - traefik.tls.certificates = parsedCertificates + traefik.tls.certificates = parsedCertificates; } let applications = []; @@ -236,7 +261,7 @@ export async function proxyConfiguration(request: FastifyRequest, remote destinationDocker: true, persistentStorage: true, serviceSecret: true, - serviceSetting: true, + serviceSetting: true }, orderBy: { createdAt: 'desc' } }); @@ -251,23 +276,25 @@ export async function proxyConfiguration(request: FastifyRequest, remote destinationDocker: true, persistentStorage: true, serviceSecret: true, - serviceSetting: true, + serviceSetting: true }, - orderBy: { createdAt: 'desc' }, + orderBy: { createdAt: 'desc' } }); } - if (applications.length > 0) { - const dockerIds = new Set() - const runningContainers = {} + const dockerIds = new Set(); + const runningContainers = {}; applications.forEach((app) => dockerIds.add(app.destinationDocker.id)); for (const dockerId of dockerIds) { - const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` }) + const { stdout: container } = await executeCommand({ + dockerId, + command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` + }); if (container) { const containersArray = container.trim().split('\n'); if (containersArray.length > 0) { - runningContainers[dockerId] = containersArray + runningContainers[dockerId] = containersArray; } } } @@ -289,38 +316,54 @@ export async function proxyConfiguration(request: FastifyRequest, remote if ( !runningContainers[destinationDockerId] || runningContainers[destinationDockerId].length === 0 || - runningContainers[destinationDockerId].filter((container) => container.startsWith(id)).length === 0 + runningContainers[destinationDockerId].filter((container) => container.startsWith(id)) + .length === 0 ) { - continue + continue; } if (buildPack === 'compose') { - const services = Object.entries(JSON.parse(dockerComposeConfiguration)) + const services = Object.entries(JSON.parse(dockerComposeConfiguration)); if (services.length > 0) { for (const service of services) { - const [key, value] = service + const [key, value] = service; if (key && value) { if (!value.fqdn || !value.port) { continue; } - const { fqdn, port } = value - const containerId = `${id}-${key}` + const { fqdn, port } = value; + const containerId = `${id}-${key}`; const domain = getDomain(fqdn); const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const pathPrefix = '/' + const pathPrefix = '/'; const isCustomSSL = false; const dualCerts = false; - const serviceId = `${id}-${port || 'default'}` + const serviceId = `${id}-${port || 'default'}`; - traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } - traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, containerId, port) } + traefik.http.routers = { + ...traefik.http.routers, + ...generateRouters( + serviceId, + domain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + dualCerts, + isCustomSSL + ) + }; + traefik.http.services = { + ...traefik.http.services, + ...generateServices(serviceId, containerId, port) + }; } } } continue; } - const { previews, dualCerts, isCustomSSL } = settings; + const { previews, dualCerts, isCustomSSL, isHttp2 } = settings; const { network, id: dockerId } = destinationDocker; if (!fqdn) { continue; @@ -329,12 +372,30 @@ export async function proxyConfiguration(request: FastifyRequest, remote const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const pathPrefix = '/' - const serviceId = `${id}-${port || 'default'}` - traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } - traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) } + const pathPrefix = '/'; + const serviceId = `${id}-${port || 'default'}`; + traefik.http.routers = { + ...traefik.http.routers, + ...generateRouters( + serviceId, + domain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + dualCerts, + isCustomSSL + ) + }; + traefik.http.services = { + ...traefik.http.services, + ...generateServices(serviceId, id, port, isHttp2) + }; if (previews) { - const { stdout } = await executeCommand({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` }) + const { stdout } = await executeCommand({ + dockerId, + command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` + }); if (stdout) { const containers = stdout .trim() @@ -343,44 +404,57 @@ export async function proxyConfiguration(request: FastifyRequest, remote .map((c) => c.replace(/"/g, '')); if (containers.length > 0) { for (const container of containers) { - const previewDomain = `${container.split('-')[1]}${coolifySettings.previewSeparator}${domain}`; + const previewDomain = `${container.split('-')[1]}${ + coolifySettings.previewSeparator + }${domain}`; const nakedDomain = previewDomain.replace(/^www\./, ''); - const pathPrefix = '/' - const serviceId = `${container}-${port || 'default'}` - traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } - traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) } + const pathPrefix = '/'; + const serviceId = `${container}-${port || 'default'}`; + traefik.http.routers = { + ...traefik.http.routers, + ...generateRouters( + serviceId, + previewDomain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + dualCerts, + isCustomSSL + ) + }; + traefik.http.services = { + ...traefik.http.services, + ...generateServices(serviceId, container, port, isHttp2) + }; } } } } } catch (error) { - console.log(error) + console.log(error); } } } if (services.length > 0) { - const dockerIds = new Set() - const runningContainers = {} + const dockerIds = new Set(); + const runningContainers = {}; services.forEach((app) => dockerIds.add(app.destinationDocker.id)); for (const dockerId of dockerIds) { - const { stdout: container } = await executeCommand({ dockerId, command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` }) + const { stdout: container } = await executeCommand({ + dockerId, + command: `docker container ls --filter 'label=coolify.managed=true' --format '{{ .Names}}'` + }); if (container) { const containersArray = container.trim().split('\n'); if (containersArray.length > 0) { - runningContainers[dockerId] = containersArray + runningContainers[dockerId] = containersArray; } } } for (const service of services) { try { - let { - fqdn, - id, - type, - destinationDockerId, - dualCerts, - serviceSetting - } = service; + let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service; if (!fqdn) { continue; } @@ -392,7 +466,7 @@ export async function proxyConfiguration(request: FastifyRequest, remote runningContainers[destinationDockerId].length === 0 || !runningContainers[destinationDockerId].includes(id) ) { - continue + continue; } const templates = await getTemplates(); let found = templates.find((a) => a.type === type); @@ -401,88 +475,143 @@ export async function proxyConfiguration(request: FastifyRequest, remote } found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id)); for (const oneService of Object.keys(found.services)) { - const isDomainConfiguration = found?.services[oneService]?.proxy?.filter(p => p.domain) ?? []; + const isDomainConfiguration = + found?.services[oneService]?.proxy?.filter((p) => p.domain) ?? []; if (isDomainConfiguration.length > 0) { const { proxy } = found.services[oneService]; for (let configuration of proxy) { if (configuration.domain) { - const setting = serviceSetting.find((a) => a.variableName === configuration.domain); + const setting = serviceSetting.find( + (a) => a.variableName === configuration.domain + ); if (setting) { - configuration.domain = configuration.domain.replace(configuration.domain, setting.value); + configuration.domain = configuration.domain.replace( + configuration.domain, + setting.value + ); } } - const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port') + const foundPortVariable = serviceSetting.find( + (a) => a.name.toLowerCase() === 'port' + ); if (foundPortVariable) { - configuration.port = foundPortVariable.value + configuration.port = foundPortVariable.value; } let port, pathPrefix, customDomain; if (configuration) { port = configuration?.port; pathPrefix = configuration?.pathPrefix || '/'; - customDomain = configuration?.domain + customDomain = configuration?.domain; } if (customDomain) { - fqdn = customDomain + fqdn = customDomain; } else { - fqdn = service.fqdn + fqdn = service.fqdn; } const domain = getDomain(fqdn); const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); const isCustomSSL = false; - const serviceId = `${oneService}-${port || 'default'}` - traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } - traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, oneService, port) } + const serviceId = `${oneService}-${port || 'default'}`; + traefik.http.routers = { + ...traefik.http.routers, + ...generateRouters( + serviceId, + domain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + dualCerts, + isCustomSSL + ) + }; + traefik.http.services = { + ...traefik.http.services, + ...generateServices(serviceId, oneService, port) + }; } } else { if (found.services[oneService].ports && found.services[oneService].ports.length > 0) { for (let [index, port] of found.services[oneService].ports.entries()) { if (port == 22) continue; if (index === 0) { - const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port') + const foundPortVariable = serviceSetting.find( + (a) => a.name.toLowerCase() === 'port' + ); if (foundPortVariable) { - port = foundPortVariable.value + port = foundPortVariable.value; } } const domain = getDomain(fqdn); const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const pathPrefix = '/' - const isCustomSSL = false - const serviceId = `${oneService}-${port || 'default'}` - traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } - traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) } + const pathPrefix = '/'; + const isCustomSSL = false; + const serviceId = `${oneService}-${port || 'default'}`; + traefik.http.routers = { + ...traefik.http.routers, + ...generateRouters( + serviceId, + domain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + dualCerts, + isCustomSSL + ) + }; + traefik.http.services = { + ...traefik.http.services, + ...generateServices(serviceId, id, port) + }; } } } } } catch (error) { - console.log(error) + console.log(error); } } } if (!remote) { const { fqdn, dualCerts } = await prisma.setting.findFirst(); if (!fqdn) { - return + return; } const domain = getDomain(fqdn); const nakedDomain = domain.replace(/^www\./, ''); const isHttps = fqdn.startsWith('https://'); const isWWW = fqdn.includes('www.'); - const id = isDev ? 'host.docker.internal' : 'coolify' - const container = isDev ? 'host.docker.internal' : 'coolify' - const port = 3000 - const pathPrefix = '/' - const isCustomSSL = false - const serviceId = `${id}-${port || 'default'}` - traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } - traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) } + const id = isDev ? 'host.docker.internal' : 'coolify'; + const container = isDev ? 'host.docker.internal' : 'coolify'; + const port = 3000; + const pathPrefix = '/'; + const isCustomSSL = false; + const serviceId = `${id}-${port || 'default'}`; + traefik.http.routers = { + ...traefik.http.routers, + ...generateRouters( + serviceId, + domain, + nakedDomain, + pathPrefix, + isHttps, + isWWW, + dualCerts, + isCustomSSL + ) + }; + traefik.http.services = { + ...traefik.http.services, + ...generateServices(serviceId, container, port) + }; } } catch (error) { - console.log(error) + console.log(error); } finally { if (Object.keys(traefik.http.routers).length === 0) { traefik.http.routers = null; @@ -496,9 +625,9 @@ export async function proxyConfiguration(request: FastifyRequest, remote export async function otherProxyConfiguration(request: FastifyRequest) { try { - const { id } = request.query + const { id } = request.query; if (id) { - const { privatePort, publicPort, type, address = id } = request.query + const { privatePort, publicPort, type, address = id } = request.query; let traefik = {}; if (publicPort && type && privatePort) { if (type === 'tcp') { @@ -559,18 +688,18 @@ export async function otherProxyConfiguration(request: FastifyRequest !isDisabled && changeSettings('dualCerts')} /> +
+ changeSettings('isHttp2')} + /> +
{#if isHttps && application.buildPack !== 'compose'}
{ - const { id, debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } = + const { id, debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL, isHttp2 } = input; await prisma.application.update({ where: { id }, data: { fqdn: isBot ? null : undefined, settings: { - update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } + update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL, isHttp2 } } }, include: { destinationDocker: true } diff --git a/apps/ui/src/routes/applications/[id]/index.svelte b/apps/ui/src/routes/applications/[id]/index.svelte index fbf617992..bae564c46 100644 --- a/apps/ui/src/routes/applications/[id]/index.svelte +++ b/apps/ui/src/routes/applications/[id]/index.svelte @@ -79,6 +79,7 @@ let isBot = application.settings?.isBot; let isDBBranching = application.settings?.isDBBranching; let htmlUrl = application.gitSource?.htmlUrl; + let isHttp2 = application.settings.isHttp2; let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null; let dockerComposeServices: any[] = []; @@ -195,6 +196,9 @@ if (name === 'isDBBranching') { isDBBranching = !isDBBranching; } + if (name === 'isHttp2') { + isHttp2 = !isHttp2; + } try { await post(`/applications/${id}/settings`, { previews, @@ -204,6 +208,7 @@ autodeploy, isDBBranching, isCustomSSL, + isHttp2, branch: application.branch, projectId: application.projectId }); @@ -746,6 +751,16 @@ on:click={() => !isDisabled && changeSettings('dualCerts')} />
+
+ changeSettings('isHttp2')} + /> +
{#if isHttps && application.buildPack !== 'compose'}