fix: Minio urls + domain checks

This commit is contained in:
Andras Bacsai 2022-05-19 13:45:17 +02:00
parent b01f5f47b3
commit 6fb6a514ac
13 changed files with 270 additions and 96 deletions

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Minio" ADD COLUMN "apiFqdn" TEXT;

View File

@ -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())

View File

@ -51,10 +51,12 @@ export async function isSecretExists({
export async function isDomainConfigured({
id,
fqdn
fqdn,
checkOwn = false
}: {
id: string;
fqdn: string;
checkOwn?: boolean;
}): Promise<boolean> {
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: [

View File

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

View File

@ -360,7 +360,24 @@ export async function updateService({
}): Promise<Service> {
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<Service> {
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
}: {

View File

@ -81,6 +81,7 @@ export default async function (): Promise<void | {
}
// HTTP Proxies
if (!settings.isTraefikUsed) {
const minioInstances = await prisma.minio.findMany({
where: { publicPort: { not: null } },
include: { service: { include: { destinationDocker: true } } }
@ -90,10 +91,6 @@ export default async function (): Promise<void | {
const { destinationDockerId, destinationDocker, id } = service;
if (destinationDockerId) {
if (destinationDocker.isCoolifyProxyUsed) {
if (settings.isTraefikUsed) {
await stopTcpHttpProxy(id, destinationDocker, publicPort, `haproxy-for-${publicPort}`);
await startTraefikHTTPProxy(destinationDocker, id, publicPort, 9000);
} else {
await stopTcpHttpProxy(id, destinationDocker, publicPort, `${id}-${publicPort}`);
await startHttpProxy(destinationDocker, id, publicPort, 9000);
}

View File

@ -30,7 +30,8 @@
value={service.minio.rootUserPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
{#if !service.minio.apiFqdn}
<div class="grid grid-cols-2 items-center px-10">
<label for="publicPort">{$t('forms.api_port')}</label>
<input
name="publicPort"
@ -40,4 +41,5 @@
readonly
placeholder={$t('forms.generated_automatically_after_start')}
/>
</div>
</div>
{/if}

View File

@ -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 @@
</div>
<div class="grid grid-flow-row gap-2">
{#if service.type === 'minio' && !service.minio.apiFqdn && isRunning}
<div class="text-center">
<span class="font-bold text-red-500">IMPORTANT!</span> 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.
</div>
{/if}
<div class="mt-2 grid grid-cols-2 items-center px-10">
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<div>
@ -134,6 +143,41 @@
{/if}
</div>
</div>
{#if service.type === 'minio'}
<div class="grid grid-cols-2 px-10">
<div class="flex-col ">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100">Console URL</label>
</div>
<CopyPasswordField
placeholder="eg: https://console.min.io"
readonly={!$session.isAdmin && !isRunning}
disabled={!$session.isAdmin || isRunning}
name="fqdn"
id="fqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
bind:value={service.fqdn}
required
/>
</div>
<div class="grid grid-cols-2 px-10">
<div class="flex-col ">
<label for="apiFqdn" class="pt-2 text-base font-bold text-stone-100">API URL</label>
<Explainer text={$t('application.https_explainer')} />
</div>
<CopyPasswordField
placeholder="eg: https://min.io"
readonly={!$session.isAdmin && !isRunning}
disabled={!$session.isAdmin || isRunning}
name="apiFqdn"
id="apiFqdn"
pattern="^https?://([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{'{'}2,{'}'}$"
bind:value={service.minio.apiFqdn}
required
/>
</div>
{:else}
<div class="grid grid-cols-2 px-10">
<div class="flex-col ">
<label for="fqdn" class="pt-2 text-base font-bold text-stone-100"
@ -153,6 +197,8 @@
required
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center px-10">
<Setting
disabled={isRunning}

View File

@ -10,12 +10,14 @@ export const post: RequestHandler = async (event) => {
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);

View File

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

View File

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

View File

@ -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`] = {

View File

@ -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,10 +31,18 @@ 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://');
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: {
@ -70,7 +75,53 @@ export const get: RequestHandler = async (event) => {
}
}
}
} 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}` }]
}
}
}
}
};
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,