Merge pull request #858 from coollabsio/next

v3.12.12
This commit is contained in:
Andras Bacsai 2023-01-17 12:33:51 +01:00 committed by GitHub
commit becf37b676
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 563 additions and 247 deletions

View File

@ -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;

View File

@ -186,6 +186,7 @@ model ApplicationSettings {
isPublicRepository Boolean @default(false) isPublicRepository Boolean @default(false)
isDBBranching Boolean @default(false) isDBBranching Boolean @default(false)
isCustomSSL Boolean @default(false) isCustomSSL Boolean @default(false)
isHttp2 Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id]) application Application @relation(fields: [applicationId], references: [id])

View File

@ -53,12 +53,22 @@ export default async function (data) {
value['environment'] = [...environment, ...envs]; value['environment'] = [...environment, ...envs];
let build = typeof value['build'] === 'undefined' ? [] : value['build']; let build = typeof value['build'] === 'undefined' ? [] : value['build'];
if (Object.keys(build).length > 0) { if (typeof build === 'string') {
build = Object.entries(build).map(([key, value]) => `${key}=${value}`); build = { context: build };
}
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
let finalArgs = [...buildEnvs];
if (Object.keys(buildArgs).length > 0) {
for (const arg of buildArgs) {
const [key, _] = arg.split('=');
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
finalArgs.push(arg);
}
}
} }
value['build'] = { value['build'] = {
...build, ...build,
args: [...(build?.args || []), ...buildEnvs] args: finalArgs
}; };
value['labels'] = labels; value['labels'] = labels;

View File

@ -19,7 +19,7 @@ import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common
import { scheduler } from './scheduler'; import { scheduler } from './scheduler';
import type { ExecaChildProcess } from 'execa'; import type { ExecaChildProcess } from 'execa';
export const version = '3.12.11'; export const version = '3.12.12';
export const isDev = process.env.NODE_ENV === 'development'; export const isDev = process.env.NODE_ENV === 'development';
export const sentryDSN = export const sentryDSN =
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216'; 'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
@ -714,8 +714,10 @@ export async function startTraefikProxy(id: string): Promise<void> {
--network coolify-infra \ --network coolify-infra \
-p "80:80" \ -p "80:80" \
-p "443:443" \ -p "443:443" \
${isDev ? '-p "8080:8080"' : ''} \
--name coolify-proxy \ --name coolify-proxy \
-d ${defaultTraefikImage} \ -d ${defaultTraefikImage} \
${isDev ? '--api.insecure=true' : ''} \
--entrypoints.web.address=:80 \ --entrypoints.web.address=:80 \
--entrypoints.web.forwardedHeaders.insecure=true \ --entrypoints.web.forwardedHeaders.insecure=true \
--entrypoints.websecure.address=:443 \ --entrypoints.websecure.address=:443 \

View File

@ -503,14 +503,24 @@ export async function saveApplicationSettings(
projectId, projectId,
isBot, isBot,
isDBBranching, isDBBranching,
isCustomSSL isCustomSSL,
isHttp2
} = request.body; } = request.body;
await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { data: {
fqdn: isBot ? null : undefined, fqdn: isBot ? null : undefined,
settings: { settings: {
update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } update: {
debug,
previews,
dualCerts,
autodeploy,
isBot,
isDBBranching,
isCustomSSL,
isHttp2
}
} }
}, },
include: { destinationDocker: true } include: { destinationDocker: true }

View File

@ -1,154 +1,170 @@
import type { OnlyId } from "../../../../types"; import type { OnlyId } from '../../../../types';
export interface SaveApplication extends OnlyId { export interface SaveApplication extends OnlyId {
Body: { Body: {
name: string, name: string;
buildPack: string, buildPack: string;
fqdn: string, fqdn: string;
port: number, port: number;
exposePort: number, exposePort: number;
installCommand: string, installCommand: string;
buildCommand: string, buildCommand: string;
startCommand: string, startCommand: string;
baseDirectory: string, baseDirectory: string;
publishDirectory: string, publishDirectory: string;
pythonWSGI: string, pythonWSGI: string;
pythonModule: string, pythonModule: string;
pythonVariable: string, pythonVariable: string;
dockerFileLocation: string, dockerFileLocation: string;
denoMainFile: string, denoMainFile: string;
denoOptions: string, denoOptions: string;
baseImage: string, baseImage: string;
gitCommitHash: string, gitCommitHash: string;
baseBuildImage: string, baseBuildImage: string;
deploymentType: string, deploymentType: string;
baseDatabaseBranch: string, baseDatabaseBranch: string;
dockerComposeFile: string, dockerComposeFile: string;
dockerComposeFileLocation: string, dockerComposeFileLocation: string;
dockerComposeConfiguration: string, dockerComposeConfiguration: string;
simpleDockerfile: string, simpleDockerfile: string;
dockerRegistryImageName: string dockerRegistryImageName: string;
} };
} }
export interface SaveApplicationSettings extends OnlyId { export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; }; Querystring: { domain: string };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; isBot: boolean; isDBBranching: boolean, isCustomSSL: boolean }; 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 { export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; }; Querystring: { domain: string };
Body: { force: boolean } Body: { force: boolean };
} }
export interface CheckDomain extends OnlyId { export interface CheckDomain extends OnlyId {
Querystring: { domain: string; }; Querystring: { domain: string };
} }
export interface CheckDNS extends OnlyId { export interface CheckDNS extends OnlyId {
Querystring: { domain: string; }; Querystring: { domain: string };
Body: { Body: {
exposePort: number, exposePort: number;
fqdn: string, fqdn: string;
forceSave: boolean, forceSave: boolean;
dualCerts: boolean dualCerts: boolean;
} };
} }
export interface DeployApplication { export interface DeployApplication {
Querystring: { domain: string } Querystring: { domain: string };
Body: { pullmergeRequestId: string | null, branch: string, forceRebuild?: boolean } Body: { pullmergeRequestId: string | null; branch: string; forceRebuild?: boolean };
} }
export interface GetImages { export interface GetImages {
Body: { buildPack: string, deploymentType: string } Body: { buildPack: string; deploymentType: string };
} }
export interface SaveApplicationSource extends OnlyId { 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 { export interface CheckRepository extends OnlyId {
Querystring: { repository: string, branch: string } Querystring: { repository: string; branch: string };
} }
export interface SaveDestination extends OnlyId { export interface SaveDestination extends OnlyId {
Body: { destinationId: string } Body: { destinationId: string };
} }
export interface SaveSecret extends OnlyId { export interface SaveSecret extends OnlyId {
Body: { Body: {
name: string, name: string;
value: string, value: string;
isBuildSecret: boolean, isBuildSecret: boolean;
previewSecret: boolean, previewSecret: boolean;
isNew: boolean isNew: boolean;
} };
} }
export interface DeleteSecret extends OnlyId { export interface DeleteSecret extends OnlyId {
Body: { name: string } Body: { name: string };
} }
export interface SaveStorage extends OnlyId { export interface SaveStorage extends OnlyId {
Body: { Body: {
path: string, path: string;
newStorage: boolean, newStorage: boolean;
storageId: string storageId: string;
} };
} }
export interface DeleteStorage extends OnlyId { export interface DeleteStorage extends OnlyId {
Body: { Body: {
path: string, path: string;
} };
} }
export interface GetApplicationLogs { export interface GetApplicationLogs {
Params: { Params: {
id: string, id: string;
containerId: string containerId: string;
} };
Querystring: { Querystring: {
since: number, since: number;
} };
} }
export interface GetBuilds extends OnlyId { export interface GetBuilds extends OnlyId {
Querystring: { Querystring: {
buildId: string buildId: string;
skip: number, skip: number;
} };
} }
export interface GetBuildIdLogs { export interface GetBuildIdLogs {
Params: { Params: {
id: string, id: string;
buildId: string buildId: string;
}, };
Querystring: { Querystring: {
sequence: number sequence: number;
} };
} }
export interface SaveDeployKey extends OnlyId { export interface SaveDeployKey extends OnlyId {
Body: { Body: {
deployKeyId: number deployKeyId: number;
} };
} }
export interface CancelDeployment { export interface CancelDeployment {
Body: { Body: {
buildId: string, buildId: string;
applicationId: string applicationId: string;
} };
} }
export interface DeployApplication extends OnlyId { export interface DeployApplication extends OnlyId {
Body: { Body: {
pullmergeRequestId: string | null, pullmergeRequestId: string | null;
branch: string, branch: string;
forceRebuild?: boolean forceRebuild?: boolean;
} };
} }
export interface StopPreviewApplication extends OnlyId { export interface StopPreviewApplication extends OnlyId {
Body: { Body: {
pullmergeRequestId: string | null, pullmergeRequestId: string | null;
} };
} }
export interface RestartPreviewApplication { export interface RestartPreviewApplication {
Params: { Params: {
id: string, id: string;
pullmergeRequestId: string | null, pullmergeRequestId: string | null;
} };
} }
export interface RestartApplication { export interface RestartApplication {
Params: { Params: {
id: string, id: string;
}, };
Body: { Body: {
imageId: string | null, imageId: string | null;
} };
} }

View File

@ -1,9 +1,31 @@
import { FastifyRequest } from "fastify"; import { FastifyRequest } from 'fastify';
import { errorHandler, getDomain, isDev, prisma, executeCommand } from "../../../lib/common"; import { errorHandler, getDomain, isDev, prisma, executeCommand } from '../../../lib/common';
import { getTemplates } from "../../../lib/services"; import { getTemplates } from '../../../lib/services';
import { OnlyId } from "../../../types"; import { OnlyId } from '../../../types';
function generateServices(serviceId, containerId, port) { function generateServices(serviceId, containerId, port, isHttp2 = false, isHttps = false) {
if (isHttp2) {
return {
[serviceId]: {
loadbalancer: {
servers: [
{
url: `${isHttps ? 'https' : 'http'}://${containerId}:${port}`
}
]
}
},
[`${serviceId}-http2`]: {
loadbalancer: {
servers: [
{
url: `h2c://${containerId}:${port}`
}
]
}
}
};
}
return { return {
[serviceId]: { [serviceId]: {
loadbalancer: { loadbalancer: {
@ -14,43 +36,54 @@ function generateServices(serviceId, containerId, port) {
] ]
} }
} }
} };
} }
function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, isDualCerts, isCustomSSL) { function generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
isDualCerts,
isCustomSSL,
isHttp2 = false
) {
let rule = `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`;
let http: any = { let http: any = {
entrypoints: ['web'], entrypoints: ['web'],
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, rule,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
middlewares: [] middlewares: []
} };
let https: any = { let https: any = {
entrypoints: ['websecure'], entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, rule,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
tls: { tls: {
certresolver: 'letsencrypt' certresolver: 'letsencrypt'
}, },
middlewares: [] middlewares: []
} };
let httpWWW: any = { let httpWWW: any = {
entrypoints: ['web'], entrypoints: ['web'],
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, rule,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
middlewares: [] middlewares: []
} };
let httpsWWW: any = { let httpsWWW: any = {
entrypoints: ['websecure'], entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`)${pathPrefix ? ` && PathPrefix(\`${pathPrefix}\`)` : ''}`, rule,
service: `${serviceId}`, service: `${serviceId}`,
priority: 2, priority: 2,
tls: { tls: {
certresolver: 'letsencrypt' certresolver: 'letsencrypt'
}, },
middlewares: [] middlewares: []
} };
// 2. http + non-www only // 2. http + non-www only
if (!isHttps && !isWWW) { if (!isHttps && !isWWW) {
https.middlewares.push('redirect-to-http'); https.middlewares.push('redirect-to-http');
@ -58,8 +91,8 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
httpWWW.middlewares.push('redirect-to-non-www'); httpWWW.middlewares.push('redirect-to-non-www');
httpsWWW.middlewares.push('redirect-to-non-www'); httpsWWW.middlewares.push('redirect-to-non-www');
delete https.tls delete https.tls;
delete httpsWWW.tls delete httpsWWW.tls;
} }
// 3. http + www only // 3. http + www only
@ -69,8 +102,8 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
http.middlewares.push('redirect-to-www'); http.middlewares.push('redirect-to-www');
https.middlewares.push('redirect-to-www'); https.middlewares.push('redirect-to-www');
delete https.tls delete https.tls;
delete httpsWWW.tls delete httpsWWW.tls;
} }
// 5. https + non-www only // 5. https + non-www only
if (isHttps && !isWWW) { if (isHttps && !isWWW) {
@ -86,17 +119,17 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
httpsWWW.tls = true; httpsWWW.tls = true;
} else { } else {
https.tls = true; https.tls = true;
delete httpsWWW.tls.certresolver delete httpsWWW.tls.certresolver;
httpsWWW.tls.domains = { httpsWWW.tls.domains = {
main: domain main: domain
} };
} }
} else { } else {
if (!isDualCerts) { if (!isDualCerts) {
delete httpsWWW.tls.certresolver delete httpsWWW.tls.certresolver;
httpsWWW.tls.domains = { httpsWWW.tls.domains = {
main: domain main: domain
} };
} }
} }
} }
@ -114,26 +147,59 @@ function generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, is
httpsWWW.tls = true; httpsWWW.tls = true;
} else { } else {
httpsWWW.tls = true; httpsWWW.tls = true;
delete https.tls.certresolver delete https.tls.certresolver;
https.tls.domains = { https.tls.domains = {
main: domain main: domain
} };
} }
} else { } else {
if (!isDualCerts) { if (!isDualCerts) {
delete https.tls.certresolver delete https.tls.certresolver;
https.tls.domains = { https.tls.domains = {
main: domain main: domain
} };
} }
} }
} }
if (isHttp2) {
let http2 = {
...http,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let http2WWW = {
...httpWWW,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let https2 = {
...https,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
let https2WWW = {
...httpsWWW,
service: `${serviceId}-http2`,
rule: `${rule} && HeadersRegexp(\`Content-Type\`, \`application/grpc*\`)`
};
return {
[`${serviceId}-${pathPrefix}`]: { ...http },
[`${serviceId}-${pathPrefix}-http2`]: { ...http2 },
[`${serviceId}-${pathPrefix}-secure`]: { ...https },
[`${serviceId}-${pathPrefix}-secure-http2`]: { ...https2 },
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
[`${serviceId}-${pathPrefix}-www-http2`]: { ...http2WWW },
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW },
[`${serviceId}-${pathPrefix}-secure-www-http2`]: { ...https2WWW }
};
}
return { return {
[`${serviceId}-${pathPrefix}`]: { ...http }, [`${serviceId}-${pathPrefix}`]: { ...http },
[`${serviceId}-${pathPrefix}-secure`]: { ...https }, [`${serviceId}-${pathPrefix}-secure`]: { ...https },
[`${serviceId}-${pathPrefix}-www`]: { ...httpWWW }, [`${serviceId}-${pathPrefix}-www`]: { ...httpWWW },
[`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }, [`${serviceId}-${pathPrefix}-secure-www`]: { ...httpsWWW }
} };
} }
export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) { export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
const traefik = { const traefik = {
@ -174,26 +240,26 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const coolifySettings = await prisma.setting.findFirst(); const coolifySettings = await prisma.setting.findFirst();
if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) { if (coolifySettings.isTraefikUsed && coolifySettings.proxyDefaultRedirect) {
traefik.http.routers['catchall-http'] = { traefik.http.routers['catchall-http'] = {
entrypoints: ["web"], entrypoints: ['web'],
rule: "HostRegexp(`{catchall:.*}`)", rule: 'HostRegexp(`{catchall:.*}`)',
service: "noop", service: 'noop',
priority: 1, priority: 1,
middlewares: ["redirect-regexp"] middlewares: ['redirect-regexp']
} };
traefik.http.routers['catchall-https'] = { traefik.http.routers['catchall-https'] = {
entrypoints: ["websecure"], entrypoints: ['websecure'],
rule: "HostRegexp(`{catchall:.*}`)", rule: 'HostRegexp(`{catchall:.*}`)',
service: "noop", service: 'noop',
priority: 1, priority: 1,
middlewares: ["redirect-regexp"] middlewares: ['redirect-regexp']
} };
traefik.http.middlewares['redirect-regexp'] = { traefik.http.middlewares['redirect-regexp'] = {
redirectregex: { redirectregex: {
regex: '(.*)', regex: '(.*)',
replacement: coolifySettings.proxyDefaultRedirect, replacement: coolifySettings.proxyDefaultRedirect,
permanent: false permanent: false
} }
} };
traefik.http.services['noop'] = { traefik.http.services['noop'] = {
loadBalancer: { loadBalancer: {
servers: [ servers: [
@ -202,25 +268,41 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
} }
] ]
} }
} };
} }
const sslpath = '/etc/traefik/acme/custom'; 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) { 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) { for (const certificate of certificates) {
parsedCertificates.push({ parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`, certFile: `${sslpath}/${certificate.id}-cert.pem`,
keyFile: `${sslpath}/${certificate.id}-key.pem` keyFile: `${sslpath}/${certificate.id}-key.pem`
}) });
} }
if (parsedCertificates.length > 0) { if (parsedCertificates.length > 0) {
traefik.tls.certificates = parsedCertificates traefik.tls.certificates = parsedCertificates;
} }
let applications = []; let applications = [];
@ -236,7 +318,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
destinationDocker: true, destinationDocker: true,
persistentStorage: true, persistentStorage: true,
serviceSecret: true, serviceSecret: true,
serviceSetting: true, serviceSetting: true
}, },
orderBy: { createdAt: 'desc' } orderBy: { createdAt: 'desc' }
}); });
@ -251,23 +333,25 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
destinationDocker: true, destinationDocker: true,
persistentStorage: true, persistentStorage: true,
serviceSecret: true, serviceSecret: true,
serviceSetting: true, serviceSetting: true
}, },
orderBy: { createdAt: 'desc' }, orderBy: { createdAt: 'desc' }
}); });
} }
if (applications.length > 0) { if (applications.length > 0) {
const dockerIds = new Set() const dockerIds = new Set();
const runningContainers = {} const runningContainers = {};
applications.forEach((app) => dockerIds.add(app.destinationDocker.id)); applications.forEach((app) => dockerIds.add(app.destinationDocker.id));
for (const dockerId of dockerIds) { 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) { if (container) {
const containersArray = container.trim().split('\n'); const containersArray = container.trim().split('\n');
if (containersArray.length > 0) { if (containersArray.length > 0) {
runningContainers[dockerId] = containersArray runningContainers[dockerId] = containersArray;
} }
} }
} }
@ -289,38 +373,54 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
if ( if (
!runningContainers[destinationDockerId] || !runningContainers[destinationDockerId] ||
runningContainers[destinationDockerId].length === 0 || 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') { if (buildPack === 'compose') {
const services = Object.entries(JSON.parse(dockerComposeConfiguration)) const services = Object.entries(JSON.parse(dockerComposeConfiguration));
if (services.length > 0) { if (services.length > 0) {
for (const service of services) { for (const service of services) {
const [key, value] = service const [key, value] = service;
if (key && value) { if (key && value) {
if (!value.fqdn || !value.port) { if (!value.fqdn || !value.port) {
continue; continue;
} }
const { fqdn, port } = value const { fqdn, port } = value;
const containerId = `${id}-${key}` const containerId = `${id}-${key}`;
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const pathPrefix = '/' const pathPrefix = '/';
const isCustomSSL = false; const isCustomSSL = false;
const dualCerts = 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.routers = {
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, containerId, port) } ...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, containerId, port)
};
} }
} }
} }
continue; continue;
} }
const { previews, dualCerts, isCustomSSL } = settings; const { previews, dualCerts, isCustomSSL, isHttp2 } = settings;
const { network, id: dockerId } = destinationDocker; const { network, id: dockerId } = destinationDocker;
if (!fqdn) { if (!fqdn) {
continue; continue;
@ -329,12 +429,31 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const pathPrefix = '/' const pathPrefix = '/';
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.routers = {
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) } ...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL,
isHttp2
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, id, port, isHttp2, isHttps)
};
if (previews) { 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) { if (stdout) {
const containers = stdout const containers = stdout
.trim() .trim()
@ -343,44 +462,57 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
.map((c) => c.replace(/"/g, '')); .map((c) => c.replace(/"/g, ''));
if (containers.length > 0) { if (containers.length > 0) {
for (const container of containers) { 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 nakedDomain = previewDomain.replace(/^www\./, '');
const pathPrefix = '/' const pathPrefix = '/';
const serviceId = `${container}-${port || 'default'}` const serviceId = `${container}-${port || 'default'}`;
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, previewDomain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } traefik.http.routers = {
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) } ...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) { } catch (error) {
console.log(error) console.log(error);
} }
} }
} }
if (services.length > 0) { if (services.length > 0) {
const dockerIds = new Set() const dockerIds = new Set();
const runningContainers = {} const runningContainers = {};
services.forEach((app) => dockerIds.add(app.destinationDocker.id)); services.forEach((app) => dockerIds.add(app.destinationDocker.id));
for (const dockerId of dockerIds) { 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) { if (container) {
const containersArray = container.trim().split('\n'); const containersArray = container.trim().split('\n');
if (containersArray.length > 0) { if (containersArray.length > 0) {
runningContainers[dockerId] = containersArray runningContainers[dockerId] = containersArray;
} }
} }
} }
for (const service of services) { for (const service of services) {
try { try {
let { let { fqdn, id, type, destinationDockerId, dualCerts, serviceSetting } = service;
fqdn,
id,
type,
destinationDockerId,
dualCerts,
serviceSetting
} = service;
if (!fqdn) { if (!fqdn) {
continue; continue;
} }
@ -392,7 +524,7 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
runningContainers[destinationDockerId].length === 0 || runningContainers[destinationDockerId].length === 0 ||
!runningContainers[destinationDockerId].includes(id) !runningContainers[destinationDockerId].includes(id)
) { ) {
continue continue;
} }
const templates = await getTemplates(); const templates = await getTemplates();
let found = templates.find((a) => a.type === type); let found = templates.find((a) => a.type === type);
@ -401,88 +533,143 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
} }
found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id)); found = JSON.parse(JSON.stringify(found).replaceAll('$$id', id));
for (const oneService of Object.keys(found.services)) { 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) { if (isDomainConfiguration.length > 0) {
const { proxy } = found.services[oneService]; const { proxy } = found.services[oneService];
for (let configuration of proxy) { for (let configuration of proxy) {
if (configuration.domain) { if (configuration.domain) {
const setting = serviceSetting.find((a) => a.variableName === configuration.domain); const setting = serviceSetting.find(
(a) => a.variableName === configuration.domain
);
if (setting) { 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) { if (foundPortVariable) {
configuration.port = foundPortVariable.value configuration.port = foundPortVariable.value;
} }
let port, pathPrefix, customDomain; let port, pathPrefix, customDomain;
if (configuration) { if (configuration) {
port = configuration?.port; port = configuration?.port;
pathPrefix = configuration?.pathPrefix || '/'; pathPrefix = configuration?.pathPrefix || '/';
customDomain = configuration?.domain customDomain = configuration?.domain;
} }
if (customDomain) { if (customDomain) {
fqdn = customDomain fqdn = customDomain;
} else { } else {
fqdn = service.fqdn fqdn = service.fqdn;
} }
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const isCustomSSL = false; const isCustomSSL = false;
const serviceId = `${oneService}-${port || 'default'}` const serviceId = `${oneService}-${port || 'default'}`;
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } traefik.http.routers = {
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, oneService, port) } ...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, oneService, port)
};
} }
} else { } else {
if (found.services[oneService].ports && found.services[oneService].ports.length > 0) { if (found.services[oneService].ports && found.services[oneService].ports.length > 0) {
for (let [index, port] of found.services[oneService].ports.entries()) { for (let [index, port] of found.services[oneService].ports.entries()) {
if (port == 22) continue; if (port == 22) continue;
if (index === 0) { if (index === 0) {
const foundPortVariable = serviceSetting.find((a) => a.name.toLowerCase() === 'port') const foundPortVariable = serviceSetting.find(
(a) => a.name.toLowerCase() === 'port'
);
if (foundPortVariable) { if (foundPortVariable) {
port = foundPortVariable.value port = foundPortVariable.value;
} }
} }
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const pathPrefix = '/' const pathPrefix = '/';
const isCustomSSL = false const isCustomSSL = false;
const serviceId = `${oneService}-${port || 'default'}` const serviceId = `${oneService}-${port || 'default'}`;
traefik.http.routers = { ...traefik.http.routers, ...generateRouters(serviceId, domain, nakedDomain, pathPrefix, isHttps, isWWW, dualCerts, isCustomSSL) } traefik.http.routers = {
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, id, port) } ...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, id, port)
};
} }
} }
} }
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} }
} }
if (!remote) { if (!remote) {
const { fqdn, dualCerts } = await prisma.setting.findFirst(); const { fqdn, dualCerts } = await prisma.setting.findFirst();
if (!fqdn) { if (!fqdn) {
return return;
} }
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, ''); const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const id = isDev ? 'host.docker.internal' : 'coolify' const id = isDev ? 'host.docker.internal' : 'coolify';
const container = isDev ? 'host.docker.internal' : 'coolify' const container = isDev ? 'host.docker.internal' : 'coolify';
const port = 3000 const port = 3000;
const pathPrefix = '/' const pathPrefix = '/';
const isCustomSSL = false const isCustomSSL = 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.routers = {
traefik.http.services = { ...traefik.http.services, ...generateServices(serviceId, container, port) } ...traefik.http.routers,
...generateRouters(
serviceId,
domain,
nakedDomain,
pathPrefix,
isHttps,
isWWW,
dualCerts,
isCustomSSL
)
};
traefik.http.services = {
...traefik.http.services,
...generateServices(serviceId, container, port)
};
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} finally { } finally {
if (Object.keys(traefik.http.routers).length === 0) { if (Object.keys(traefik.http.routers).length === 0) {
traefik.http.routers = null; traefik.http.routers = null;
@ -496,9 +683,9 @@ export async function proxyConfiguration(request: FastifyRequest<OnlyId>, remote
export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) { export async function otherProxyConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
try { try {
const { id } = request.query const { id } = request.query;
if (id) { if (id) {
const { privatePort, publicPort, type, address = id } = request.query const { privatePort, publicPort, type, address = id } = request.query;
let traefik = {}; let traefik = {};
if (publicPort && type && privatePort) { if (publicPort && type && privatePort) {
if (type === 'tcp') { if (type === 'tcp') {
@ -559,18 +746,18 @@ export async function otherProxyConfiguration(request: FastifyRequest<TraefikOth
} }
} }
} else { } else {
throw { status: 500 } throw { status: 500 };
} }
} }
} else { } else {
throw { status: 500 } throw { status: 500 };
} }
return { return {
...traefik ...traefik
}; };
} }
throw { status: 500 } throw { status: 500 };
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message });
} }
} }

View File

@ -61,6 +61,7 @@
let isBot = application.settings?.isBot; let isBot = application.settings?.isBot;
let isDBBranching = application.settings?.isDBBranching; let isDBBranching = application.settings?.isDBBranching;
let htmlUrl = application.gitSource?.htmlUrl; let htmlUrl = application.gitSource?.htmlUrl;
let isHttp2 = application.settings?.isHttp2;
let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null; let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null;
let dockerComposeServices: any[] = []; let dockerComposeServices: any[] = [];
@ -177,6 +178,9 @@
if (name === 'isDBBranching') { if (name === 'isDBBranching') {
isDBBranching = !isDBBranching; isDBBranching = !isDBBranching;
} }
if (name === 'isHttp2') {
isHttp2 = !isHttp2;
}
try { try {
await trpc.applications.saveSettings.mutate({ await trpc.applications.saveSettings.mutate({
id: application.id, id: application.id,
@ -713,7 +717,7 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="grid grid-cols-2 items-center pb-4"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
id="dualCerts" id="dualCerts"
dataTooltip="Must be stopped to modify." dataTooltip="Must be stopped to modify."
@ -737,6 +741,16 @@
/> />
</div> </div>
{/if} {/if}
<div class="grid grid-cols-2 items-center pb-4">
<Setting
id="isHttp2"
isCenter={false}
bind:setting={isHttp2}
title="Enable HTTP/2 protocol?"
description="Enable HTTP/2 protocol. <br><br>HTTP/2 is a major revision of the HTTP network protocol used by the World Wide Web that allows faster web page loading by reducing the number of requests needed to load a web page.<br><br>Useful for gRPC and other HTTP/2 based services."
on:click={() => changeSettings('isHttp2')}
/>
</div>
{/if} {/if}
</div> </div>
{#if isSimpleDockerfile} {#if isSimpleDockerfile}

View File

@ -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;

View File

@ -186,6 +186,7 @@ model ApplicationSettings {
isPublicRepository Boolean @default(false) isPublicRepository Boolean @default(false)
isDBBranching Boolean @default(false) isDBBranching Boolean @default(false)
isCustomSSL Boolean @default(false) isCustomSSL Boolean @default(false)
isHttp2 Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
application Application @relation(fields: [applicationId], references: [id]) application Application @relation(fields: [applicationId], references: [id])

View File

@ -55,12 +55,22 @@ export default async function (data) {
value['environment'] = [...environment, ...envs]; value['environment'] = [...environment, ...envs];
let build = typeof value['build'] === 'undefined' ? [] : value['build']; let build = typeof value['build'] === 'undefined' ? [] : value['build'];
if (Object.keys(build).length > 0) { if (typeof build === 'string') {
build = Object.entries(build).map(([key, value]) => `${key}=${value}`); build = { context: build };
}
const buildArgs = typeof build['args'] === 'undefined' ? [] : build['args'];
let finalArgs = [...buildEnvs];
if (Object.keys(buildArgs).length > 0) {
for (const arg of buildArgs) {
const [key, _] = arg.split('=');
if (finalArgs.filter((env) => env.startsWith(key)).length === 0) {
finalArgs.push(arg);
}
}
} }
value['build'] = { value['build'] = {
...build, ...build,
args: [...(build?.args || []), ...buildEnvs] args: finalArgs
}; };
value['labels'] = labels; value['labels'] = labels;

View File

@ -808,18 +808,19 @@ export const applicationsRouter = router({
isBot: z.boolean().optional(), isBot: z.boolean().optional(),
autodeploy: z.boolean().optional(), autodeploy: z.boolean().optional(),
isDBBranching: z.boolean().optional(), isDBBranching: z.boolean().optional(),
isCustomSSL: z.boolean().optional() isCustomSSL: z.boolean().optional(),
isHttp2: z.boolean().optional()
}) })
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const { id, debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } = const { id, debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL, isHttp2 } =
input; input;
await prisma.application.update({ await prisma.application.update({
where: { id }, where: { id },
data: { data: {
fqdn: isBot ? null : undefined, fqdn: isBot ? null : undefined,
settings: { settings: {
update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL } update: { debug, previews, dualCerts, autodeploy, isBot, isDBBranching, isCustomSSL, isHttp2 }
} }
}, },
include: { destinationDocker: true } include: { destinationDocker: true }

View File

@ -79,6 +79,7 @@
let isBot = application.settings?.isBot; let isBot = application.settings?.isBot;
let isDBBranching = application.settings?.isDBBranching; let isDBBranching = application.settings?.isDBBranching;
let htmlUrl = application.gitSource?.htmlUrl; let htmlUrl = application.gitSource?.htmlUrl;
let isHttp2 = application.settings.isHttp2;
let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null; let dockerComposeFile = JSON.parse(application.dockerComposeFile) || null;
let dockerComposeServices: any[] = []; let dockerComposeServices: any[] = [];
@ -195,6 +196,9 @@
if (name === 'isDBBranching') { if (name === 'isDBBranching') {
isDBBranching = !isDBBranching; isDBBranching = !isDBBranching;
} }
if (name === 'isHttp2') {
isHttp2 = !isHttp2;
}
try { try {
await post(`/applications/${id}/settings`, { await post(`/applications/${id}/settings`, {
previews, previews,
@ -204,6 +208,7 @@
autodeploy, autodeploy,
isDBBranching, isDBBranching,
isCustomSSL, isCustomSSL,
isHttp2,
branch: application.branch, branch: application.branch,
projectId: application.projectId projectId: application.projectId
}); });
@ -734,7 +739,7 @@
{/if} {/if}
</div> </div>
</div> </div>
<div class="grid grid-cols-2 items-center pb-4"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
id="dualCerts" id="dualCerts"
dataTooltip={$t('forms.must_be_stopped_to_modify')} dataTooltip={$t('forms.must_be_stopped_to_modify')}
@ -746,6 +751,7 @@
on:click={() => !isDisabled && changeSettings('dualCerts')} on:click={() => !isDisabled && changeSettings('dualCerts')}
/> />
</div> </div>
{#if isHttps && application.buildPack !== 'compose'} {#if isHttps && application.buildPack !== 'compose'}
<div class="grid grid-cols-2 items-center pb-4"> <div class="grid grid-cols-2 items-center pb-4">
<Setting <Setting
@ -758,6 +764,16 @@
/> />
</div> </div>
{/if} {/if}
<div class="grid grid-cols-2 items-center pb-4">
<Setting
id="isHttp2"
isCenter={false}
bind:setting={isHttp2}
title="Enable HTTP/2 protocol?"
description="Enable HTTP/2 protocol. <br><br>HTTP/2 is a major revision of the HTTP network protocol used by the World Wide Web that allows faster web page loading by reducing the number of requests needed to load a web page.<br><br>Useful for gRPC and other HTTP/2 based services."
on:click={() => changeSettings('isHttp2')}
/>
</div>
{/if} {/if}
</div> </div>
{#if isSimpleDockerfile} {#if isSimpleDockerfile}

View File

@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.12.11", "version": "3.12.12",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": "github:coollabsio/coolify", "repository": "github:coollabsio/coolify",
"scripts": { "scripts": {