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,
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(

View File

@ -321,8 +321,8 @@ async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
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 });

View File

@ -252,8 +252,8 @@ export async function saveApplication(request: FastifyRequest<SaveApplication>,
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<CheckDNS>) {
}
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;

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 { 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<OnlyId>) {
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<SaveDatabaseS
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) {
publicPort = await getFreePublicPort(id, dockerId);
publicPort = await getFreePublicPort({ id, remoteEngine, engine, remoteIpAddress });
}
await prisma.database.update({
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 (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<CheckService>) {
}
}
}
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<ActivateWordp
const { id } = request.params
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 ftpPassword = generatePassword({});