WIP
This commit is contained in:
@@ -2,6 +2,7 @@ import { dev } from '$app/env';
|
|||||||
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
|
|
||||||
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555';
|
||||||
|
|
||||||
@@ -71,7 +72,9 @@ export async function removeProxyConfiguration(fqdn) {
|
|||||||
export async function forceSSLOffApplication(domain) {
|
export async function forceSSLOffApplication(domain) {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
let transactionId;
|
let transactionId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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`, {
|
||||||
@@ -87,7 +90,6 @@ export async function forceSSLOffApplication(domain) {
|
|||||||
);
|
);
|
||||||
if (rule) {
|
if (rule) {
|
||||||
transactionId = await getNextTransactionId();
|
transactionId = await getNextTransactionId();
|
||||||
|
|
||||||
await haproxy
|
await haproxy
|
||||||
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
|
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
@@ -188,168 +190,325 @@ export async function reloadHaproxy(engine) {
|
|||||||
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
||||||
}
|
}
|
||||||
export async function checkProxyConfigurations() {
|
export async function checkProxyConfigurations() {
|
||||||
|
const timeout = 10;
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
try {
|
try {
|
||||||
const stats: any = await haproxy.get(`v2/services/haproxy/stats/native`).json();
|
const stats: any = await haproxy.get(`v2/services/haproxy/stats/native`).json();
|
||||||
|
let transactionId = null;
|
||||||
for (const stat of stats[0].stats) {
|
for (const stat of stats[0].stats) {
|
||||||
if (stat.stats.status === 'DOWN' && stat.type === 'server') {
|
if (stat.stats.status !== 'no check' && stat.type === 'server') {
|
||||||
const {
|
if (!transactionId) await getNextTransactionId();
|
||||||
name,
|
const { backend_name: backendName } = stat;
|
||||||
backend_name: backendName,
|
await haproxy
|
||||||
stats: { lastchg }
|
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
|
||||||
} = stat;
|
searchParams: {
|
||||||
|
transaction_id: transactionId
|
||||||
const { fqdn } = await db.listSettings();
|
}
|
||||||
if (fqdn) {
|
})
|
||||||
const domain = getDomain(fqdn);
|
.json();
|
||||||
if (backendName === domain) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const application = await db.prisma.application.findUnique({
|
|
||||||
where: { id: name },
|
|
||||||
include: { destinationDocker: true }
|
|
||||||
});
|
|
||||||
const service = await db.prisma.service.findUnique({
|
|
||||||
where: { id: name },
|
|
||||||
include: { destinationDocker: true }
|
|
||||||
});
|
|
||||||
if (!application && !service) {
|
|
||||||
const transactionId = await getNextTransactionId();
|
|
||||||
await haproxy
|
|
||||||
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
|
|
||||||
searchParams: {
|
|
||||||
transaction_id: transactionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.json();
|
|
||||||
return await completeTransaction(transactionId);
|
|
||||||
}
|
|
||||||
if (application?.destinationDocker?.engine && lastchg > 120) {
|
|
||||||
const found = await checkContainer(application.destinationDocker.engine, name);
|
|
||||||
if (!found) {
|
|
||||||
const transactionId = await getNextTransactionId();
|
|
||||||
await haproxy
|
|
||||||
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
|
|
||||||
searchParams: {
|
|
||||||
transaction_id: transactionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.json();
|
|
||||||
return await completeTransaction(transactionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service?.destinationDocker?.engine && lastchg > 120) {
|
|
||||||
const found = await checkContainer(service.destinationDocker.engine, name);
|
|
||||||
if (!found) {
|
|
||||||
const transactionId = await getNextTransactionId();
|
|
||||||
await haproxy
|
|
||||||
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
|
|
||||||
searchParams: {
|
|
||||||
transaction_id: transactionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.json();
|
|
||||||
return await completeTransaction(transactionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastchg > 120) {
|
|
||||||
const transactionId = await getNextTransactionId();
|
|
||||||
await haproxy
|
|
||||||
.delete(`v2/services/haproxy/configuration/backends/${backendName}`, {
|
|
||||||
searchParams: {
|
|
||||||
transaction_id: transactionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.json();
|
|
||||||
await completeTransaction(transactionId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (transactionId) await completeTransaction(transactionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error.response.body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function configureProxyForApplication({ domain, imageId, applicationId, port }) {
|
export async function configureHAProxy(fqdn, id, port, containerRunning, engine) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
|
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
|
||||||
|
|
||||||
|
console.log({ application: true, fqdn, domain, id, port, containerRunning, isHttps, isWWW });
|
||||||
|
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
|
|
||||||
let serverConfigured = false;
|
let transactionId;
|
||||||
let backendAvailable: any = null;
|
|
||||||
|
|
||||||
try {
|
if (!containerRunning) {
|
||||||
backendAvailable = await haproxy
|
try {
|
||||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
|
||||||
.json();
|
transactionId = await getNextTransactionId();
|
||||||
const server: any = await haproxy
|
await haproxy
|
||||||
.get(`v2/services/haproxy/configuration/servers/${imageId}`, {
|
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
backend: domain
|
transaction_id: transactionId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!transactionId) await getNextTransactionId();
|
||||||
|
let rules: any;
|
||||||
|
// Force SSL off
|
||||||
|
rules = 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.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
||||||
|
);
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.json();
|
|
||||||
|
|
||||||
if (backendAvailable && server) {
|
// Force WWW off
|
||||||
// Very sophisticated way to check if the server is already configured in proxy
|
rules = await haproxy
|
||||||
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
if (backendAvailable.data.name === domain) {
|
searchParams: {
|
||||||
if (server.data.check === 'enabled') {
|
parent_name: 'http',
|
||||||
if (server.data.address === imageId) {
|
parent_type: 'frontend'
|
||||||
if (server.data.port === port) {
|
}
|
||||||
serverConfigured = true;
|
})
|
||||||
|
.json();
|
||||||
|
if (rules.data.length > 0) {
|
||||||
|
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
//
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (transactionId) {
|
||||||
|
console.log(transactionId);
|
||||||
|
await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.response?.body) {
|
||||||
|
const json = JSON.parse(error.response.body);
|
||||||
|
if (json.code === 400 && json.message.includes('could not resolve address')) {
|
||||||
|
await stopCoolifyProxy(engine);
|
||||||
|
await startCoolifyProxy(engine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log('adding ', domain);
|
||||||
|
let serverConfigured = false;
|
||||||
|
let backendAvailable: any = null;
|
||||||
|
try {
|
||||||
|
backendAvailable = await haproxy
|
||||||
|
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||||
|
.json();
|
||||||
|
const server: any = await haproxy
|
||||||
|
.get(`v2/services/haproxy/configuration/servers/${id}`, {
|
||||||
|
searchParams: {
|
||||||
|
backend: domain
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
|
||||||
|
if (backendAvailable && server) {
|
||||||
|
// Very sophisticated way to check if the server is already configured in proxy
|
||||||
|
if (backendAvailable.data.forwardfor.enabled === 'enabled') {
|
||||||
|
if (backendAvailable.data.name === domain) {
|
||||||
|
if (server.data.check === 'disabled') {
|
||||||
|
if (server.data.address === id) {
|
||||||
|
if (server.data.port === port) {
|
||||||
|
serverConfigured = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
}
|
}
|
||||||
} catch (error) {
|
if (serverConfigured) {
|
||||||
//console.log('error getting backend or server', error?.response?.body);
|
console.log('server configured');
|
||||||
//
|
return;
|
||||||
}
|
}
|
||||||
|
if (!transactionId) transactionId = await getNextTransactionId();
|
||||||
if (serverConfigured) return;
|
if (backendAvailable) {
|
||||||
const transactionId = await getNextTransactionId();
|
await haproxy
|
||||||
if (backendAvailable) {
|
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||||
await haproxy
|
searchParams: {
|
||||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
transaction_id: transactionId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
console.log('adding ', domain);
|
||||||
|
await haproxy.post('v2/services/haproxy/configuration/backends', {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
transaction_id: transactionId
|
transaction_id: transactionId
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
'init-addr': 'last,libc,none',
|
||||||
|
forwardfor: { enabled: 'enabled' },
|
||||||
|
name: domain
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.json();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await haproxy.post('v2/services/haproxy/configuration/backends', {
|
|
||||||
searchParams: {
|
|
||||||
transaction_id: transactionId
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
'init-addr': 'last,libc,none',
|
|
||||||
forwardfor: { enabled: 'enabled' },
|
|
||||||
name: domain
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await haproxy.post('v2/services/haproxy/configuration/servers', {
|
await haproxy.post('v2/services/haproxy/configuration/servers', {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
transaction_id: transactionId,
|
transaction_id: transactionId,
|
||||||
backend: domain
|
backend: domain
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
address: imageId,
|
address: id,
|
||||||
check: 'enabled',
|
check: 'disabled',
|
||||||
name: imageId,
|
name: id,
|
||||||
port: port
|
port: port
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let rules: any;
|
||||||
|
|
||||||
|
// Force SSL off
|
||||||
|
rules = 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.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
||||||
|
);
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} catch (error) {
|
// Generate SSL && force SSL on
|
||||||
throw error?.response?.body || error;
|
|
||||||
} finally {
|
if (isHttps) {
|
||||||
await completeTransaction(transactionId);
|
await letsEncrypt(domain, id, false);
|
||||||
|
rules = await haproxy
|
||||||
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
let nextRule = 0;
|
||||||
|
if (rules.data.length > 0) {
|
||||||
|
const rule = rules.data.find((rule) =>
|
||||||
|
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
||||||
|
);
|
||||||
|
if (rule) return;
|
||||||
|
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
await haproxy
|
||||||
|
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
transaction_id: transactionId,
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
index: nextRule,
|
||||||
|
cond: 'if',
|
||||||
|
cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
|
||||||
|
type: 'redirect',
|
||||||
|
redir_type: 'scheme',
|
||||||
|
redir_value: 'https',
|
||||||
|
redir_code: dev ? 302 : 301
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// WWW redirect on
|
||||||
|
rules = await haproxy
|
||||||
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
let nextRule = 0;
|
||||||
|
if (rules.data.length > 0) {
|
||||||
|
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
|
||||||
|
if (rule) return;
|
||||||
|
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
await haproxy
|
||||||
|
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
transaction_id: transactionId,
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
index: nextRule,
|
||||||
|
cond: 'if',
|
||||||
|
cond_test: contTest,
|
||||||
|
type: 'redirect',
|
||||||
|
redir_type: 'location',
|
||||||
|
redir_value: redirectValue,
|
||||||
|
redir_code: dev ? 302 : 301
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw error?.response?.body || error;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (transactionId) {
|
||||||
|
console.log('Committing transaction');
|
||||||
|
await haproxy.put(`v2/services/haproxy/transactions/${transactionId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error?.response?.body) {
|
||||||
|
const json = JSON.parse(error.response.body);
|
||||||
|
if (json.code === 400 && json.message.includes('could not resolve address')) {
|
||||||
|
await stopCoolifyProxy(engine);
|
||||||
|
await startCoolifyProxy(engine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +603,7 @@ export async function configureCoolifyProxyOn(fqdn) {
|
|||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
address: dev ? 'host.docker.internal' : 'coolify',
|
address: dev ? 'host.docker.internal' : 'coolify',
|
||||||
check: 'enabled',
|
check: 'enable',
|
||||||
fall: 10,
|
fall: 10,
|
||||||
name: 'coolify',
|
name: 'coolify',
|
||||||
port: 3000
|
port: 3000
|
||||||
@@ -587,6 +746,7 @@ export async function configureNetworkCoolifyProxy(engine) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
||||||
|
console.log({ service: true, id, domain, port });
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
let serverConfigured = false;
|
let serverConfigured = false;
|
||||||
@@ -637,7 +797,7 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
|||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
address: id,
|
address: id,
|
||||||
check: 'enabled',
|
check: 'enable',
|
||||||
name: id,
|
name: id,
|
||||||
port: port
|
port: port
|
||||||
}
|
}
|
||||||
@@ -676,29 +836,37 @@ export async function removeWwwRedirection(fqdn) {
|
|||||||
|
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
await checkHAProxy();
|
await checkHAProxy();
|
||||||
const rules: any = await haproxy
|
|
||||||
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
let transactionId;
|
||||||
searchParams: {
|
|
||||||
parent_name: 'http',
|
try {
|
||||||
parent_type: 'frontend'
|
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(redirectValue));
|
||||||
|
if (rule) {
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.json();
|
|
||||||
if (rules.data.length > 0) {
|
|
||||||
const rule = rules.data.find((rule) => rule.redir_value.includes(redirectValue));
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
if (transactionId) await completeTransaction(transactionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function setWwwRedirection(fqdn) {
|
export async function setWwwRedirection(fqdn) {
|
||||||
|
@@ -14,7 +14,7 @@ export default async function ({
|
|||||||
buildId
|
buildId
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
try {
|
try {
|
||||||
saveBuildLog({ line: 'GitHub importer started', buildId, applicationId });
|
saveBuildLog({ line: 'GitHub importer started.', buildId, applicationId });
|
||||||
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
const { privateKey, appId, installationId } = await db.getUniqueGithubApp({ githubAppId });
|
||||||
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
const githubPrivateKey = privateKey.replace(/\\n/g, '\n').replace(/"/g, '');
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ import * as db from '$lib/database';
|
|||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
import getPort, { portNumbers } from 'get-port';
|
import getPort, { portNumbers } from 'get-port';
|
||||||
|
|
||||||
export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
|
export async function letsEncrypt(domain, id = null, isCoolify = false) {
|
||||||
try {
|
try {
|
||||||
const data = await db.prisma.setting.findFirst();
|
const data = await db.prisma.setting.findFirst();
|
||||||
const { minPort, maxPort } = data;
|
const { minPort, maxPort } = data;
|
||||||
@@ -47,7 +47,6 @@ export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await forceSSLOffApplication(domain);
|
|
||||||
if (dualCerts) {
|
if (dualCerts) {
|
||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
`DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email ${
|
||||||
@@ -71,9 +70,5 @@ export async function letsEncrypt({ domain, isCoolify = false, id = null }) {
|
|||||||
if (error.code !== 0) {
|
if (error.code !== 0) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (!isCoolify) {
|
|
||||||
await forceSSLOnApplication(domain);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,12 +4,6 @@ import * as buildpacks from '../buildPacks';
|
|||||||
import * as importers from '../importers';
|
import * as importers from '../importers';
|
||||||
import { dockerInstance } from '../docker';
|
import { dockerInstance } from '../docker';
|
||||||
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
||||||
import {
|
|
||||||
checkProxyConfigurations,
|
|
||||||
configureProxyForApplication,
|
|
||||||
reloadHaproxy,
|
|
||||||
setWwwRedirection
|
|
||||||
} from '../haproxy';
|
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { decrypt } from '$lib/crypto';
|
import { decrypt } from '$lib/crypto';
|
||||||
import { sentry } from '$lib/common';
|
import { sentry } from '$lib/common';
|
||||||
@@ -261,26 +255,27 @@ export default async function (job) {
|
|||||||
sentry.captureException(error);
|
sentry.captureException(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
try {
|
saveBuildLog({ line: 'Proxy will be configured shortly.', buildId, applicationId });
|
||||||
if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
// try {
|
||||||
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
|
// if (destinationDockerId && destinationDocker.isCoolifyProxyUsed) {
|
||||||
await checkProxyConfigurations();
|
// saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
|
||||||
await configureProxyForApplication({ domain, imageId, applicationId, port });
|
// await checkProxyConfigurations();
|
||||||
if (isHttps) await letsEncrypt({ domain, id: applicationId });
|
// await configureProxyForApplication(domain, imageId, port);
|
||||||
await setWwwRedirection(fqdn);
|
// if (isHttps) await letsEncrypt({ domain, id: applicationId });
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await setWwwRedirection(fqdn);
|
||||||
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
} else {
|
// saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
|
||||||
saveBuildLog({
|
// } else {
|
||||||
line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
|
// saveBuildLog({
|
||||||
buildId,
|
// line: 'Coolify Proxy is not configured for this destination. Nothing else to do.',
|
||||||
applicationId
|
// buildId,
|
||||||
});
|
// applicationId
|
||||||
}
|
// });
|
||||||
} catch (error) {
|
// }
|
||||||
saveBuildLog({ line: error.stdout || error, buildId, applicationId });
|
// } catch (error) {
|
||||||
sentry.captureException(error);
|
// saveBuildLog({ line: error.stdout || error, buildId, applicationId });
|
||||||
throw new Error(error);
|
// sentry.captureException(error);
|
||||||
}
|
// throw new Error(error);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -87,8 +87,8 @@ const cron = async () => {
|
|||||||
|
|
||||||
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
await queue.proxy.add('proxy', {}, { repeat: { every: 10000 } });
|
||||||
// await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } });
|
// await queue.ssl.add('ssl', {}, { repeat: { every: 10000 } });
|
||||||
await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } });
|
// await queue.cleanup.add('cleanup', {}, { repeat: { every: 600000 } });
|
||||||
await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
// await queue.sslRenew.add('sslRenew', {}, { repeat: { every: 1800000 } });
|
||||||
|
|
||||||
const events = {
|
const events = {
|
||||||
proxy: new QueueEvents('proxy', { ...connectionOptions }),
|
proxy: new QueueEvents('proxy', { ...connectionOptions }),
|
||||||
|
@@ -1,20 +1,15 @@
|
|||||||
|
import * as db from '$lib/database';
|
||||||
import { getDomain } from '$lib/common';
|
import { getDomain } from '$lib/common';
|
||||||
import { getApplicationById, prisma, supportedServiceTypesAndVersions } from '$lib/database';
|
|
||||||
import { dockerInstance } from '$lib/docker';
|
|
||||||
import {
|
import {
|
||||||
checkContainer,
|
checkContainer,
|
||||||
checkProxyConfigurations,
|
checkProxyConfigurations,
|
||||||
configureCoolifyProxyOn,
|
configureCoolifyProxyOn,
|
||||||
configureProxyForApplication,
|
configureHAProxy,
|
||||||
configureSimpleServiceProxyOn,
|
|
||||||
forceSSLOnApplication,
|
forceSSLOnApplication,
|
||||||
reloadHaproxy,
|
|
||||||
setWwwRedirection,
|
setWwwRedirection,
|
||||||
startCoolifyProxy,
|
startCoolifyProxy,
|
||||||
startHttpProxy
|
startHttpProxy
|
||||||
} from '$lib/haproxy';
|
} from '$lib/haproxy';
|
||||||
import * as db from '$lib/database';
|
|
||||||
// import { generateRemoteEngine } from '$lib/components/common';
|
|
||||||
|
|
||||||
export default async function () {
|
export default async function () {
|
||||||
try {
|
try {
|
||||||
@@ -23,83 +18,55 @@ export default async function () {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Check destination containers and configure proxy if needed
|
const applications = await db.prisma.application.findMany({
|
||||||
const destinationDockers = await prisma.destinationDocker.findMany({});
|
include: { destinationDocker: true }
|
||||||
for (const destination of destinationDockers) {
|
});
|
||||||
if (destination.isCoolifyProxyUsed) {
|
|
||||||
// if (destination.remoteEngine) {
|
for (const application of applications) {
|
||||||
// const engine = generateRemoteEngine(destination);
|
const {
|
||||||
// }
|
fqdn,
|
||||||
const docker = dockerInstance({ destinationDocker: destination });
|
id,
|
||||||
const containers = await docker.engine.listContainers();
|
port,
|
||||||
const configurations = containers.filter(
|
destinationDocker: { engine }
|
||||||
(container) => container.Labels['coolify.managed']
|
} = application;
|
||||||
);
|
const containerRunning = await checkContainer(engine, id);
|
||||||
for (const configuration of configurations) {
|
await configureHAProxy(fqdn, id, port, containerRunning, engine);
|
||||||
if (configuration.Labels['coolify.configuration']) {
|
}
|
||||||
const parsedConfiguration = JSON.parse(
|
|
||||||
Buffer.from(configuration.Labels['coolify.configuration'], 'base64').toString()
|
const services = await db.prisma.service.findMany({
|
||||||
);
|
include: {
|
||||||
if (
|
destinationDocker: true,
|
||||||
parsedConfiguration &&
|
minio: true,
|
||||||
configuration.Labels['coolify.type'] === 'standalone-application'
|
plausibleAnalytics: true,
|
||||||
) {
|
vscodeserver: true,
|
||||||
const { fqdn, applicationId, port, pullmergeRequestId } = parsedConfiguration;
|
wordpress: true
|
||||||
if (fqdn) {
|
}
|
||||||
const found = await getApplicationById({ id: applicationId });
|
});
|
||||||
if (found) {
|
|
||||||
const domain = getDomain(fqdn);
|
for (const service of services) {
|
||||||
await configureProxyForApplication({
|
const {
|
||||||
domain,
|
fqdn,
|
||||||
imageId: pullmergeRequestId
|
id,
|
||||||
? `${applicationId}-${pullmergeRequestId}`
|
type,
|
||||||
: applicationId,
|
destinationDocker: { engine }
|
||||||
applicationId,
|
} = service;
|
||||||
port
|
const found = db.supportedServiceTypesAndVersions.find((a) => a.name === type);
|
||||||
});
|
if (found) {
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const port = found.ports.main;
|
||||||
if (isHttps) await forceSSLOnApplication(domain);
|
const publicPort = service[type]?.publicPort;
|
||||||
await setWwwRedirection(fqdn);
|
const containerRunning = await checkContainer(engine, id);
|
||||||
}
|
await configureHAProxy(fqdn, id, port, containerRunning, engine);
|
||||||
}
|
if (publicPort) {
|
||||||
}
|
const containerFound = await checkContainer(
|
||||||
}
|
service.destinationDocker.engine,
|
||||||
}
|
`haproxy-for-${publicPort}`
|
||||||
for (const container of containers) {
|
);
|
||||||
const image = container.Image.split(':')[0];
|
if (!containerFound) {
|
||||||
const found = supportedServiceTypesAndVersions.find((a) => a.baseImage === image);
|
await startHttpProxy(service.destinationDocker, id, publicPort, 9000);
|
||||||
if (found) {
|
|
||||||
const type = found.name;
|
|
||||||
const mainPort = found.ports.main;
|
|
||||||
const id = container.Names[0].replace('/', '');
|
|
||||||
const service = await db.prisma.service.findUnique({
|
|
||||||
where: { id },
|
|
||||||
include: {
|
|
||||||
destinationDocker: true,
|
|
||||||
minio: true,
|
|
||||||
plausibleAnalytics: true,
|
|
||||||
vscodeserver: true,
|
|
||||||
wordpress: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const { fqdn } = service;
|
|
||||||
const domain = getDomain(fqdn);
|
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: mainPort });
|
|
||||||
const publicPort = service[type]?.publicPort;
|
|
||||||
if (publicPort) {
|
|
||||||
const containerFound = await checkContainer(
|
|
||||||
destination.engine,
|
|
||||||
`haproxy-for-${publicPort}`
|
|
||||||
);
|
|
||||||
if (!containerFound) {
|
|
||||||
await startHttpProxy(destination, id, publicPort, 9000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const services = await prisma.service.findMany({});
|
|
||||||
// Check Coolify FQDN and configure proxy if needed
|
// Check Coolify FQDN and configure proxy if needed
|
||||||
const { fqdn } = await db.listSettings();
|
const { fqdn } = await db.listSettings();
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
|
@@ -20,7 +20,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
await docker.engine.getContainer(id).stop();
|
await docker.engine.getContainer(id).stop();
|
||||||
}
|
}
|
||||||
await removeProxyConfiguration(fqdn);
|
// await removeProxyConfiguration(fqdn);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
@@ -24,7 +24,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await checkHAProxy();
|
// await checkHAProxy();
|
||||||
const service = await db.getService({ id, teamId });
|
const service = await db.getService({ id, teamId });
|
||||||
const {
|
const {
|
||||||
type,
|
type,
|
||||||
@@ -96,16 +96,16 @@ 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 checkProxyConfigurations();
|
// await checkProxyConfigurations();
|
||||||
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 setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
@@ -35,7 +35,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await stopTcpHttpProxy(destinationDocker, publicPort);
|
await stopTcpHttpProxy(destinationDocker, publicPort);
|
||||||
await configureSimpleServiceProxyOff(fqdn);
|
// await configureSimpleServiceProxyOff(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
@@ -56,14 +56,14 @@ 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 checkProxyConfigurations();
|
// await checkProxyConfigurations();
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
|
// await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
|
||||||
|
|
||||||
if (isHttps) {
|
// if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
// await letsEncrypt({ domain, id });
|
||||||
}
|
// }
|
||||||
await setWwwRedirection(fqdn);
|
// await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
@@ -187,14 +187,14 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
await asyncExecShell(
|
await asyncExecShell(
|
||||||
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
|
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up --build -d`
|
||||||
);
|
);
|
||||||
await checkProxyConfigurations();
|
// await checkProxyConfigurations();
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: 8000 });
|
// await configureSimpleServiceProxyOn({ id, domain, port: 8000 });
|
||||||
|
|
||||||
if (isHttps) {
|
// if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
// await letsEncrypt({ domain, id });
|
||||||
}
|
// }
|
||||||
await setWwwRedirection(fqdn);
|
// await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
@@ -74,14 +74,14 @@ 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 checkProxyConfigurations();
|
// await checkProxyConfigurations();
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
|
// await configureSimpleServiceProxyOn({ id, domain, port: 80 });
|
||||||
|
|
||||||
if (isHttps) {
|
// if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
// await letsEncrypt({ domain, id });
|
||||||
}
|
// }
|
||||||
await setWwwRedirection(fqdn);
|
// await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
@@ -84,14 +84,14 @@ 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 checkProxyConfigurations();
|
// await checkProxyConfigurations();
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
|
// await configureSimpleServiceProxyOn({ id, domain, port: 8080 });
|
||||||
|
|
||||||
if (isHttps) {
|
// if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
// await letsEncrypt({ domain, id });
|
||||||
}
|
// }
|
||||||
await setWwwRedirection(fqdn);
|
// await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
@@ -121,14 +121,14 @@ 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 checkProxyConfigurations();
|
// await checkProxyConfigurations();
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: 80 });
|
// await configureSimpleServiceProxyOn({ id, domain, port: 80 });
|
||||||
|
|
||||||
if (isHttps) {
|
// if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
// await letsEncrypt({ domain, id });
|
||||||
}
|
// }
|
||||||
await setWwwRedirection(fqdn);
|
// await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
// await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user