fix: toast, rde, webhooks

This commit is contained in:
Andras Bacsai 2022-11-03 11:32:18 +01:00
parent fa9738a2e0
commit 9dfbbe58ff
12 changed files with 446 additions and 689 deletions

View File

@ -34,7 +34,6 @@ export async function migrateServicesToNewTemplate() {
if (!service.type) {
continue;
}
console.log(service.type)
let template = templates.find(t => fixType(t.type) === fixType(service.type));
if (template) {
template = JSON.parse(JSON.stringify(template).replaceAll('$$id', service.id))

View File

@ -505,56 +505,59 @@ export async function createRemoteEngineConfiguration(id: string) {
const localPort = await getFreeSSHLocalPort(id);
const {
sshKey: { privateKey },
network,
remoteIpAddress,
remotePort,
remoteUser
} = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } });
await fs.writeFile(sshKeyFile, decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 });
// Needed for remote docker compose
const { stdout: numberOfSSHAgentsRunning } = await asyncExecShell(
`ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l`
);
if (numberOfSSHAgentsRunning !== '' && Number(numberOfSSHAgentsRunning.trim()) == 0) {
try {
await fs.stat(`/tmp/coolify-ssh-agent.pid`);
await fs.rm(`/tmp/coolify-ssh-agent.pid`);
} catch (error) { }
await asyncExecShell(`eval $(ssh-agent -sa /tmp/coolify-ssh-agent.pid)`);
}
await asyncExecShell(`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${sshKeyFile}`);
// const { stdout: numberOfSSHAgentsRunning } = await asyncExecShell(
// `ps ax | grep [s]sh-agent | grep coolify-ssh-agent.pid | grep -v grep | wc -l`
// );
// if (numberOfSSHAgentsRunning !== '' && Number(numberOfSSHAgentsRunning.trim()) == 0) {
// try {
// await fs.stat(`/tmp/coolify-ssh-agent.pid`);
// await fs.rm(`/tmp/coolify-ssh-agent.pid`);
// } catch (error) { }
// await asyncExecShell(`eval $(ssh-agent -sa /tmp/coolify-ssh-agent.pid)`);
// }
// await asyncExecShell(`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh-add -q ${sshKeyFile}`);
const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(
`ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${localPort}:localhost:${remotePort}' | grep -v grep | wc -l`
);
if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) {
try {
await asyncExecShell(
`SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`
);
} catch (error) { }
}
// const { stdout: numberOfSSHTunnelsRunning } = await asyncExecShell(
// `ps ax | grep 'ssh -F /dev/null -o StrictHostKeyChecking no -fNL ${localPort}:localhost:${remotePort}' | grep -v grep | wc -l`
// );
// if (numberOfSSHTunnelsRunning !== '' && Number(numberOfSSHTunnelsRunning.trim()) == 0) {
// try {
// await asyncExecShell(
// `SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid ssh -F /dev/null -o "StrictHostKeyChecking no" -fNL ${localPort}:localhost:${remotePort} ${remoteUser}@${remoteIpAddress}`
// );
// } catch (error) { }
// }
const config = sshConfig.parse('');
const foundWildcard = config.find({ Host: '*' });
if (!foundWildcard) {
const Host = `${remoteIpAddress}-remote`
await asyncExecShell(`ssh-keygen -R ${Host}`);
await asyncExecShell(`ssh-keygen -R ${remoteIpAddress}`);
await asyncExecShell(`ssh-keygen -R localhost:${localPort}`);
const found = config.find({ Host });
const foundIp = config.find({ Host: remoteIpAddress });
if (found) config.remove({ Host })
if (foundIp) config.remove({ Host: remoteIpAddress })
config.append({
Host: '*',
StrictHostKeyChecking: 'no',
ControlMaster: 'auto',
ControlPath: `${homedir}/.ssh/coolify-%r@%h:%p`,
ControlPersist: '10m'
})
}
const found = config.find({ Host: remoteIpAddress });
if (!found) {
config.append({
Host: remoteIpAddress,
Hostname: 'localhost',
Port: localPort.toString(),
Host,
Hostname: remoteIpAddress,
Port: remotePort.toString(),
User: remoteUser,
StrictHostKeyChecking: 'no',
IdentityFile: sshKeyFile,
StrictHostKeyChecking: 'no'
ControlMaster: 'auto',
ControlPath: `${homedir}/.ssh/coolify-${remoteIpAddress}-%r@%h:%p`,
ControlPersist: '10m'
});
}
try {
await fs.stat(`${homedir}/.ssh/`);
@ -565,27 +568,23 @@ export async function createRemoteEngineConfiguration(id: string) {
}
export async function executeSSHCmd({ dockerId, command }) {
const { execaCommand } = await import('execa')
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
let { remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId)
engine = `ssh://${remoteIpAddress}`
} else {
engine = 'unix:///var/run/docker.sock'
}
if (process.env.CODESANDBOX_HOST) {
if (command.startsWith('docker compose')) {
command = command.replace(/docker compose/gi, 'docker-compose')
}
}
command = `ssh ${remoteIpAddress} ${command}`
return await execaCommand(command)
return await execaCommand(`ssh ${remoteIpAddress}-remote ${command}`)
}
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
const { execaCommand } = await import('execa')
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId);
engine = `ssh://${remoteIpAddress}`;
engine = `ssh://${remoteIpAddress}-remote`;
} else {
engine = 'unix:///var/run/docker.sock';
}

View File

@ -204,8 +204,8 @@ export async function assignSSHKey(request: FastifyRequest) {
}
export async function verifyRemoteDockerEngineFn(id: string) {
await createRemoteEngineConfiguration(id);
const { remoteIpAddress, remoteUser, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
const host = `ssh://${remoteUser}@${remoteIpAddress}`
const { remoteIpAddress, network, isCoolifyProxyUsed } = await prisma.destinationDocker.findFirst({ where: { id } })
const host = `ssh://${remoteIpAddress}-remote`
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
if (!stdout) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
@ -215,8 +215,8 @@ export async function verifyRemoteDockerEngineFn(id: string) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable coolify-infra`);
}
if (isCoolifyProxyUsed) await startTraefikProxy(id);
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
try {
const { stdout: daemonJson } = await executeSSHCmd({ dockerId: id, command: `cat /etc/docker/daemon.json` });
let daemonJsonParsed = JSON.parse(daemonJson);
let isUpdated = false;
if (!daemonJsonParsed['live-restore'] || daemonJsonParsed['live-restore'] !== true) {

View File

@ -4,272 +4,22 @@ import { TraefikOtherConfiguration } from "./types";
import { OnlyId } from "../../../types";
import { getTemplates } from "../../../lib/services";
function generateLoadBalancerService(id, port) {
return {
loadbalancer: {
servers: [
{
url: `http://${id}:${port}`
}
]
}
};
}
function generateHttpRouter(id, nakedDomain, pathPrefix) {
return {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
service: `${id}`,
middlewares: []
}
}
function generateProtocolRedirectRouter(id, nakedDomain, pathPrefix, fromTo) {
if (fromTo === 'https-to-http') {
return {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
service: `${id}`,
tls: {
domains: {
main: `${nakedDomain}`
}
},
middlewares: ['redirect-to-http']
}
} else if (fromTo === 'http-to-https') {
return {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
service: `${id}`,
middlewares: ['redirect-to-https']
};
}
}
function configureMiddleware(
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type, isCustomSSL },
traefik
) {
if (isHttps) {
traefik.http.routers[id] = {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
service: `${id}`,
middlewares: ['redirect-to-https']
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${container}:${port}`
}
]
}
};
if (type === 'appwrite') {
traefik.http.routers[`${id}-realtime`] = {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
service: `${`${id}-realtime`}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: []
};
traefik.http.services[`${id}-realtime`] = {
loadbalancer: {
servers: [
{
url: `http://${container}-realtime:${port}`
}
]
}
};
}
if (isDualCerts) {
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
async function applicationConfiguration(traefik: any, remoteId: string | null = null) {
console.log({ type: 'applications', remoteId })
let applications = []
if (remoteId) {
applications = await prisma.application.findMany({
where: { destinationDocker: { id: remoteId } },
include: { destinationDocker: true, settings: true }
});
} else {
if (isWWW) {
traefik.http.routers[`${id}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-www']
};
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-non-www']
};
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${domain}\`) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
}
}
} else {
traefik.http.routers[id] = {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
service: `${id}`,
middlewares: []
};
traefik.http.routers[`${id}-secure`] = {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/\`)`,
service: `${id}`,
tls: {
domains: {
main: `${nakedDomain}`
}
},
middlewares: ['redirect-to-http']
};
traefik.http.services[id] = {
loadbalancer: {
servers: [
{
url: `http://${container}:${port}`
}
]
}
};
if (type === 'appwrite') {
traefik.http.routers[`${id}-realtime`] = {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`/v1/realtime\`)`,
service: `${id}-realtime`,
middlewares: []
};
traefik.http.services[`${id}-realtime`] = {
loadbalancer: {
servers: [
{
url: `http://${container}-realtime:${port}`
}
]
}
};
}
if (!isDualCerts) {
if (isWWW) {
traefik.http.routers[`${id}`].middlewares.push('redirect-to-www');
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}`].middlewares.push('redirect-to-non-www');
traefik.http.routers[`${id}-secure`].middlewares.push('redirect-to-non-www');
}
}
}
if (type === 'plausibleanalytics' && scriptName && scriptName !== 'plausible.js') {
if (!traefik.http.routers[`${id}`].middlewares.includes(`${id}-redir`)) {
traefik.http.routers[`${id}`].middlewares.push(`${id}-redir`);
}
if (!traefik.http.routers[`${id}-secure`].middlewares.includes(`${id}-redir`)) {
traefik.http.routers[`${id}-secure`].middlewares.push(`${id}-redir`);
}
}
}
export async function traefikConfiguration(request, reply) {
try {
const sslpath = '/etc/traefik/acme/custom';
const certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { remoteEngine: false, isCoolifyProxyUsed: true } } } } })
let parsedCertificates = []
for (const certificate of certificates) {
parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`,
keyFile: `${sslpath}/${certificate.id}-key.pem`
})
}
const traefik = {
tls: {
certificates: parsedCertificates
},
http: {
routers: {},
services: {},
middlewares: {
'redirect-to-https': {
redirectscheme: {
scheme: 'https'
}
},
'redirect-to-http': {
redirectscheme: {
scheme: 'http'
}
},
'redirect-to-non-www': {
redirectregex: {
regex: '^https?://www\\.(.+)',
replacement: 'http://${1}'
}
},
'redirect-to-www': {
redirectregex: {
regex: '^https?://(?:www\\.)?(.+)',
replacement: 'http://www.${1}'
}
}
}
}
};
const applications = await prisma.application.findMany({
applications = await prisma.application.findMany({
where: { destinationDocker: { remoteEngine: false } },
include: { destinationDocker: true, settings: true }
});
const data = {
applications: [],
services: [],
coolify: []
};
}
const configurableApplications = []
if (applications.length > 0) {
for (const application of applications) {
const {
fqdn,
@ -294,7 +44,7 @@ export async function traefikConfiguration(request, reply) {
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
data.applications.push({
configurableApplications.push({
id: `${id}-${key}`,
container: `${id}-${key}`,
port: customPort ? customPort : port || 3000,
@ -304,7 +54,8 @@ export async function traefikConfiguration(request, reply) {
isHttps,
isWWW,
isDualCerts: dualCerts,
isCustomSSL
isCustomSSL,
pathPrefix: '/'
});
}
}
@ -317,7 +68,7 @@ export async function traefikConfiguration(request, reply) {
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
data.applications.push({
configurableApplications.push({
id,
container: id,
port: port || 3000,
@ -327,7 +78,8 @@ export async function traefikConfiguration(request, reply) {
isHttps,
isWWW,
isDualCerts: dualCerts,
isCustomSSL
isCustomSSL,
pathPrefix: '/'
});
}
if (previews) {
@ -341,7 +93,7 @@ export async function traefikConfiguration(request, reply) {
for (const container of containers) {
const previewDomain = `${container.split('-')[1]}.${domain}`;
const nakedDomain = previewDomain.replace(/^www\./, '');
data.applications.push({
configurableApplications.push({
id: container,
container,
port: port || 3000,
@ -351,7 +103,8 @@ export async function traefikConfiguration(request, reply) {
isHttps,
isWWW,
isDualCerts: dualCerts,
isCustomSSL
isCustomSSL,
pathPrefix: '/'
});
}
}
@ -359,7 +112,103 @@ export async function traefikConfiguration(request, reply) {
}
}
}
const services: any = await prisma.service.findMany({
for (const application of configurableApplications) {
let { id, port, isCustomSSL, pathPrefix, isHttps, nakedDomain, isWWW, domain, dualCerts } = application
if (isHttps) {
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}-secure`, nakedDomain, pathPrefix, 'http-to-https')
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
if (dualCerts) {
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
} else {
if (isWWW) {
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-www']
};
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-non-www']
};
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${domain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
}
}
} else {
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix, 'https-to-http')
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
if (!dualCerts) {
if (isWWW) {
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-non-www');
}
}
}
}
}
}
async function serviceConfiguration(traefik: any, remoteId: string | null = null) {
console.log({ type: 'services', remoteId })
let services = [];
if (remoteId) {
services = await prisma.service.findMany({
where: { destinationDocker: { id: remoteId } },
include: {
destinationDocker: true,
persistentStorage: true,
serviceSecret: true,
serviceSetting: true,
},
orderBy: { createdAt: 'desc' }
});
} else {
services = await prisma.service.findMany({
where: { destinationDocker: { remoteEngine: false } },
include: {
destinationDocker: true,
@ -369,7 +218,10 @@ export async function traefikConfiguration(request, reply) {
},
orderBy: { createdAt: 'desc' },
});
}
const configurableServices = []
if (services.length > 0) {
for (const service of services) {
let {
fqdn,
@ -399,7 +251,7 @@ export async function traefikConfiguration(request, reply) {
configuration.port = foundPortVariable.value
}
if (fqdn) {
data.services.push({
configurableServices.push({
id: oneService,
publicPort,
fqdn,
@ -416,7 +268,7 @@ export async function traefikConfiguration(request, reply) {
port = foundPortVariable.value
}
if (fqdn) {
data.services.push({
configurableServices.push({
id: oneService,
configuration: {
port
@ -431,7 +283,8 @@ export async function traefikConfiguration(request, reply) {
}
}
}
for (const service of data.services) {
for (const service of configurableServices) {
let { id, fqdn, dualCerts, configuration, isCustomSSL = false } = service
let port, pathPrefix, customDomain;
if (configuration) {
@ -523,14 +376,17 @@ export async function traefikConfiguration(request, reply) {
}
}
}
}
}
async function coolifyConfiguration(traefik: any) {
const { fqdn, dualCerts } = await prisma.setting.findFirst();
let coolifyConfigurations = []
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
data.coolify.push({
coolifyConfigurations.push({
id: isDev ? 'host.docker.internal' : 'coolify',
container: isDev ? 'host.docker.internal' : 'coolify',
port: 3000,
@ -538,39 +394,191 @@ export async function traefikConfiguration(request, reply) {
nakedDomain,
isHttps,
isWWW,
isDualCerts: dualCerts
isDualCerts: dualCerts,
pathPrefix: '/'
});
}
for (const application of data.applications) {
configureMiddleware(application, traefik);
}
// 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`] = {
// replacepathregex: {
// regex: `/js/${scriptName}`,
// replacement: '/js/plausible.js'
// }
// };
// }
// }
for (const coolify of data.coolify) {
configureMiddleware(coolify, traefik);
for (const coolify of coolifyConfigurations) {
const { id, pathPrefix, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type, isCustomSSL } = coolify;
if (isHttps) {
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}-secure`, nakedDomain, pathPrefix, 'http-to-https')
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
if (dualCerts) {
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`)) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
} else {
if (isWWW) {
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-www']
};
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}-${port || 'default'}-secure-www`] = {
entrypoints: ['websecure'],
rule: `Host(\`www.${nakedDomain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: {
domains: {
main: `${domain}`
}
},
middlewares: ['redirect-to-non-www']
};
traefik.http.routers[`${id}-${port || 'default'}-secure`] = {
entrypoints: ['websecure'],
rule: `Host(\`${domain}\`) && PathPrefix(\`${pathPrefix}\`)`,
service: `${id}`,
tls: isCustomSSL ? true : {
certresolver: 'letsencrypt'
},
middlewares: []
};
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
}
}
} else {
traefik.http.routers[`${id}-${port || 'default'}`] = generateHttpRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix)
traefik.http.routers[`${id}-${port || 'default'}-secure`] = generateProtocolRedirectRouter(`${id}-${port || 'default'}`, nakedDomain, pathPrefix, 'https-to-http')
traefik.http.services[`${id}-${port || 'default'}`] = generateLoadBalancerService(id, port)
if (!dualCerts) {
if (isWWW) {
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-www');
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-www');
} else {
traefik.http.routers[`${id}-${port || 'default'}`].middlewares.push('redirect-to-non-www');
traefik.http.routers[`${id}-${port || 'default'}-secure`].middlewares.push('redirect-to-non-www');
}
}
}
}
}
function generateLoadBalancerService(id, port) {
return {
loadbalancer: {
servers: [
{
url: `http://${id}:${port}`
}
]
}
};
}
function generateHttpRouter(id, nakedDomain, pathPrefix) {
return {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
service: `${id}`,
middlewares: []
}
}
function generateProtocolRedirectRouter(id, nakedDomain, pathPrefix, fromTo) {
if (fromTo === 'https-to-http') {
return {
entrypoints: ['websecure'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
service: `${id}`,
tls: {
domains: {
main: `${nakedDomain}`
}
},
middlewares: ['redirect-to-http']
}
} else if (fromTo === 'http-to-https') {
return {
entrypoints: ['web'],
rule: `(Host(\`${nakedDomain}\`) || Host(\`www.${nakedDomain}\`))${pathPrefix ? `&& PathPrefix(\`${pathPrefix}\`)` : ''}`,
service: `${id}`,
middlewares: ['redirect-to-https']
};
}
}
export async function traefikConfiguration(request: FastifyRequest<OnlyId>, remote: boolean = false) {
try {
const { id = null } = request.params
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 } } } } })
if (remote) {
certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
}
let parsedCertificates = []
for (const certificate of certificates) {
parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`,
keyFile: `${sslpath}/${certificate.id}-key.pem`
})
}
const traefik = {
tls: {
certificates: parsedCertificates
},
http: {
routers: {},
services: {},
middlewares: {
'redirect-to-https': {
redirectscheme: {
scheme: 'https'
}
},
'redirect-to-http': {
redirectscheme: {
scheme: 'http'
}
},
'redirect-to-non-www': {
redirectregex: {
regex: '^https?://www\\.(.+)',
replacement: 'http://${1}'
}
},
'redirect-to-www': {
redirectregex: {
regex: '^https?://(?:www\\.)?(.+)',
replacement: 'http://www.${1}'
}
}
}
}
};
await applicationConfiguration(traefik, id)
await serviceConfiguration(traefik, id)
if (!remote) {
await coolifyConfiguration(traefik)
}
if (Object.keys(traefik.http.routers).length === 0) {
traefik.http.routers = null;
}
@ -584,7 +592,6 @@ export async function traefikConfiguration(request, reply) {
return errorHandler({ status, message })
}
}
export async function traefikOtherConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
try {
const { id } = request.query
@ -705,273 +712,3 @@ export async function traefikOtherConfiguration(request: FastifyRequest<TraefikO
return errorHandler({ status, message })
}
}
export async function remoteTraefikConfiguration(request: FastifyRequest<OnlyId>) {
const { id } = request.params
try {
const sslpath = '/etc/traefik/acme/custom';
const certificates = await prisma.certificate.findMany({ where: { team: { applications: { some: { settings: { isCustomSSL: true } } }, destinationDocker: { some: { id, remoteEngine: true, isCoolifyProxyUsed: true, remoteVerified: true } } } } })
let parsedCertificates = []
for (const certificate of certificates) {
parsedCertificates.push({
certFile: `${sslpath}/${certificate.id}-cert.pem`,
keyFile: `${sslpath}/${certificate.id}-key.pem`
})
}
const traefik = {
tls: {
certificates: parsedCertificates
},
http: {
routers: {},
services: {},
middlewares: {
'redirect-to-https': {
redirectscheme: {
scheme: 'https'
}
},
'redirect-to-http': {
redirectscheme: {
scheme: 'http'
}
},
'redirect-to-non-www': {
redirectregex: {
regex: '^https?://www\\.(.+)',
replacement: 'http://${1}'
}
},
'redirect-to-www': {
redirectregex: {
regex: '^https?://(?:www\\.)?(.+)',
replacement: 'http://www.${1}'
}
}
}
}
};
const applications = await prisma.application.findMany({
where: { destinationDocker: { id } },
include: { destinationDocker: true, settings: true }
});
const data = {
applications: [],
services: [],
coolify: []
};
for (const application of applications) {
const {
fqdn,
id,
port,
buildPack,
dockerComposeConfiguration,
destinationDocker,
destinationDockerId,
settings: { previews, dualCerts, isCustomSSL }
} = application;
if (destinationDockerId) {
const { id: dockerId, network } = destinationDocker;
const isRunning = true;
if (buildPack === 'compose') {
const services = Object.entries(JSON.parse(dockerComposeConfiguration))
for (const service of services) {
const [key, value] = service
const { port: customPort, fqdn } = value
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
data.applications.push({
id: `${id}-${key}`,
container: `${id}-${key}`,
port: customPort ? customPort : port || 3000,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts,
isCustomSSL
});
}
}
continue;
}
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
data.applications.push({
id,
container: id,
port: port || 3000,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts,
isCustomSSL
});
}
if (previews) {
const { stdout } = await executeDockerCmd({ dockerId, command: `docker container ls --filter="status=running" --filter="network=${network}" --filter="name=${id}-" --format="{{json .Names}}"` })
const containers = stdout
.trim()
.split('\n')
.filter((a) => a)
.map((c) => c.replace(/"/g, ''));
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,
isWWW,
isDualCerts: dualCerts,
isCustomSSL
});
}
}
}
}
}
}
const services: any = await prisma.service.findMany({
where: { destinationDocker: { id } },
include: {
destinationDocker: true,
persistentStorage: true,
serviceSecret: true,
serviceSetting: true,
},
orderBy: { createdAt: 'desc' }
});
for (const service of services) {
const {
fqdn,
id,
type,
destinationDocker,
destinationDockerId,
dualCerts,
plausibleAnalytics
} = service;
if (destinationDockerId) {
const templates = await getTemplates();
let found = templates.find((a) => a.type === type);
if (found) {
const port = found.ports.main;
const publicPort = service[type]?.publicPort;
const isRunning = true;
if (fqdn) {
const domain = getDomain(fqdn);
const nakedDomain = domain.replace(/^www\./, '');
const isHttps = fqdn.startsWith('https://');
const isWWW = fqdn.includes('www.');
if (isRunning) {
// Plausible Analytics custom script
let scriptName = false;
if (type === 'plausibleanalytics' && plausibleAnalytics.scriptName !== 'plausible.js') {
scriptName = plausibleAnalytics.scriptName;
}
let container = id;
let otherDomain = null;
let otherNakedDomain = null;
let otherIsHttps = null;
let otherIsWWW = null;
// if (type === 'minio' && service.minio.apiFqdn) {
// otherDomain = getDomain(service.minio.apiFqdn);
// otherNakedDomain = otherDomain.replace(/^www\./, '');
// otherIsHttps = service.minio.apiFqdn.startsWith('https://');
// otherIsWWW = service.minio.apiFqdn.includes('www.');
// }
if (type === 'minio') {
const domain = service.serviceSetting.find((a) => a.name === 'MINIO_SERVER_URL')?.value
otherDomain = getDomain(domain);
otherNakedDomain = otherDomain.replace(/^www\./, '');
otherIsHttps = domain.startsWith('https://');
otherIsWWW = domain.includes('www.');
}
data.services.push({
id,
container,
type,
otherDomain,
otherNakedDomain,
otherIsHttps,
otherIsWWW,
port,
publicPort,
domain,
nakedDomain,
isRunning,
isHttps,
isWWW,
isDualCerts: dualCerts,
scriptName
});
}
}
}
}
}
for (const application of data.applications) {
configureMiddleware(application, traefik);
}
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`] = {
replacepathregex: {
regex: `/js/${scriptName}`,
replacement: '/js/plausible.js'
}
};
}
}
for (const coolify of data.coolify) {
configureMiddleware(coolify, traefik);
}
if (Object.keys(traefik.http.routers).length === 0) {
traefik.http.routers = null;
}
if (Object.keys(traefik.http.services).length === 0) {
traefik.http.services = null;
}
return {
...traefik
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@ -1,13 +1,13 @@
import { FastifyPluginAsync } from 'fastify';
import { OnlyId } from '../../../types';
import { remoteTraefikConfiguration, traefikConfiguration, traefikOtherConfiguration } from './handlers';
import { traefikConfiguration, traefikOtherConfiguration } from './handlers';
import { TraefikOtherConfiguration } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
fastify.get<OnlyId>('/main.json', async (request, reply) => traefikConfiguration(request, false));
fastify.get<OnlyId>('/remote/:id', async (request) => traefikConfiguration(request, true));
fastify.get<OnlyId>('/remote/:id', async (request) => remoteTraefikConfiguration(request));
fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
};
export default root;

View File

@ -1,4 +1,4 @@
export interface OnlyId {
Params: { id: string },
Params: { id?: string },
}

View File

@ -4,7 +4,7 @@
export let type = 'info';
function success() {
if (type === 'success') {
return 'bg-coollabs';
return 'bg-dark lg:bg-primary';
}
}
</script>

View File

@ -6,7 +6,7 @@
{#if $toasts.length > 0}
<section>
<article class="toast toast-top toast-center rounded-none min-w-[18rem]" role="alert">
<article class="toast toast-top toast-center rounded-none w-2/3 lg:w-[20rem]" role="alert">
{#each $toasts as toast (toast.id)}
<Toast
type={toast.type}

View File

@ -68,12 +68,6 @@
</script>
<div class="w-full relative p-5 ">
{#if loading.usage}
<span class="indicator-item badge bg-yellow-500 badge-sm" />
{:else}
<span class="indicator-item badge bg-success badge-sm" />
{/if}
<div class="w-full flex flex-col lg:flex-row space-y-4 lg:space-y-0 space-x-4">
<div class="flex flex-col">
<h1 class="font-bold text-lg lg:text-xl truncate">
@ -99,6 +93,11 @@
class="btn btn-sm">Cleanup Storage</button
>
{/if}
{#if loading.usage}
<button id="streaming" class=" btn btn-sm bg-transparent border-none loading"
>Getting data...</button
>
{/if}
</div>
<div class="flex lg:flex-row flex-col gap-4">
<div class="flex lg:flex-row flex-col space-x-0 lg:space-x-2 space-y-2 lg:space-y-0" />

View File

@ -75,7 +75,7 @@
let statusInterval: any;
let forceDelete = false;
let stopping = false;
const { id } = $page.params;
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
@ -138,17 +138,17 @@
}
async function stopApplication() {
try {
$status.application.initialLoading = true;
stopping = true;
await post(`/applications/${id}/stop`, {});
} catch (error) {
return errorNotification(error);
} finally {
$status.application.initialLoading = false;
stopping = false;
await getStatus();
}
}
async function getStatus() {
if ($status.application.loading) return;
if ($status.application.loading && stopping) return;
$status.application.loading = true;
const data = await get(`/applications/${id}/status`);
@ -298,7 +298,29 @@
Application Error
</a>
{/if}
{#if $status.application.initialLoading}
{#if stopping}
<button class="btn btn-ghost btn-sm gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 animate-spin duration-500 ease-in-out"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M9 4.55a8 8 0 0 1 6 14.9m0 -4.45v5h5" />
<line x1="5.63" y1="7.16" x2="5.63" y2="7.17" />
<line x1="4.06" y1="11" x2="4.06" y2="11.01" />
<line x1="4.63" y1="15.1" x2="4.63" y2="15.11" />
<line x1="7.16" y1="18.37" x2="7.16" y2="18.38" />
<line x1="11" y1="19.94" x2="11" y2="19.95" />
</svg>
Stopping...
</button>
{:else if $status.application.initialLoading}
<button class="btn btn-ghost btn-sm gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -321,7 +343,6 @@
Loading...
</button>
{:else if $status.application.overallStatus === 'healthy'}
{#if application.buildPack !== 'compose'}
<button
on:click={restartApplication}

View File

@ -31,7 +31,7 @@
<div class="flex justify-center px-6 pb-8">
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex items-start lg:items-center space-x-0 lg:space-x-4 pb-5 flex-col space-y-4 lg:space-y-0">
<div class="flex items-start lg:items-center space-x-0 lg:space-x-4 pb-5 flex-col lg:flex-row space-y-4 lg:space-y-0">
<div class="title font-bold">{$t('forms.configuration')}</div>
<button type="submit" class="btn btn-sm bg-destinations w-full lg:w-fit" class:loading disabled={loading}
>{loading

View File

@ -184,15 +184,17 @@
? 'Verify Remote Docker Engine'
: 'Check Remote Docker Engine'}</button
>
{#if destination.remoteVerified}
<button
class="btn btn-sm"
class:loading={loading.restart}
class:bg-error={!loading.restart}
disabled={loading.restart}
on:click|preventDefault={forceRestartProxy}>{$t('destination.force_restart_proxy')}</button
on:click|preventDefault={forceRestartProxy}
>{$t('destination.force_restart_proxy')}</button
>
{/if}
{/if}
</div>
<div class="grid grid-cols-2 items-center px-10 ">
<label for="name">{$t('forms.name')}</label>