From a02bcc3d025116ad5af908bc7ab13814b20d12d4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Fri, 22 Jul 2022 12:01:07 +0000 Subject: [PATCH] fix: states and exposed ports --- apps/api/package.json | 1 + apps/api/src/lib/common.ts | 42 +++++++++++-------- .../routes/api/v1/applications/handlers.ts | 17 ++++---- .../src/routes/api/v1/databases/handlers.ts | 33 +++++++-------- .../src/routes/api/v1/services/handlers.ts | 33 +++++++-------- .../src/routes/applications/[id]/index.svelte | 4 +- apps/ui/src/routes/applications/index.svelte | 7 +++- .../[id]/_Databases/_Databases.svelte | 2 +- apps/ui/src/routes/databases/index.svelte | 3 ++ .../services/[id]/_Services/_Services.svelte | 22 ++++++---- .../services/[id]/_Services/_Wordpress.svelte | 13 ++++-- apps/ui/src/routes/services/index.svelte | 3 ++ pnpm-lock.yaml | 7 ++++ 13 files changed, 112 insertions(+), 75 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index ef63a504b..491012b04 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -38,6 +38,7 @@ "get-port": "6.1.2", "got": "12.1.0", "is-ip": "4.0.0", + "is-port-reachable": "4.0.0", "js-yaml": "4.1.0", "jsonwebtoken": "8.5.1", "node-forge": "1.3.1", diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index e489fc52b..52ab8b651 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -31,7 +31,7 @@ const customConfig: Config = { export const defaultProxyImage = `coolify-haproxy-alpine:latest`; export const defaultProxyImageTcp = `coolify-haproxy-tcp-alpine:latest`; export const defaultProxyImageHttp = `coolify-haproxy-http-alpine:latest`; -export const defaultTraefikImage = `traefik:v2.6`; +export const defaultTraefikImage = `traefik:v2.8`; export function getAPIUrl() { if (process.env.GITPOD_WORKSPACE_URL) { const { href } = new URL(process.env.GITPOD_WORKSPACE_URL) @@ -994,49 +994,58 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) { } } } -export async function getExposedFreePort(id, exposePort) { +export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) { const { default: getPort } = await import('get-port'); const applicationUsed = await ( await prisma.application.findMany({ - where: { exposePort: { not: null }, id: { not: id } }, + where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId }, select: { exposePort: true } }) ).map((a) => a.exposePort); const serviceUsed = await ( await prisma.service.findMany({ - where: { exposePort: { not: null }, id: { not: id } }, + where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId }, select: { exposePort: true } }) ).map((a) => a.exposePort); const usedPorts = [...applicationUsed, ...serviceUsed]; - return await getPort({ port: exposePort, exclude: usedPorts }); + if (remoteIpAddress) { + const { default: checkPort } = await import('is-port-reachable'); + const found = await checkPort(exposePort, { host: remoteIpAddress }); + if (!found) { + return exposePort + } + return false + } + return await getPort({ port: Number(exposePort), exclude: usedPorts }); + } -export async function getFreePublicPort() { +export async function getFreePublicPort(id, dockerId) { const { default: getPort, portNumbers } = await import('get-port'); const data = await prisma.setting.findFirst(); const { minPort, maxPort } = data; const dbUsed = await ( await prisma.database.findMany({ - where: { publicPort: { not: null } }, + where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId }, select: { publicPort: true } }) ).map((a) => a.publicPort); const wpFtpUsed = await ( await prisma.wordpress.findMany({ - where: { ftpPublicPort: { not: null } }, + where: { ftpPublicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } }, select: { ftpPublicPort: true } }) ).map((a) => a.ftpPublicPort); const wpUsed = await ( await prisma.wordpress.findMany({ - where: { mysqlPublicPort: { not: null } }, + where: { mysqlPublicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } }, select: { mysqlPublicPort: true } }) ).map((a) => a.mysqlPublicPort); const minioUsed = await ( await prisma.minio.findMany({ - where: { publicPort: { not: null } }, + where: { publicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } }, select: { publicPort: true } }) ).map((a) => a.publicPort); @@ -1044,7 +1053,6 @@ export async function getFreePublicPort() { return await getPort({ port: portNumbers(minPort, maxPort), exclude: usedPorts }); } - export async function startTraefikTCPProxy( destinationDocker: any, id: string, @@ -1067,19 +1075,19 @@ export async function startTraefikTCPProxy( const ip = JSON.parse(Config)[0].Gateway; const tcpProxy = { - version: '3.5', + version: '3.8', services: { [`${id}-${publicPort}`]: { container_name: container, - image: 'traefik:v2.6', + image: 'traefik:v2.8', command: [ - `--entrypoints.tcp.address =: ${publicPort}`, - `--entryPoints.tcp.forwardedHeaders.insecure = true`, - `--providers.http.endpoint = ${otherTraefikEndpoint} ? id = ${id} & privatePort=${privatePort} & publicPort=${publicPort} & type=tcp & address=${dependentId}`, + `--entrypoints.tcp.address=:${publicPort}`, + `--entryPoints.tcp.forwardedHeaders.insecure=true`, + `--providers.http.endpoint=${otherTraefikEndpoint} ? id=${id}&privatePort=${privatePort}&publicPort=${publicPort}&type=tcp&address=${dependentId}`, '--providers.http.pollTimeout=2s', '--log.level=error' ], - ports: [`${publicPort}: ${publicPort}`], + ports: [`${publicPort}:${publicPort}`], extra_hosts: ['host.docker.internal:host-gateway', `host.docker.internal: ${ip}`], volumes: ['/var/run/docker.sock:/var/run/docker.sock'], networks: ['coolify-infra', network] diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index bfa0067a9..b48101687 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -5,7 +5,7 @@ import axios from 'axios'; import { FastifyReply } from 'fastify'; import { day } from '../../../../lib/dayjs'; import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; -import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getExposedFreePort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; +import { checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, executeDockerCmd, generateSshKeyPair, getContainerUsage, getDomain, getFreeExposedPort, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { checkContainer, dockerInstance, isContainerExited, removeContainer } from '../../../../lib/docker'; import { scheduler } from '../../../../lib/scheduler'; @@ -18,7 +18,7 @@ export async function listApplications(request: FastifyRequest) { const { teamId } = request.user const applications = await prisma.application.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, - include: { teams: true } + include: { teams: true, destinationDocker: true } }); const settings = await prisma.setting.findFirst() return { @@ -249,7 +249,6 @@ export async function saveApplication(request: FastifyRequest, dockerFileLocation, denoMainFile }); - console.log({ baseImage }) await prisma.application.update({ where: { id }, data: { @@ -353,7 +352,7 @@ export async function checkDNS(request: FastifyRequest) { const { id } = request.params let { exposePort, fqdn, forceSave, dualCerts } = request.body - + if (fqdn) fqdn = fqdn.toLowerCase(); if (exposePort) exposePort = Number(exposePort); @@ -363,13 +362,15 @@ export async function checkDNS(request: FastifyRequest) { throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } } if (exposePort) { - if (exposePort < 1024 || exposePort > 65535) { throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } } - const availablePort = await getExposedFreePort(id, exposePort); - if (availablePort.toString() !== exposePort.toString()) { - throw { status: 500, message: `Port ${exposePort} is already in use.` } + const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }) + if (configuredPort !== exposePort) { + const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress); + if (availablePort.toString() !== exposePort.toString()) { + throw { status: 500, message: `Port ${exposePort} is already in use.` } + } } } if (isDNSCheckEnabled && !isDev && !forceSave) { diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index 7ce0eb557..ca5fc67c0 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -13,15 +13,10 @@ import { SaveDatabaseType } from './types'; export async function listDatabases(request: FastifyRequest) { try { const teamId = request.user.teamId; - let databases = [] - if (teamId === '0') { - databases = await prisma.database.findMany({ include: { teams: true } }); - } else { - databases = await prisma.database.findMany({ - where: { teams: { some: { id: teamId } } }, - include: { teams: true } - }); - } + const databases = await prisma.database.findMany({ + where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, + include: { teams: true, destinationDocker: true } + }); return { databases } @@ -335,13 +330,13 @@ export async function getDatabaseLogs(request: FastifyRequest) try { // const found = await checkContainer({ dockerId, container: id }) // if (found) { - const { default: ansi } = await import('strip-ansi') - const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` }) - const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); - const logs = stripLogsStderr.concat(stripLogsStdout) - const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) - return { logs: sortedLogs } + const { default: ansi } = await import('strip-ansi') + const { stdout, stderr } = await executeDockerCmd({ dockerId, command: `docker logs --since ${since} --tail 5000 --timestamps ${id}` }) + const stripLogsStdout = stdout.toString().split('\n').map((l) => ansi(l)).filter((a) => a); + const stripLogsStderr = stderr.toString().split('\n').map((l) => ansi(l)).filter((a) => a); + const logs = stripLogsStderr.concat(stripLogsStdout) + const sortedLogs = logs.sort((a, b) => (day(a.split(' ')[0]).isAfter(day(b.split(' ')[0])) ? 1 : -1)) + return { logs: sortedLogs } // } } catch (error) { const { statusCode } = error; @@ -431,8 +426,10 @@ export async function saveDatabaseSettings(request: FastifyRequest) { if (exposePort < 1024 || exposePort > 65535) { throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } } - - const availablePort = await getExposedFreePort(id, exposePort); - if (availablePort.toString() !== exposePort.toString()) { - throw { status: 500, message: `Port ${exposePort} is already in use.` } + const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }) + if (configuredPort !== exposePort) { + const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress); + if (availablePort.toString() !== exposePort.toString()) { + throw { status: 500, message: `Port ${exposePort} is already in use.` } + } } } return {} @@ -980,7 +977,9 @@ async function startMinioService(request: FastifyRequest) { const network = destinationDockerId && destinationDocker.network; const port = getServiceMainPort('minio'); - const publicPort = await getFreePublicPort(); + const { service: { destinationDocker: { id: dockerId } } } = await prisma.minio.findUnique({ where: { id }, include: { service: { include: { destinationDocker: true } } } }) + + const publicPort = await getFreePublicPort(id, dockerId); const consolePort = 9001; const { workdir } = await createDirectories({ repository: type, buildId: id }); @@ -2675,8 +2674,8 @@ export async function activatePlausibleUsers(request: FastifyRequest, re export async function activateWordpressFtp(request: FastifyRequest, reply: FastifyReply) { const { id } = request.params const { ftpEnabled } = request.body; - - const publicPort = await getFreePublicPort(); + const { service: { destinationDocker: { id: dockerId } } } = await prisma.wordpress.findUnique({ where: { id }, include: { service: { include: { destinationDocker: true } } } }) + const publicPort = await getFreePublicPort(id, dockerId); let ftpUser = cuid(); let ftpPassword = generatePassword(); diff --git a/apps/ui/src/routes/applications/[id]/index.svelte b/apps/ui/src/routes/applications/[id]/index.svelte index e525278e5..569e77de8 100644 --- a/apps/ui/src/routes/applications/[id]/index.svelte +++ b/apps/ui/src/routes/applications/[id]/index.svelte @@ -41,7 +41,7 @@ import Setting from './_Setting.svelte'; const { id } = $page.params; - $: isDisabled = !$appSession.isAdmin || $status.application.isRunning; + $: isDisabled = !$appSession.isAdmin || $status.application.isRunning || $status.application.initialLoading; let domainEl: HTMLInputElement; @@ -536,7 +536,7 @@
{getDomain(application.fqdn) || ''}
{/if} + {#if application.destinationDocker.name} +
{application.destinationDocker.name}
+ {/if} {#if !application.gitSourceId || !application.repository || !application.branch}
Git Source Missing
- {:else if !application.destinationDockerId} + {:else if !application.destinationDockerId}
Destination Missing
- {:else if !application.fqdn} + {:else if !application.fqdn}
URL Missing
diff --git a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte index a99a4b79e..94c8ed75e 100644 --- a/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte +++ b/apps/ui/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -150,7 +150,7 @@ > diff --git a/apps/ui/src/routes/databases/index.svelte b/apps/ui/src/routes/databases/index.svelte index 233fde31c..4cb81188c 100644 --- a/apps/ui/src/routes/databases/index.svelte +++ b/apps/ui/src/routes/databases/index.svelte @@ -100,6 +100,9 @@ {#if $appSession.teamId === '0' && otherDatabases.length > 0}
{database.teams[0].name}
{/if} + {#if database.destinationDocker.name} +
{database.destinationDocker.name}
+ {/if} {#if !database.type}
{$t('application.configuration.configuration_missing')} diff --git a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte index d856a9278..1e8bff373 100644 --- a/apps/ui/src/routes/services/[id]/_Services/_Services.svelte +++ b/apps/ui/src/routes/services/[id]/_Services/_Services.svelte @@ -31,6 +31,8 @@ const { id } = $page.params; + $: isDisabled = !$appSession.isAdmin || $status.service.initialLoading; + let loading = false; let loadingVerification = false; let dualCerts = service.dualCerts; @@ -45,7 +47,7 @@ exposePort: service.exposePort }); await post(`/services/${id}`, { ...service }); - setLocation(service) + setLocation(service); $disabledButton = false; toast.push('Configuration saved.'); } catch (error) { @@ -145,7 +147,7 @@
@@ -184,7 +186,9 @@ Exposed Port {$t('forms.extra_config')}