From 67c8e9c31d4852a198979a8b5d97833e68c5505a Mon Sep 17 00:00:00 2001 From: Christian Risi <75698846+CnF-Gris@users.noreply.github.com> Date: Fri, 4 Jul 2025 03:05:43 +0000 Subject: [PATCH] V 0.1.2 Ended writing backend utils --- src/hooks.server.ts | 22 +++ .../{Endpoint.ts => Endpoint-Manager.ts} | 54 ++++++- .../server/broker-utils/FileSystem/Manual.ts | 48 ++++++ .../FileSystem/SSLTerminations.ts | 150 ++++++++++++++---- .../FileSystem/endpoints/endpoints.ts | 2 +- .../FileSystem/endpoints/ssltermination-fs.ts | 27 ++-- .../classes/endpoints/endpoint-manager.ts | 11 +- .../classes/endpoints/manual-endpoint.ts | 2 +- .../endpoints/ssl-termination-endpoint.ts | 44 ++--- src/lib/server/utils/filesystem-utils.ts | 15 ++ src/lib/server/utils/ports-utils.ts | 7 +- 11 files changed, 283 insertions(+), 99 deletions(-) rename src/lib/server/broker-utils/FileSystem/{Endpoint.ts => Endpoint-Manager.ts} (87%) create mode 100644 src/lib/server/broker-utils/FileSystem/Manual.ts diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 1bbe356..5af9e3b 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -8,6 +8,11 @@ import { SessionDBBroker } from '$lib/server/broker-utils/SQLite/Sessions'; import { AppData } from '$lib/server/classes/appdata'; import { logger } from '$lib/server/utils/logger'; import { JoseApp } from '$lib/server/utils/jtw-utils'; +import { EndpointManagerApp } from '$lib/server/classes/endpoints/endpoint-manager'; +import { SSLTerminationEndpointApp } from '$lib/server/classes/endpoints/ssl-termination-endpoint'; +import { SSLTerminationFSBroker } from '$lib/server/broker-utils/FileSystem/SSLTerminations'; +import { ManualEndpointApp } from '$lib/server/classes/endpoints/manual-endpoint'; +import { ManualFSBroker } from '$lib/server/broker-utils/FileSystem/Manual'; export const init: ServerInit = async () => { @@ -34,6 +39,23 @@ export const init: ServerInit = async () => { await JoseApp.init() } + if(!EndpointManagerApp.ready) { + // This is async + await EndpointManagerApp.init() + } + + if (!SSLTerminationEndpointApp.ready) { + SSLTerminationEndpointApp.init( + new SSLTerminationFSBroker() + ) + } + + if (!ManualEndpointApp.ready) { + ManualEndpointApp.init( + new ManualFSBroker() + ) + } + logger.debug("Init run successfully", "App Init") diff --git a/src/lib/server/broker-utils/FileSystem/Endpoint.ts b/src/lib/server/broker-utils/FileSystem/Endpoint-Manager.ts similarity index 87% rename from src/lib/server/broker-utils/FileSystem/Endpoint.ts rename to src/lib/server/broker-utils/FileSystem/Endpoint-Manager.ts index 85552b8..4fa936e 100644 --- a/src/lib/server/broker-utils/FileSystem/Endpoint.ts +++ b/src/lib/server/broker-utils/FileSystem/Endpoint-Manager.ts @@ -1,6 +1,6 @@ import type { FSHeader, IEndpointFS } from "$lib/server/broker-utils/FileSystem/endpoints/endpoints" import { NGINX_ACTIVE, NGINX_BASE, NGINX_INACTIVE, NGINX_TRACKED } from "$lib/server/utils/constants" -import { doesFileExist, isDir, loadFile } from "$lib/server/utils/filesystem-utils" +import { doesFileExist, isDir, listFiles, loadFile } from "$lib/server/utils/filesystem-utils" import { watch, type FSWatcher } from "node:fs" import { parseConf } from "./utils" import { logger } from "$lib/server/utils/logger" @@ -29,10 +29,44 @@ export class EndpointBrokerManagerFS { // UGLY: be specific throw new Error("Broker already initialized") } - // TODO: Read all files + + const configurations = (await listFiles(NGINX_TRACKED, true)).filter( async (path) => { + if (await isDir(path)) { + return false + } - // TODO: QUICK parse them - parseConf() + if (!path.endsWith(".conf")) { + return false + } + + return true + }) + + for (const confPath of configurations) { + const file = await loadFile(confPath) + await file.lock() + + const conf = await file.text() + const fileStats = await file.getStats() + + const fsHeader: FSHeader = { + name: confPath.split("/").pop()!, + stats: fileStats, + path: confPath, + hash: await file.hash() + } + + await file.release() + const endpoint = await parseConf(fsHeader, conf) + + const relativePath = confPath.replace(NGINX_TRACKED, "") + EndpointBrokerManagerFS.endpoints.set( + relativePath, + endpoint + ) + + } + // TODO: Initialize a file watcher EndpointBrokerManagerFS.watcher = EndpointBrokerManagerFS.watchNginxDirectory() @@ -72,14 +106,24 @@ export class EndpointBrokerManagerFS { const file = await loadFile(REAL_PATH) await file.delete() + const endpoint = EndpointBrokerManagerFS.endpoints.get( + path + ) EndpointBrokerManagerFS.endpoints.delete( path ) + return endpoint + } public static async changeEndpoint(path: string, newEndpoint: IEndpointFS) { + if (newEndpoint.type === EndpointType.MANUAL) { + // UGLY: be specific + throw new Error("Change of Manual endpoint is not supported yet") + } + validatePath(path) const REAL_PATH = `${NGINX_TRACKED}/${path}` @@ -305,7 +349,7 @@ export class EndpointBrokerManagerFS { const fsHeader: FSHeader = { name: filename!.split("/").pop()!, stats: stats, - path: RELATIVE_PATH, + path: FULL_PATH, hash: hash } diff --git a/src/lib/server/broker-utils/FileSystem/Manual.ts b/src/lib/server/broker-utils/FileSystem/Manual.ts new file mode 100644 index 0000000..d19295c --- /dev/null +++ b/src/lib/server/broker-utils/FileSystem/Manual.ts @@ -0,0 +1,48 @@ +import type { IManualBroker, Manual } from "$lib/server/classes/endpoints/manual-endpoint"; +import { EndpointType } from "$lib/server/enums/endpoints"; +import { EndpointBrokerManagerFS } from "./Endpoint-Manager"; +import type { ManualFS } from "./endpoints/manual-fs"; + +export class ManualFSBroker implements IManualBroker { + public async init(): Promise { + // Do nothing? + } + public async getManualByPath(path: string): Promise { + const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path) + + if (!endpoint) { + return null + } + + if (endpoint.type !== EndpointType.MANUAL) { + // UGLY: be specific + throw new Error("This is not a Manual Endpoint") + } + + return endpoint.toIEndpoint() as Manual + } + + public async getAllManuals(): Promise { + const endpoints = (await EndpointBrokerManagerFS.getAll()).filter(async (endpoint) => { + if (endpoint.type !== EndpointType.MANUAL) { + return false + } + + return true + }) as ManualFS[] + + return endpoints.map( (endpoint) => { + return endpoint.toIEndpoint() as Manual + }) + } + + public async activateEndpointByPath(path: string): Promise { + return await EndpointBrokerManagerFS.activateEndpoint(path) + } + + public async deactivateEndpointByPath(path: string): Promise { + return await EndpointBrokerManagerFS.deactivateEndpoint(path) + } + + +} \ No newline at end of file diff --git a/src/lib/server/broker-utils/FileSystem/SSLTerminations.ts b/src/lib/server/broker-utils/FileSystem/SSLTerminations.ts index 89c2b49..ee3330a 100644 --- a/src/lib/server/broker-utils/FileSystem/SSLTerminations.ts +++ b/src/lib/server/broker-utils/FileSystem/SSLTerminations.ts @@ -1,57 +1,139 @@ -import { type ISSLTerminationBroker, type SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint"; -import type { NginxProtocol } from "$lib/server/enums/protocols"; +import type { ISSLTerminationBroker, SSLTermination, SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint" +import { EndpointType } from "$lib/server/enums/endpoints" +import type { NginxProtocol } from "$lib/server/enums/protocols" +import { isPortAvailable, validatePort } from "$lib/server/utils/ports-utils" +import { validatePath } from "$lib/shared/utils/path-utils" +import { EndpointBrokerManagerFS } from "./Endpoint-Manager" +import { SSLTerminationFS } from "./endpoints/ssltermination-fs" -export class SSLTerminationBroker implements ISSLTerminationBroker { - - private static initialized = false +export class SSLTerminationFSBroker implements ISSLTerminationBroker { - async init(): Promise { - if (SSLTerminationBroker.initialized) { - // UGLY: more specific - throw new Error("SSLTerminationBroker was already initialized") - } - + public async init(): Promise { + // Do nothing? } - async createSSLTerminationSimple( - name: string, - servicePort: number, - serviceEndpoint: string, - certificateURI: string, - privateKeyURI: string - ): Promise { - throw new Error("Method not implemented."); - } - - - async createSSLTerminationComplete( + public async createSSLTermination( name: string, sslPort: number, clearPort: number, servicePort: number, serviceEndpoint: string, + protocol: NginxProtocol, certificateURI: string, privateKeyURI: string ): Promise { - throw new Error("Method not implemented."); - } - - async getSSLTerminationByPath(name: string): Promise { - throw new Error("Method not implemented."); + + if ( + !isPortAvailable(sslPort) || + !isPortAvailable(clearPort) + ) { + // UGLY be specific + throw new Error("some ports were already in use") + } + + validatePort(servicePort) + + const newEndpoint = new SSLTerminationFS( + name, + null, + sslPort, + clearPort, + servicePort, + serviceEndpoint, + protocol, + certificateURI, + privateKeyURI + ) + + await EndpointBrokerManagerFS.createEndpoint(newEndpoint) + return (await this.getSSLTerminationFSByPath(newEndpoint.path))?.toIEndpoint() as SSLTermination } - async modifySSLTerminationByPath(name: string, changes: SSLTerminationChanges): Promise { - throw new Error("Method not implemented."); + + public async activateEndpointByPath(path: string): Promise { + return await EndpointBrokerManagerFS.activateEndpoint(path) } - async deleteSSLTerminationByPath(name: string): Promise { - throw new Error("Method not implemented."); + + public async deactivateEndpointByPath(path: string): Promise { + return await EndpointBrokerManagerFS.deactivateEndpoint(path) } - async getAllSSLTerminations(): Promise { - throw new Error("Method not implemented."); + + public async getSSLTerminationByPath(path: string): Promise { + const endpoint = await this.getSSLTerminationFSByPath(path) + return endpoint?.toIEndpoint() as SSLTermination } + + public async modifySSLTerminationByPath(path: string, changes: SSLTerminationChanges): Promise { + const oldEndpoint = await this.getSSLTerminationFSByPath(path) + + if (!oldEndpoint) { + // UGLY: be specific + throw new Error("There was no old endpoint at this path") + } + + const newEndpoint = new SSLTerminationFS( + changes.name ?? oldEndpoint.name, + oldEndpoint.stats, + changes.sslPort ?? oldEndpoint.sslPort, + changes.clearPort ?? oldEndpoint.clearPort, + changes.servicePort ?? oldEndpoint.servicePort, + changes.serviceEndpoint ?? oldEndpoint.serviceEndpoint, + changes.protocol ?? oldEndpoint.protocol, + changes.certificateURI ?? oldEndpoint.certificateURI, + changes.privateKeyURI ?? oldEndpoint.privateKeyURI + ) + + EndpointBrokerManagerFS.changeEndpoint(path, newEndpoint) + + return newEndpoint.toIEndpoint() as SSLTermination + } + + + public async deleteSSLTerminationByPath(path: string): Promise { + const candidate = await EndpointBrokerManagerFS.deleteEndpoint(path) + + if (!candidate) { + return null + } + + return candidate as SSLTerminationFS + } + + // Technically expensive to implement + public async getAllSSLTerminations(): Promise { + + const endpoints = (await EndpointBrokerManagerFS.getAll()).filter((endpoint) => { + if (endpoint.type !== EndpointType.SSL_TERMINATION) { + return false + } + + return true + }) as SSLTerminationFS[] + + return endpoints.map((endpoint) => { + return endpoint.toIEndpoint() as SSLTermination + }) + + } + + private async getSSLTerminationFSByPath(path: string): Promise { + const endpoint = EndpointBrokerManagerFS.getEndpointByPath(path) + + if (!endpoint) { + return null + } + + if (endpoint.type !== EndpointType.SSL_TERMINATION) { + // UGLY: be specific + throw new Error("This is not an SSLEndpoint") + } + + return endpoint as SSLTerminationFS + } + } \ No newline at end of file diff --git a/src/lib/server/broker-utils/FileSystem/endpoints/endpoints.ts b/src/lib/server/broker-utils/FileSystem/endpoints/endpoints.ts index 8ec01ce..b953ce8 100644 --- a/src/lib/server/broker-utils/FileSystem/endpoints/endpoints.ts +++ b/src/lib/server/broker-utils/FileSystem/endpoints/endpoints.ts @@ -16,7 +16,7 @@ export interface IEndpointFS { * whether the file has been actually * parsed by nginx during last reload */ - stats: FileStats + stats: FileStats | null /** File path */ path: string /** Hash of the file */ diff --git a/src/lib/server/broker-utils/FileSystem/endpoints/ssltermination-fs.ts b/src/lib/server/broker-utils/FileSystem/endpoints/ssltermination-fs.ts index b7e0661..c1602dc 100644 --- a/src/lib/server/broker-utils/FileSystem/endpoints/ssltermination-fs.ts +++ b/src/lib/server/broker-utils/FileSystem/endpoints/ssltermination-fs.ts @@ -6,7 +6,8 @@ import type { FSHeader, IEndpointFS } from "./endpoints" import { createHeader, parseDefaultHeader, parseGenericHeader } from "../utils" import { hashUtil } from "$lib/server/utils/filesystem-utils" import type { IEndpoint } from "$lib/server/classes/endpoints/endpoints-interfaces" -import { SSLTermination } from "$lib/server/classes/endpoints/ssl-termination-endpoint" +import { SSLTermination, type ISSLTerminationBroker, type SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint" +import { EndpointBrokerManagerFS } from "../Endpoint-Manager" // TODO: add broker implementation @@ -21,11 +22,15 @@ export class SSLTerminationFS implements IEndpointFS { public name: string - public stats: Stats + public stats: Stats|null public get path() { return `${this.protocol}/name.conf` } - public hash: string + public get hash() { + return hashUtil( + this.toConf() + ) + } public sslPort: number public clearPort: number public servicePort: number @@ -38,8 +43,7 @@ export class SSLTerminationFS implements IEndpointFS { constructor( name: string, - stats: Stats, - hash: string, + stats: Stats|null, sslPort: number, clearPort: number, servicePort: number, @@ -51,7 +55,6 @@ export class SSLTerminationFS implements IEndpointFS { this.name = name this.stats = stats - this.hash = hash this.sslPort = sslPort this.clearPort = clearPort this.servicePort = servicePort @@ -121,7 +124,7 @@ export class SSLTerminationFS implements IEndpointFS { const sslPort = Number.parseInt( keyValue.get( - "SSL_PORT" + "SSL_PORT" ) ?? "" ) @@ -129,7 +132,7 @@ export class SSLTerminationFS implements IEndpointFS { const clearPort = Number.parseInt( keyValue.get( - "CLEAR_PORT" + "CLEAR_PORT" ) ?? "" ) @@ -137,7 +140,7 @@ export class SSLTerminationFS implements IEndpointFS { const servicePort = Number.parseInt( keyValue.get( - "SERVICE_PORT" + "SERVICE_PORT" ) ?? "" ) @@ -164,7 +167,6 @@ export class SSLTerminationFS implements IEndpointFS { return new SSLTerminationFS( name, fsHeader.stats, - fsHeader.hash, sslPort, clearPort, servicePort, @@ -174,7 +176,7 @@ export class SSLTerminationFS implements IEndpointFS { privateKeyURI ) - + } @@ -288,4 +290,5 @@ export class SSLTerminationFS implements IEndpointFS { } -} \ No newline at end of file +} + diff --git a/src/lib/server/classes/endpoints/endpoint-manager.ts b/src/lib/server/classes/endpoints/endpoint-manager.ts index fd300c2..c81a57a 100644 --- a/src/lib/server/classes/endpoints/endpoint-manager.ts +++ b/src/lib/server/classes/endpoints/endpoint-manager.ts @@ -1,4 +1,4 @@ -import { EndpointBrokerManagerFS } from "$lib/server/broker-utils/FileSystem/Endpoint" +import { EndpointBrokerManagerFS } from "$lib/server/broker-utils/FileSystem/Endpoint-Manager" import type { IEndpoint } from "./endpoints-interfaces" @@ -15,13 +15,8 @@ export class EndpointManagerApp { return EndpointManagerApp.initialized } - public static init() { - // TODO: Read all files - - // TODO: QUICK parse them - - // TODO: Initialize a file watcher - + public static async init() { + await EndpointBrokerManagerFS.init() } diff --git a/src/lib/server/classes/endpoints/manual-endpoint.ts b/src/lib/server/classes/endpoints/manual-endpoint.ts index 3dcc215..b605dab 100644 --- a/src/lib/server/classes/endpoints/manual-endpoint.ts +++ b/src/lib/server/classes/endpoints/manual-endpoint.ts @@ -6,7 +6,7 @@ export interface IManualBroker { init(): Promise - getManualByPath(path: string): Promise + getManualByPath(path: string): Promise getAllManuals(): Promise diff --git a/src/lib/server/classes/endpoints/ssl-termination-endpoint.ts b/src/lib/server/classes/endpoints/ssl-termination-endpoint.ts index 35c93ce..8a70894 100644 --- a/src/lib/server/classes/endpoints/ssl-termination-endpoint.ts +++ b/src/lib/server/classes/endpoints/ssl-termination-endpoint.ts @@ -14,22 +14,17 @@ export interface ISSLTerminationBroker { // TODO: in the next version support // TODO: creation of endpoints // TODO: according to path - // Creation should throw if something goes wrong - // with reasons why - createSSLTerminationSimple( - name: string, - servicePort: number, - serviceEndpoint: string, - certificateURI: string, - privateKeyURI: string - ): Promise - createSSLTerminationComplete( + // NOTES: it's useless to generate ports backend + // NOTES: generate them frontend and validate backend + + createSSLTermination( name: string, sslPort: number, clearPort: number, servicePort: number, serviceEndpoint: string, + protocol: NginxProtocol, certificateURI: string, privateKeyURI: string ): Promise @@ -150,47 +145,26 @@ export class SSLTerminationEndpointApp { SSLTerminationEndpointApp.initialized = true } - - // Creation should throw if something goes wrong - // with reasons why - public static async createSSLTerminationSimple( - name: string, - servicePort: number, - serviceEndpoint: string, - certificateURI: string, - privateKeyURI: string - ): Promise { - - SSLTerminationEndpointApp.assureInitialized() - - return await this.broker.createSSLTerminationSimple( - name, - servicePort, - serviceEndpoint, - certificateURI, - privateKeyURI - ) - - } - - public static async createSSLTerminationComplete( + public static async createSSLTermination( name: string, sslPort: number, clearPort: number, servicePort: number, serviceEndpoint: string, + protocol: NginxProtocol, certificateURI: string, privateKeyURI: string ): Promise { SSLTerminationEndpointApp.assureInitialized() - return await this.broker.createSSLTerminationComplete( + return await this.broker.createSSLTermination( name, sslPort, clearPort, servicePort, serviceEndpoint, + protocol, certificateURI, privateKeyURI ) diff --git a/src/lib/server/utils/filesystem-utils.ts b/src/lib/server/utils/filesystem-utils.ts index f3feb1b..7475357 100644 --- a/src/lib/server/utils/filesystem-utils.ts +++ b/src/lib/server/utils/filesystem-utils.ts @@ -157,3 +157,18 @@ export function hashUtil(data: BinaryLike) { ).digest('base64') return hash } + +export async function listFiles(path: string, recursive?: boolean): Promise { + + if (!recursive) { + recursive = false + } + + if (!await isDir(path)) { + // UGLY: be specific + throw new Error("This is not a directory") + } + + return await Node.readdir(path, {recursive: recursive}) + +} diff --git a/src/lib/server/utils/ports-utils.ts b/src/lib/server/utils/ports-utils.ts index bac2235..11181f1 100644 --- a/src/lib/server/utils/ports-utils.ts +++ b/src/lib/server/utils/ports-utils.ts @@ -1,4 +1,5 @@ -// TODO: remove bun dependecies + +import { shell } from "./shell-commands" /** * This methods runs `netstat -ltun` @@ -14,8 +15,8 @@ export async function portScan(): Promise> { const portRegex = new RegExp("(?:\:)(?[0-9]+)", "gm") - const netstatOutput : string = await $`netstat -ltun`.text() - const ports = netstatOutput.matchAll(portRegex) + const netstatOutput = await shell(`netstat -ltun`) + const ports = netstatOutput.stdout.matchAll(portRegex) const portArray : Set= new Set()