feat: www <-> non-www redirection

This commit is contained in:
Andras Bacsai
2022-02-13 22:56:37 +01:00
parent 69d3cb5dd8
commit aec1d184c8
11 changed files with 90 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
import { decrypt, encrypt } from '$lib/crypto'; import { decrypt, encrypt } from '$lib/crypto';
import { removeProxyConfiguration } from '$lib/haproxy'; import { removeProxyConfiguration, removeWwwRedirection } from '$lib/haproxy';
import { asyncExecShell, getEngine } from '$lib/common'; import { asyncExecShell, getEngine } from '$lib/common';
import { getDomain, removeDestinationDocker } from '$lib/common'; import { getDomain, removeDestinationDocker } from '$lib/common';

View File

@@ -50,12 +50,12 @@ export async function completeTransaction(transactionId) {
} }
export async function removeProxyConfiguration({ domain }) { export async function removeProxyConfiguration({ domain }) {
const transactionId = await getNextTransactionId();
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
const backendFound = await haproxy const backendFound = await haproxy
.get(`v2/services/haproxy/configuration/backends/${domain}`) .get(`v2/services/haproxy/configuration/backends/${domain}`)
.json(); .json();
if (backendFound) { if (backendFound) {
const transactionId = await getNextTransactionId();
await haproxy await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, { .delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: { searchParams: {
@@ -63,32 +63,9 @@ export async function removeProxyConfiguration({ domain }) {
} }
}) })
.json(); .json();
await completeTransaction(transactionId);
} }
const rules: any = await haproxy await removeWwwRedirection(domain);
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) {
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
}
}
await completeTransaction(transactionId);
} }
export async function forceSSLOffApplication({ domain }) { export async function forceSSLOffApplication({ domain }) {
if (!dev) { if (!dev) {
@@ -303,7 +280,8 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
} }
} }
export async function configureCoolifyProxyOff({ domain }) { export async function configureCoolifyProxyOff(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { try {
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
@@ -325,6 +303,7 @@ export async function configureCoolifyProxyOff({ domain }) {
if (!dev) { if (!dev) {
await forceSSLOffApplication({ domain }); await forceSSLOffApplication({ domain });
} }
await setWwwRedirection(fqdn);
} catch (error) { } catch (error) {
throw error?.response?.body || error; throw error?.response?.body || error;
} }
@@ -337,7 +316,8 @@ export async function checkHAProxy(haproxy) {
throw 'HAProxy is not running, but it should be!'; throw 'HAProxy is not running, but it should be!';
} }
} }
export async function configureCoolifyProxyOn({ domain }) { export async function configureCoolifyProxyOn(fqdn) {
const domain = getDomain(fqdn);
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { try {
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
@@ -544,7 +524,15 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
await checkHAProxy(haproxy); await checkHAProxy(haproxy);
try { try {
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json(); await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
return; const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
searchParams: {
transaction_id: transactionId
}
})
.json();
await completeTransaction(transactionId);
} catch (error) {} } catch (error) {}
try { try {
const transactionId = await getNextTransactionId(); const transactionId = await getNextTransactionId();
@@ -570,6 +558,12 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
port: port port: port
} }
}); });
console.log({
address: id,
check: 'enabled',
name: id,
port: port
});
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -596,9 +590,45 @@ export async function configureSimpleServiceProxyOff({ domain }) {
.json(); .json();
await completeTransaction(transactionId); await completeTransaction(transactionId);
} catch (error) {} } catch (error) {}
await removeWwwRedirection(domain);
return; return;
} }
export async function removeWwwRedirection(domain) {
const haproxy = await haproxyInstance();
try {
await checkHAProxy(haproxy);
} catch (error) {
return;
}
const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: {
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
if (rules.data.length > 0) {
const rule = rules.data.find((rule) =>
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
);
if (rule) {
const transactionId = await getNextTransactionId();
await haproxy
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
searchParams: {
transaction_id: transactionId,
parent_name: 'http',
parent_type: 'frontend'
}
})
.json();
await completeTransaction(transactionId);
}
}
}
export async function setWwwRedirection(fqdn) { export async function setWwwRedirection(fqdn) {
const haproxy = await haproxyInstance(); const haproxy = await haproxyInstance();
try { try {
@@ -612,6 +642,7 @@ export async function setWwwRedirection(fqdn) {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.'); const isWWW = fqdn.includes('www.');
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
const rules: any = await haproxy const rules: any = await haproxy
.get(`v2/services/haproxy/configuration/http_request_rules`, { .get(`v2/services/haproxy/configuration/http_request_rules`, {
searchParams: { searchParams: {
@@ -638,12 +669,12 @@ export async function setWwwRedirection(fqdn) {
}, },
json: { json: {
index: nextRule, index: nextRule,
cond: `${isWWW ? 'unless' : 'if'}`, cond: 'if',
cond_test: `{ hdr_beg(host) -i www }`, cond_test: contTest,
type: 'redirect', type: 'redirect',
redir_type: 'location', redir_type: 'location',
redir_value: redirectValue, redir_value: redirectValue,
redir_code: 301 redir_code: dev ? 302 : 301
} }
}) })
.json(); .json();

View File

@@ -7,6 +7,7 @@ import {
configureProxyForApplication, configureProxyForApplication,
forceSSLOnApplication, forceSSLOnApplication,
reloadHaproxy, reloadHaproxy,
setWwwRedirection,
startCoolifyProxy startCoolifyProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
import * as db from '$lib/database'; import * as db from '$lib/database';
@@ -40,6 +41,7 @@ export default async function () {
}); });
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain }); if (isHttps) await forceSSLOnApplication({ domain });
await setWwwRedirection(fqdn);
} }
} }
} }
@@ -52,6 +54,7 @@ export default async function () {
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy'); const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
if (!found) await startCoolifyProxy('/var/run/docker.sock'); if (!found) await startCoolifyProxy('/var/run/docker.sock');
await configureCoolifyProxyOn({ domain }); await configureCoolifyProxyOn({ domain });
await setWwwRedirection(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (isHttps) await forceSSLOnApplication({ domain }); if (isHttps) await forceSSLOnApplication({ domain });
} }

View File

@@ -7,6 +7,7 @@ import { letsEncrypt } from '$lib/letsencrypt';
import { import {
configureSimpleServiceProxyOn, configureSimpleServiceProxyOn,
reloadHaproxy, reloadHaproxy,
setWwwRedirection,
startHttpProxy, startHttpProxy,
startTcpProxy startTcpProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
@@ -86,13 +87,13 @@ export const post: RequestHandler = async (event) => {
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`); await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
await configureSimpleServiceProxyOn({ id, domain, port: consolePort }); await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
await db.updateMinioService({ id, publicPort }); await db.updateMinioService({ id, publicPort });
await startHttpProxy(destinationDocker, id, publicPort, apiPort); await startHttpProxy(destinationDocker, id, publicPort, apiPort);
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -52,6 +52,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -185,6 +185,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { getServiceImage, PrismaErrorHandler } from '$lib/database'; import { getServiceImage, PrismaErrorHandler } from '$lib/database';
@@ -70,6 +70,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -80,6 +80,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy'; import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
import { getDomain } from '$lib/components/common'; import { getDomain } from '$lib/components/common';
import { PrismaErrorHandler } from '$lib/database'; import { PrismaErrorHandler } from '$lib/database';
@@ -117,6 +117,7 @@ export const post: RequestHandler = async (event) => {
if (isHttps) { if (isHttps) {
await letsEncrypt({ domain, id }); await letsEncrypt({ domain, id });
} }
await setWwwRedirection(fqdn);
await reloadHaproxy(destinationDocker.engine); await reloadHaproxy(destinationDocker.engine);
return { return {
status: 200 status: 200

View File

@@ -9,6 +9,8 @@ import {
forceSSLOffApplication, forceSSLOffApplication,
forceSSLOnApplication, forceSSLOnApplication,
reloadHaproxy, reloadHaproxy,
removeWwwRedirection,
setWwwRedirection,
startCoolifyProxy startCoolifyProxy
} from '$lib/haproxy'; } from '$lib/haproxy';
import { letsEncrypt } from '$lib/letsencrypt'; import { letsEncrypt } from '$lib/letsencrypt';
@@ -45,9 +47,10 @@ export const del: RequestHandler = async (event) => {
const { fqdn } = await event.request.json(); const { fqdn } = await event.request.json();
try { try {
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
await configureCoolifyProxyOff({ domain }); await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
await configureCoolifyProxyOff(fqdn);
await removeWwwRedirection(domain);
return { return {
status: 201 status: 201
}; };
@@ -77,9 +80,10 @@ export const post: RequestHandler = async (event) => {
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } }); await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
} }
if (oldFqdn && oldFqdn !== fqdn) { if (oldFqdn && oldFqdn !== fqdn) {
const oldDomain = getDomain(oldFqdn);
if (oldFqdn) { if (oldFqdn) {
await configureCoolifyProxyOff({ domain: oldDomain }); const oldDomain = getDomain(oldFqdn);
await configureCoolifyProxyOff(oldFqdn);
await removeWwwRedirection(oldDomain);
} }
} }
if (fqdn) { if (fqdn) {
@@ -88,7 +92,8 @@ export const post: RequestHandler = async (event) => {
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
if (domain) { if (domain) {
await configureCoolifyProxyOn({ domain }); await configureCoolifyProxyOn(fqdn);
await setWwwRedirection(fqdn);
if (isHttps && !dev) { if (isHttps && !dev) {
await letsEncrypt({ domain, isCoolify: true }); await letsEncrypt({ domain, isCoolify: true });
await forceSSLOnApplication({ domain }); await forceSSLOnApplication({ domain });

View File

@@ -106,7 +106,7 @@
<div class="px-4 sm:px-6"> <div class="px-4 sm:px-6">
<div class="flex space-x-4 py-4 px-4"> <div class="flex space-x-4 py-4 px-4">
<p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p> <p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p>
<div class="justify-center text-center"> <div class="justify-center">
<input <input
bind:value={fqdn} bind:value={fqdn}
readonly={!$session.isAdmin || isFqdnSet} readonly={!$session.isAdmin || isFqdnSet}