From 794329dcadcf69a4bce3d09d84cc6ecb8f774ddf Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 8 Sep 2022 11:41:38 +0200 Subject: [PATCH] fix: port checkers --- apps/api/src/lib/common.ts | 176 ++++++++++++------ apps/api/src/lib/services/handlers.ts | 4 +- .../routes/api/v1/applications/handlers.ts | 8 +- .../src/routes/api/v1/databases/handlers.ts | 15 +- .../src/routes/api/v1/services/handlers.ts | 8 +- 5 files changed, 135 insertions(+), 76 deletions(-) diff --git a/apps/api/src/lib/common.ts b/apps/api/src/lib/common.ts index 1c598dcbf..4e843c900 100644 --- a/apps/api/src/lib/common.ts +++ b/apps/api/src/lib/common.ts @@ -748,7 +748,6 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data defaultDatabase, version, type, - settings: { appendOnly } } = database; const baseImage = getDatabaseImage(type, arch); if (type === 'mysql') { @@ -829,6 +828,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data } return configuration } else if (type === 'redis') { + const { settings: { appendOnly } } = database; const configuration: DatabaseConfiguration = { privatePort: 6379, command: undefined, @@ -1112,90 +1112,150 @@ export async function updatePasswordInDb(database, user, newPassword, isRoot) { } } } -export async function checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }: { id: string, configuredPort?: number, exposePort: number, dockerId: string, remoteIpAddress?: string }) { +export async function checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress }: { id: string, configuredPort?: number, exposePort: number, engine: string, remoteEngine: boolean, remoteIpAddress?: string }) { if (exposePort < 1024 || exposePort > 65535) { throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } } if (configuredPort) { if (configuredPort !== exposePort) { - const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress); + const availablePort = await getFreeExposedPort(id, exposePort, engine, remoteEngine, remoteIpAddress); if (availablePort.toString() !== exposePort.toString()) { throw { status: 500, message: `Port ${exposePort} is already in use.` } } } } else { - const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress); + const availablePort = await getFreeExposedPort(id, exposePort, engine, remoteEngine, remoteIpAddress); if (availablePort.toString() !== exposePort.toString()) { throw { status: 500, message: `Port ${exposePort} is already in use.` } } } } -export async function getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress) { +export async function getFreeExposedPort(id, exposePort, engine, remoteEngine, remoteIpAddress) { const { default: checkPort } = await import('is-port-reachable'); - const applicationUsed = await ( - await prisma.application.findMany({ - 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 }, destinationDockerId: dockerId }, - select: { exposePort: true } - }) - ).map((a) => a.exposePort); - const usedPorts = [...applicationUsed, ...serviceUsed]; - if (usedPorts.includes(exposePort)) { + if (remoteEngine) { + const applicationUsed = await ( + await prisma.application.findMany({ + where: { exposePort: { not: null }, id: { not: id }, destinationDocker: { remoteIpAddress } }, + select: { exposePort: true } + }) + ).map((a) => a.exposePort); + const serviceUsed = await ( + await prisma.service.findMany({ + where: { exposePort: { not: null }, id: { not: id }, destinationDocker: { remoteIpAddress } }, + select: { exposePort: true } + }) + ).map((a) => a.exposePort); + const usedPorts = [...applicationUsed, ...serviceUsed]; + if (usedPorts.includes(exposePort)) { + return false + } + const found = await checkPort(exposePort, { host: remoteIpAddress }); + if (!found) { + return exposePort + } + return false + } else { + const applicationUsed = await ( + await prisma.application.findMany({ + where: { exposePort: { not: null }, id: { not: id }, destinationDocker: { engine } }, + select: { exposePort: true } + }) + ).map((a) => a.exposePort); + const serviceUsed = await ( + await prisma.service.findMany({ + where: { exposePort: { not: null }, id: { not: id }, destinationDocker: { engine } }, + select: { exposePort: true } + }) + ).map((a) => a.exposePort); + const usedPorts = [...applicationUsed, ...serviceUsed]; + if (usedPorts.includes(exposePort)) { + return false + } + const found = await checkPort(exposePort, { host: 'localhost' }); + if (!found) { + return exposePort + } return false } - const found = await checkPort(exposePort, { host: remoteIpAddress || 'localhost' }); - if (!found) { - return exposePort - } - return false - } export function generateRangeArray(start, end) { return Array.from({ length: (end - start) }, (v, k) => k + start); } -export async function getFreePublicPort(id, dockerId) { +export async function getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }) { const { default: isReachable } = await import('is-port-reachable'); const data = await prisma.setting.findFirst(); const { minPort, maxPort } = data; - const dbUsed = await ( - await prisma.database.findMany({ - 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 }, id: { not: id }, service: { destinationDockerId: dockerId } }, - select: { ftpPublicPort: true } - }) - ).map((a) => a.ftpPublicPort); - const wpUsed = await ( - await prisma.wordpress.findMany({ - 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 }, id: { not: id }, service: { destinationDockerId: dockerId } }, - select: { publicPort: true } - }) - ).map((a) => a.publicPort); - const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed]; - const range = generateRangeArray(minPort, maxPort) - const availablePorts = range.filter(port => !usedPorts.includes(port)) - for (const port of availablePorts) { - const found = await isReachable(port, { host: 'localhost' }) - if (!found) { - return port + if (remoteEngine) { + const dbUsed = await ( + await prisma.database.findMany({ + where: { publicPort: { not: null }, id: { not: id }, destinationDocker: { remoteIpAddress } }, + select: { publicPort: true } + }) + ).map((a) => a.publicPort); + const wpFtpUsed = await ( + await prisma.wordpress.findMany({ + where: { ftpPublicPort: { not: null }, id: { not: id }, service: { destinationDocker: { remoteIpAddress } } }, + select: { ftpPublicPort: true } + }) + ).map((a) => a.ftpPublicPort); + const wpUsed = await ( + await prisma.wordpress.findMany({ + where: { mysqlPublicPort: { not: null }, id: { not: id }, service: { destinationDocker: { remoteIpAddress } } }, + select: { mysqlPublicPort: true } + }) + ).map((a) => a.mysqlPublicPort); + const minioUsed = await ( + await prisma.minio.findMany({ + where: { publicPort: { not: null }, id: { not: id }, service: { destinationDocker: { remoteIpAddress } } }, + select: { publicPort: true } + }) + ).map((a) => a.publicPort); + const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed]; + const range = generateRangeArray(minPort, maxPort) + const availablePorts = range.filter(port => !usedPorts.includes(port)) + for (const port of availablePorts) { + const found = await isReachable(port, { host: remoteIpAddress }) + if (!found) { + return port + } } + return false + } else { + const dbUsed = await ( + await prisma.database.findMany({ + where: { publicPort: { not: null }, id: { not: id }, destinationDocker: { engine } }, + select: { publicPort: true } + }) + ).map((a) => a.publicPort); + const wpFtpUsed = await ( + await prisma.wordpress.findMany({ + where: { ftpPublicPort: { not: null }, id: { not: id }, service: { destinationDocker: { engine } } }, + select: { ftpPublicPort: true } + }) + ).map((a) => a.ftpPublicPort); + const wpUsed = await ( + await prisma.wordpress.findMany({ + where: { mysqlPublicPort: { not: null }, id: { not: id }, service: { destinationDocker: { engine } } }, + select: { mysqlPublicPort: true } + }) + ).map((a) => a.mysqlPublicPort); + const minioUsed = await ( + await prisma.minio.findMany({ + where: { publicPort: { not: null }, id: { not: id }, service: { destinationDocker: { engine } } }, + select: { publicPort: true } + }) + ).map((a) => a.publicPort); + const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed]; + const range = generateRangeArray(minPort, maxPort) + const availablePorts = range.filter(port => !usedPorts.includes(port)) + for (const port of availablePorts) { + const found = await isReachable(port, { host: 'localhost' }) + if (!found) { + return port + } + } + return false } - return false } export async function startTraefikTCPProxy( diff --git a/apps/api/src/lib/services/handlers.ts b/apps/api/src/lib/services/handlers.ts index f56657ce7..cd4d58d2b 100644 --- a/apps/api/src/lib/services/handlers.ts +++ b/apps/api/src/lib/services/handlers.ts @@ -321,8 +321,8 @@ async function startMinioService(request: FastifyRequest) { const network = destinationDockerId && destinationDocker.network; const port = getServiceMainPort('minio'); - const { service: { destinationDocker: { id: dockerId } } } = await prisma.minio.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } }) - const publicPort = await getFreePublicPort(id, dockerId); + const { service: { destinationDocker: { remoteEngine, engine, remoteIpAddress } } } = await prisma.minio.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } }) + const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress }); const consolePort = 9001; const { workdir } = await createDirectories({ repository: type, buildId: id }); diff --git a/apps/api/src/routes/api/v1/applications/handlers.ts b/apps/api/src/routes/api/v1/applications/handlers.ts index 1d00e94d1..8a390a3f5 100644 --- a/apps/api/src/routes/api/v1/applications/handlers.ts +++ b/apps/api/src/routes/api/v1/applications/handlers.ts @@ -252,8 +252,8 @@ export async function saveApplication(request: FastifyRequest, exposePort = Number(exposePort); } - const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }) - if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) + const { destinationDocker: { engine, remoteEngine, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }) + if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress }) if (denoOptions) denoOptions = denoOptions.trim(); const defaultConfiguration = await setDefaultConfiguration({ buildPack, @@ -534,14 +534,14 @@ export async function checkDNS(request: FastifyRequest) { } if (exposePort) exposePort = Number(exposePort); - const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }) + const { destinationDocker: { engine, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }) const { isDNSCheckEnabled } = await prisma.setting.findFirst({}); const found = await isDomainConfigured({ id, fqdn, remoteIpAddress }); if (found) { throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } } - if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) + if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress }) if (isDNSCheckEnabled && !isDev && !forceSave) { let hostname = request.hostname.split(':')[0]; if (remoteEngine) hostname = remoteIpAddress; diff --git a/apps/api/src/routes/api/v1/databases/handlers.ts b/apps/api/src/routes/api/v1/databases/handlers.ts index 5e78dbfe9..16d43205c 100644 --- a/apps/api/src/routes/api/v1/databases/handlers.ts +++ b/apps/api/src/routes/api/v1/databases/handlers.ts @@ -6,8 +6,8 @@ import fs from 'fs/promises'; import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import { day } from '../../../../lib/dayjs'; -import { DeleteDatabaseSecret, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveVersion } from '../../../../types'; -import { DeleteDatabase, SaveDatabaseType } from './types'; +import type { OnlyId } from '../../../../types'; +import type { DeleteDatabase, DeleteDatabaseSecret, GetDatabaseLogs, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveDatabaseType, SaveVersion } from './types'; export async function listDatabases(request: FastifyRequest) { try { @@ -94,15 +94,14 @@ export async function getDatabase(request: FastifyRequest) { if (!database) { throw { status: 404, message: 'Database not found.' } } - const { arch } = await listSettings(); + const settings = await listSettings(); if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword); if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); - const configuration = generateDatabaseConfiguration(database, arch); - const settings = await listSettings(); + const configuration = generateDatabaseConfiguration(database, settings.arch); return { privatePort: configuration?.privatePort, database, - versions: await getDatabaseVersions(database.type, arch), + versions: await getDatabaseVersions(database.type, settings.arch), settings }; } catch ({ status, message }) { @@ -426,10 +425,10 @@ export async function saveDatabaseSettings(request: FastifyRequest) { if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase()); if (exposePort) exposePort = Number(exposePort); - const { destinationDocker: { id: dockerId, remoteIpAddress, remoteEngine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }) + const { destinationDocker: { remoteIpAddress, remoteEngine, engine }, exposePort: configuredPort } = await prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }) const { isDNSCheckEnabled } = await prisma.setting.findFirst({}); let found = await isDomainConfigured({ id, fqdn, remoteIpAddress }); @@ -248,7 +248,7 @@ export async function checkService(request: FastifyRequest) { } } } - if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) + if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress }) if (isDNSCheckEnabled && !isDev && !forceSave) { let hostname = request.hostname.split(':')[0]; if (remoteEngine) hostname = remoteIpAddress; @@ -485,9 +485,9 @@ export async function activateWordpressFtp(request: FastifyRequest