From 6fb6a514ac7522d177e314dd5b724d9d08297b0e Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 19 May 2022 13:45:17 +0200 Subject: [PATCH] fix: Minio urls + domain checks --- .../migration.sql | 2 + prisma/schema.prisma | 1 + src/lib/database/checks.ts | 11 +- src/lib/database/common.ts | 4 +- src/lib/database/services.ts | 21 +++- src/lib/queues/proxyTcpHttp.ts | 23 ++-- .../services/[id]/_Services/_MinIO.svelte | 24 ++-- .../services/[id]/_Services/_Services.svelte | 84 ++++++++++--- src/routes/services/[id]/check.json.ts | 20 ++- src/routes/services/[id]/minio/index.json.ts | 11 +- src/routes/services/[id]/minio/start.json.ts | 3 +- src/routes/webhooks/traefik/main.json.ts | 47 +++++-- src/routes/webhooks/traefik/other.json.ts | 115 +++++++++++++----- 13 files changed, 270 insertions(+), 96 deletions(-) create mode 100644 prisma/migrations/20220519095648_minio_apifqdn/migration.sql diff --git a/prisma/migrations/20220519095648_minio_apifqdn/migration.sql b/prisma/migrations/20220519095648_minio_apifqdn/migration.sql new file mode 100644 index 000000000..a44712864 --- /dev/null +++ b/prisma/migrations/20220519095648_minio_apifqdn/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Minio" ADD COLUMN "apiFqdn" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 97d092331..4629a1ba1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -345,6 +345,7 @@ model Minio { rootUser String rootUserPassword String publicPort Int? + apiFqdn String? serviceId String @unique service Service @relation(fields: [serviceId], references: [id]) createdAt DateTime @default(now()) diff --git a/src/lib/database/checks.ts b/src/lib/database/checks.ts index bbe773ae5..2c1c25cd8 100644 --- a/src/lib/database/checks.ts +++ b/src/lib/database/checks.ts @@ -51,10 +51,12 @@ export async function isSecretExists({ export async function isDomainConfigured({ id, - fqdn + fqdn, + checkOwn = false }: { id: string; fqdn: string; + checkOwn?: boolean; }): Promise { const domain = getDomain(fqdn); const nakedDomain = domain.replace('www.', ''); @@ -72,12 +74,15 @@ export async function isDomainConfigured({ where: { OR: [ { fqdn: { endsWith: `//${nakedDomain}` } }, - { fqdn: { endsWith: `//www.${nakedDomain}` } } + { fqdn: { endsWith: `//www.${nakedDomain}` } }, + { minio: { apiFqdn: { endsWith: `//${nakedDomain}` } } }, + { minio: { apiFqdn: { endsWith: `//www.${nakedDomain}` } } } ], - id: { not: id } + id: { not: checkOwn ? undefined : id } }, select: { fqdn: true } }); + const coolifyFqdn = await prisma.setting.findFirst({ where: { OR: [ diff --git a/src/lib/database/common.ts b/src/lib/database/common.ts index 3196a40ee..c26796ea4 100644 --- a/src/lib/database/common.ts +++ b/src/lib/database/common.ts @@ -305,12 +305,12 @@ export async function getFreePort() { select: { mysqlPublicPort: true } }) ).map((a) => a.mysqlPublicPort); - const minioUSed = await ( + const minioUsed = await ( await prisma.minio.findMany({ where: { publicPort: { not: null } }, select: { publicPort: true } }) ).map((a) => a.publicPort); - const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUSed]; + const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed]; return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts }); } diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index d2a7bb899..453153c0c 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -360,7 +360,24 @@ export async function updateService({ }): Promise { return await prisma.service.update({ where: { id }, data: { fqdn, name, exposePort } }); } - +export async function updateMinioService({ + id, + fqdn, + apiFqdn, + exposePort, + name +}: { + id: string; + fqdn: string; + apiFqdn: string; + exposePort?: number; + name: string; +}): Promise { + return await prisma.service.update({ + where: { id }, + data: { fqdn, name, exposePort, minio: { update: { apiFqdn } } } + }); +} export async function updateFiderService({ id, fqdn, @@ -459,7 +476,7 @@ export async function updateWordpress({ }); } -export async function updateMinioService({ +export async function updateMinioServicePort({ id, publicPort }: { diff --git a/src/lib/queues/proxyTcpHttp.ts b/src/lib/queues/proxyTcpHttp.ts index c70339bad..9ce239b66 100644 --- a/src/lib/queues/proxyTcpHttp.ts +++ b/src/lib/queues/proxyTcpHttp.ts @@ -81,19 +81,16 @@ export default async function (): Promise -
- - -
+{#if !service.minio.apiFqdn} +
+ + +
+{/if} diff --git a/src/routes/services/[id]/_Services/_Services.svelte b/src/routes/services/[id]/_Services/_Services.svelte index 00ef5d305..03a94d311 100644 --- a/src/routes/services/[id]/_Services/_Services.svelte +++ b/src/routes/services/[id]/_Services/_Services.svelte @@ -33,6 +33,7 @@ try { await post(`/services/${id}/check.json`, { fqdn: service.fqdn, + otherFqdns: [service.minio.apiFqdn], exposePort: service.exposePort }); await post(`/services/${id}/${service.type}.json`, { ...service }); @@ -89,6 +90,14 @@
+ {#if service.type === 'minio' && !service.minio.apiFqdn && isRunning} +
+ IMPORTANT! There was a small modification with + Minio in the latest version of Coolify. Now you can separate the Console URL from the API URL, + so you could use both through SSL. But this proccess cannot be done automatically, so you have + to stop your Minio instance, configure the new domain and start it back. Sorry for any inconvenience. +
+ {/if}
@@ -134,25 +143,62 @@ {/if}
-
-
- - -
+ {#if service.type === 'minio'} +
+
+ +
+ + +
+
+
+ + +
+ + +
+ {:else} +
+
+ + +
+ + +
+ {/if} - -
!isRunning && changeSettings('dualCerts')} />
-
+
{ if (status === 401) return { status, body }; const { id } = event.params; - let { fqdn, exposePort } = await event.request.json(); + let { fqdn, exposePort, otherFqdns } = await event.request.json(); if (fqdn) fqdn = fqdn.toLowerCase(); + if (otherFqdns) otherFqdns = otherFqdns.map((fqdn) => fqdn.toLowerCase()); + if (exposePort) exposePort = Number(exposePort); try { - const found = await db.isDomainConfigured({ id, fqdn }); + let found = await db.isDomainConfigured({ id, fqdn }); if (found) { throw { message: t.get('application.domain_already_in_use', { @@ -23,6 +25,20 @@ export const post: RequestHandler = async (event) => { }) }; } + if (otherFqdns) { + for (const ofqdn of otherFqdns) { + const domain = getDomain(ofqdn); + const nakedDomain = domain.replace('www.', ''); + found = await db.isDomainConfigured({ id, fqdn: ofqdn, checkOwn: true }); + if (found) { + throw { + message: t.get('application.domain_already_in_use', { + domain: nakedDomain + }) + }; + } + } + } if (exposePort) { exposePort = Number(exposePort); diff --git a/src/routes/services/[id]/minio/index.json.ts b/src/routes/services/[id]/minio/index.json.ts index ff98ede6d..32056218d 100644 --- a/src/routes/services/[id]/minio/index.json.ts +++ b/src/routes/services/[id]/minio/index.json.ts @@ -9,12 +9,17 @@ export const post: RequestHandler = async (event) => { const { id } = event.params; - let { name, fqdn, exposePort } = await event.request.json(); + let { + name, + fqdn, + exposePort, + minio: { apiFqdn } + } = await event.request.json(); if (fqdn) fqdn = fqdn.toLowerCase(); if (exposePort) exposePort = Number(exposePort); - + if (apiFqdn) apiFqdn = apiFqdn.toLowerCase(); try { - await db.updateService({ id, fqdn, name, exposePort }); + await db.updateMinioService({ id, fqdn, apiFqdn, name, exposePort }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/services/[id]/minio/start.json.ts b/src/routes/services/[id]/minio/start.json.ts index dee17c1af..9dd75bdd9 100644 --- a/src/routes/services/[id]/minio/start.json.ts +++ b/src/routes/services/[id]/minio/start.json.ts @@ -34,7 +34,6 @@ export const post: RequestHandler = async (event) => { const publicPort = await getFreePort(); const consolePort = 9001; - const apiPort = 9000; const { workdir } = await createDirectories({ repository: type, buildId: id }); const image = getServiceImage(type); @@ -93,7 +92,7 @@ export const post: RequestHandler = async (event) => { try { await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); - await db.updateMinioService({ id, publicPort }); + await db.updateMinioServicePort({ id, publicPort }); return { status: 200 }; diff --git a/src/routes/webhooks/traefik/main.json.ts b/src/routes/webhooks/traefik/main.json.ts index e887a06d9..e39e41f83 100644 --- a/src/routes/webhooks/traefik/main.json.ts +++ b/src/routes/webhooks/traefik/main.json.ts @@ -7,7 +7,7 @@ import { checkContainer } from '$lib/haproxy'; import type { RequestHandler } from '@sveltejs/kit'; function configureMiddleware( - { id, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }, + { id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts }, traefik ) { if (isHttps) { @@ -22,7 +22,7 @@ function configureMiddleware( loadbalancer: { servers: [ { - url: `http://${id}:${port}` + url: `http://${container}:${port}` } ] } @@ -109,7 +109,7 @@ function configureMiddleware( loadbalancer: { servers: [ { - url: `http://${id}:${port}` + url: `http://${container}:${port}` } ] } @@ -172,8 +172,7 @@ export const get: RequestHandler = async (event) => { port, destinationDocker, destinationDockerId, - settings: { previews, dualCerts }, - updatedAt + settings: { previews, dualCerts } } = application; if (destinationDockerId) { const { engine, network } = destinationDocker; @@ -186,6 +185,7 @@ export const get: RequestHandler = async (event) => { if (isRunning) { data.applications.push({ id, + container: id, port: port || 3000, domain, nakedDomain, @@ -208,14 +208,17 @@ export const get: RequestHandler = async (event) => { if (containers.length > 0) { for (const container of containers) { const previewDomain = `${container.split('-')[1]}.${domain}`; + const nakedDomain = previewDomain.replace(/^www\./, ''); data.applications.push({ id: container, + container, port: port || 3000, domain: previewDomain, isRunning, + nakedDomain, isHttps, - redirectTo: isWWW ? previewDomain.replace('www.', '') : 'www.' + previewDomain, - updatedAt: updatedAt.getTime() + isWWW, + isDualCerts: dualCerts }); } } @@ -254,8 +257,26 @@ export const get: RequestHandler = async (event) => { scriptName = plausibleAnalytics.scriptName; } + let container = id; + let otherDomain = null; + let otherNakedDomain = null; + let otherIsHttps = null; + let otherIsWWW = null; + + if (type === 'minio') { + otherDomain = getDomain(service.minio.apiFqdn); + otherNakedDomain = otherDomain.replace(/^www\./, ''); + otherIsHttps = service.minio.apiFqdn.startsWith('https://'); + otherIsWWW = service.minio.apiFqdn.includes('www.'); + } data.services.push({ id, + container, + type, + otherDomain, + otherNakedDomain, + otherIsHttps, + otherIsWWW, port, publicPort, domain, @@ -280,6 +301,7 @@ export const get: RequestHandler = async (event) => { const isWWW = fqdn.includes('www.'); data.coolify.push({ id: dev ? 'host.docker.internal' : 'coolify', + container: dev ? 'host.docker.internal' : 'coolify', port: 3000, domain, nakedDomain, @@ -293,7 +315,18 @@ export const get: RequestHandler = async (event) => { } for (const service of data.services) { const { id, scriptName } = service; + configureMiddleware(service, traefik); + if (service.type === 'minio') { + service.id = id + '-minio'; + service.container = id; + service.domain = service.otherDomain; + service.nakedDomain = service.otherNakedDomain; + service.isHttps = service.otherIsHttps; + service.isWWW = service.otherIsWWW; + service.port = 9000; + configureMiddleware(service, traefik); + } if (scriptName) { traefik.http.middlewares[`${id}-redir`] = { diff --git a/src/routes/webhooks/traefik/other.json.ts b/src/routes/webhooks/traefik/other.json.ts index 428c9e217..a1c357878 100644 --- a/src/routes/webhooks/traefik/other.json.ts +++ b/src/routes/webhooks/traefik/other.json.ts @@ -1,9 +1,6 @@ import { dev } from '$app/env'; -import { asyncExecShell, getDomain, getEngine } from '$lib/common'; -import { supportedServiceTypesAndVersions } from '$lib/components/common'; +import { getDomain } from '$lib/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) => { @@ -13,7 +10,7 @@ export const get: RequestHandler = async (event) => { const publicPort = event.url.searchParams.get('publicPort'); const type = event.url.searchParams.get('type'); let traefik = {}; - if (publicPort) { + if (publicPort && type && privatePort) { if (type === 'tcp') { traefik = { [type]: { @@ -34,43 +31,97 @@ export const get: RequestHandler = async (event) => { } }; } else if (type === 'http') { - const service = await db.prisma.service.findFirst({ where: { id } }); - if (service?.fqdn) { - const domain = getDomain(service.fqdn); - const isHttps = service.fqdn.startsWith('https://'); - traefik = { - [type]: { - routers: { - [id]: { - entrypoints: [type], - rule: `Host(\`${domain}\`)`, - service: id - } - }, - services: { - [id]: { - loadbalancer: { - servers: [{ url: `http://${id}:${privatePort}` }] + const service = await db.prisma.service.findFirst({ + where: { id }, + include: { minio: true } + }); + if (service) { + if (service.type === 'minio') { + if (service?.minio?.apiFqdn) { + const { + minio: { apiFqdn } + } = service; + const domain = getDomain(apiFqdn); + const isHttps = apiFqdn.startsWith('https://'); + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `Host(\`${domain}\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [{ url: `http://${id}:${privatePort}` }] + } + } } } + }; + if (isHttps) { + if (dev) { + traefik[type].routers[id].tls = { + domains: { + main: `${domain}` + } + }; + } else { + traefik[type].routers[id].tls = { + certresolver: 'letsencrypt' + }; + } } } - }; - if (isHttps) { - if (dev) { - traefik[type].routers[id].tls = { - domains: { - main: `${domain}` + } else { + if (service?.fqdn) { + const domain = getDomain(service.fqdn); + const isHttps = service.fqdn.startsWith('https://'); + traefik = { + [type]: { + routers: { + [id]: { + entrypoints: [type], + rule: `Host(\`${domain}:${privatePort}\`)`, + service: id + } + }, + services: { + [id]: { + loadbalancer: { + servers: [{ url: `http://${id}:${privatePort}` }] + } + } + } } }; - } else { - traefik[type].routers[id].tls = { - certresolver: 'letsencrypt' - }; + if (isHttps) { + if (dev) { + traefik[type].routers[id].tls = { + domains: { + main: `${domain}` + } + }; + } else { + traefik[type].routers[id].tls = { + certresolver: 'letsencrypt' + }; + } + } } } + } else { + return { + status: 500 + }; } } + } else { + return { + status: 500 + }; } return { status: 200,