fix: port checkers

This commit is contained in:
Andras Bacsai 2022-09-08 11:41:38 +02:00
parent e36fda3ff1
commit 794329dcad
5 changed files with 135 additions and 76 deletions

View File

@ -748,7 +748,6 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
defaultDatabase, defaultDatabase,
version, version,
type, type,
settings: { appendOnly }
} = database; } = database;
const baseImage = getDatabaseImage(type, arch); const baseImage = getDatabaseImage(type, arch);
if (type === 'mysql') { if (type === 'mysql') {
@ -829,6 +828,7 @@ export function generateDatabaseConfiguration(database: any, arch: string): Data
} }
return configuration return configuration
} else if (type === 'redis') { } else if (type === 'redis') {
const { settings: { appendOnly } } = database;
const configuration: DatabaseConfiguration = { const configuration: DatabaseConfiguration = {
privatePort: 6379, privatePort: 6379,
command: undefined, 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) { if (exposePort < 1024 || exposePort > 65535) {
throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` } throw { status: 500, message: `Exposed Port needs to be between 1024 and 65535.` }
} }
if (configuredPort) { if (configuredPort) {
if (configuredPort !== exposePort) { 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()) { if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` } throw { status: 500, message: `Port ${exposePort} is already in use.` }
} }
} }
} else { } else {
const availablePort = await getFreeExposedPort(id, exposePort, dockerId, remoteIpAddress); const availablePort = await getFreeExposedPort(id, exposePort, engine, remoteEngine, remoteIpAddress);
if (availablePort.toString() !== exposePort.toString()) { if (availablePort.toString() !== exposePort.toString()) {
throw { status: 500, message: `Port ${exposePort} is already in use.` } 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 { default: checkPort } = await import('is-port-reachable');
const applicationUsed = await ( if (remoteEngine) {
await prisma.application.findMany({ const applicationUsed = await (
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId }, await prisma.application.findMany({
select: { exposePort: true } where: { exposePort: { not: null }, id: { not: id }, destinationDocker: { remoteIpAddress } },
}) select: { exposePort: true }
).map((a) => a.exposePort); })
const serviceUsed = await ( ).map((a) => a.exposePort);
await prisma.service.findMany({ const serviceUsed = await (
where: { exposePort: { not: null }, id: { not: id }, destinationDockerId: dockerId }, await prisma.service.findMany({
select: { exposePort: true } where: { exposePort: { not: null }, id: { not: id }, destinationDocker: { remoteIpAddress } },
}) select: { exposePort: true }
).map((a) => a.exposePort); })
const usedPorts = [...applicationUsed, ...serviceUsed]; ).map((a) => a.exposePort);
if (usedPorts.includes(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 return false
} }
const found = await checkPort(exposePort, { host: remoteIpAddress || 'localhost' });
if (!found) {
return exposePort
}
return false
} }
export function generateRangeArray(start, end) { export function generateRangeArray(start, end) {
return Array.from({ length: (end - start) }, (v, k) => k + start); 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 { default: isReachable } = await import('is-port-reachable');
const data = await prisma.setting.findFirst(); const data = await prisma.setting.findFirst();
const { minPort, maxPort } = data; const { minPort, maxPort } = data;
const dbUsed = await ( if (remoteEngine) {
await prisma.database.findMany({ const dbUsed = await (
where: { publicPort: { not: null }, id: { not: id }, destinationDockerId: dockerId }, await prisma.database.findMany({
select: { publicPort: true } where: { publicPort: { not: null }, id: { not: id }, destinationDocker: { remoteIpAddress } },
}) select: { publicPort: true }
).map((a) => a.publicPort); })
const wpFtpUsed = await ( ).map((a) => a.publicPort);
await prisma.wordpress.findMany({ const wpFtpUsed = await (
where: { ftpPublicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } }, await prisma.wordpress.findMany({
select: { ftpPublicPort: true } where: { ftpPublicPort: { not: null }, id: { not: id }, service: { destinationDocker: { remoteIpAddress } } },
}) select: { ftpPublicPort: true }
).map((a) => a.ftpPublicPort); })
const wpUsed = await ( ).map((a) => a.ftpPublicPort);
await prisma.wordpress.findMany({ const wpUsed = await (
where: { mysqlPublicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } }, await prisma.wordpress.findMany({
select: { mysqlPublicPort: true } where: { mysqlPublicPort: { not: null }, id: { not: id }, service: { destinationDocker: { remoteIpAddress } } },
}) select: { mysqlPublicPort: true }
).map((a) => a.mysqlPublicPort); })
const minioUsed = await ( ).map((a) => a.mysqlPublicPort);
await prisma.minio.findMany({ const minioUsed = await (
where: { publicPort: { not: null }, id: { not: id }, service: { destinationDockerId: dockerId } }, await prisma.minio.findMany({
select: { publicPort: true } where: { publicPort: { not: null }, id: { not: id }, service: { destinationDocker: { remoteIpAddress } } },
}) select: { publicPort: true }
).map((a) => a.publicPort); })
const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed]; ).map((a) => a.publicPort);
const range = generateRangeArray(minPort, maxPort) const usedPorts = [...dbUsed, ...wpFtpUsed, ...wpUsed, ...minioUsed];
const availablePorts = range.filter(port => !usedPorts.includes(port)) const range = generateRangeArray(minPort, maxPort)
for (const port of availablePorts) { const availablePorts = range.filter(port => !usedPorts.includes(port))
const found = await isReachable(port, { host: 'localhost' }) for (const port of availablePorts) {
if (!found) { const found = await isReachable(port, { host: remoteIpAddress })
return port 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( export async function startTraefikTCPProxy(

View File

@ -321,8 +321,8 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const port = getServiceMainPort('minio'); const port = getServiceMainPort('minio');
const { service: { destinationDocker: { id: dockerId } } } = await prisma.minio.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } }) const { service: { destinationDocker: { remoteEngine, engine, remoteIpAddress } } } = await prisma.minio.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } })
const publicPort = await getFreePublicPort(id, dockerId); const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress });
const consolePort = 9001; const consolePort = 9001;
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });

View File

@ -252,8 +252,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
exposePort = Number(exposePort); exposePort = Number(exposePort);
} }
const { destinationDocker: { id: dockerId, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } }) const { destinationDocker: { engine, remoteEngine, remoteIpAddress }, exposePort: configuredPort } = await prisma.application.findUnique({ where: { id }, include: { destinationDocker: true } })
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
if (denoOptions) denoOptions = denoOptions.trim(); if (denoOptions) denoOptions = denoOptions.trim();
const defaultConfiguration = await setDefaultConfiguration({ const defaultConfiguration = await setDefaultConfiguration({
buildPack, buildPack,
@ -534,14 +534,14 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
} }
if (exposePort) exposePort = Number(exposePort); 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 { isDNSCheckEnabled } = await prisma.setting.findFirst({});
const found = await isDomainConfigured({ id, fqdn, remoteIpAddress }); const found = await isDomainConfigured({ id, fqdn, remoteIpAddress });
if (found) { if (found) {
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` } 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) { if (isDNSCheckEnabled && !isDev && !forceSave) {
let hostname = request.hostname.split(':')[0]; let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress; if (remoteEngine) hostname = remoteIpAddress;

View File

@ -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 { 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 { day } from '../../../../lib/dayjs';
import { DeleteDatabaseSecret, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveVersion } from '../../../../types'; import type { OnlyId } from '../../../../types';
import { DeleteDatabase, SaveDatabaseType } from './types'; import type { DeleteDatabase, DeleteDatabaseSecret, GetDatabaseLogs, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveDatabaseType, SaveVersion } from './types';
export async function listDatabases(request: FastifyRequest) { export async function listDatabases(request: FastifyRequest) {
try { try {
@ -94,15 +94,14 @@ export async function getDatabase(request: FastifyRequest<OnlyId>) {
if (!database) { if (!database) {
throw { status: 404, message: 'Database not found.' } 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.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword); if (database.rootUserPassword) database.rootUserPassword = decrypt(database.rootUserPassword);
const configuration = generateDatabaseConfiguration(database, arch); const configuration = generateDatabaseConfiguration(database, settings.arch);
const settings = await listSettings();
return { return {
privatePort: configuration?.privatePort, privatePort: configuration?.privatePort,
database, database,
versions: await getDatabaseVersions(database.type, arch), versions: await getDatabaseVersions(database.type, settings.arch),
settings settings
}; };
} catch ({ status, message }) { } catch ({ status, message }) {
@ -426,10 +425,10 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
let publicPort = null let publicPort = null
const { destinationDocker: { id: dockerId } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } }) const { destinationDocker: { remoteEngine, engine, remoteIpAddress } } = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } })
if (isPublic) { if (isPublic) {
publicPort = await getFreePublicPort(id, dockerId); publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress });
} }
await prisma.database.update({ await prisma.database.update({
where: { id }, where: { id },

View File

@ -233,7 +233,7 @@ export async function checkService(request: FastifyRequest<CheckService>) {
if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase()); if (otherFqdns && otherFqdns.length > 0) otherFqdns = otherFqdns.map((f) => f.toLowerCase());
if (exposePort) exposePort = Number(exposePort); 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({}); const { isDNSCheckEnabled } = await prisma.setting.findFirst({});
let found = await isDomainConfigured({ id, fqdn, remoteIpAddress }); let found = await isDomainConfigured({ id, fqdn, remoteIpAddress });
@ -248,7 +248,7 @@ export async function checkService(request: FastifyRequest<CheckService>) {
} }
} }
} }
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, dockerId, remoteIpAddress }) if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
if (isDNSCheckEnabled && !isDev && !forceSave) { if (isDNSCheckEnabled && !isDev && !forceSave) {
let hostname = request.hostname.split(':')[0]; let hostname = request.hostname.split(':')[0];
if (remoteEngine) hostname = remoteIpAddress; if (remoteEngine) hostname = remoteIpAddress;
@ -485,9 +485,9 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
const { id } = request.params const { id } = request.params
const { ftpEnabled } = request.body; const { ftpEnabled } = request.body;
const { service: { destinationDocker: { id: dockerId } } } = await prisma.wordpress.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } }) const { service: { destinationDocker: { engine, remoteEngine, remoteIpAddress } } } = await prisma.wordpress.findUnique({ where: { serviceId: id }, include: { service: { include: { destinationDocker: true } } } })
const publicPort = await getFreePublicPort(id, dockerId); const publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress });
let ftpUser = cuid(); let ftpUser = cuid();
let ftpPassword = generatePassword({}); let ftpPassword = generatePassword({});