V 0.1.2 Ended writing backend utils

This commit is contained in:
Christian Risi 2025-07-04 03:05:43 +00:00
parent 3eda2cf0a1
commit 67c8e9c31d
11 changed files with 283 additions and 99 deletions

View File

@ -8,6 +8,11 @@ import { SessionDBBroker } from '$lib/server/broker-utils/SQLite/Sessions';
import { AppData } from '$lib/server/classes/appdata'; import { AppData } from '$lib/server/classes/appdata';
import { logger } from '$lib/server/utils/logger'; import { logger } from '$lib/server/utils/logger';
import { JoseApp } from '$lib/server/utils/jtw-utils'; 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 () => { export const init: ServerInit = async () => {
@ -34,6 +39,23 @@ export const init: ServerInit = async () => {
await JoseApp.init() 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") logger.debug("Init run successfully", "App Init")

View File

@ -1,6 +1,6 @@
import type { FSHeader, IEndpointFS } from "$lib/server/broker-utils/FileSystem/endpoints/endpoints" 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 { 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 { watch, type FSWatcher } from "node:fs"
import { parseConf } from "./utils" import { parseConf } from "./utils"
import { logger } from "$lib/server/utils/logger" import { logger } from "$lib/server/utils/logger"
@ -29,10 +29,44 @@ export class EndpointBrokerManagerFS {
// UGLY: be specific // UGLY: be specific
throw new Error("Broker already initialized") throw new Error("Broker already initialized")
} }
// TODO: Read all files
// TODO: QUICK parse them const configurations = (await listFiles(NGINX_TRACKED, true)).filter( async (path) => {
parseConf() if (await isDir(path)) {
return false
}
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 // TODO: Initialize a file watcher
EndpointBrokerManagerFS.watcher = EndpointBrokerManagerFS.watchNginxDirectory() EndpointBrokerManagerFS.watcher = EndpointBrokerManagerFS.watchNginxDirectory()
@ -72,14 +106,24 @@ export class EndpointBrokerManagerFS {
const file = await loadFile(REAL_PATH) const file = await loadFile(REAL_PATH)
await file.delete() await file.delete()
const endpoint = EndpointBrokerManagerFS.endpoints.get(
path
)
EndpointBrokerManagerFS.endpoints.delete( EndpointBrokerManagerFS.endpoints.delete(
path path
) )
return endpoint
} }
public static async changeEndpoint(path: string, newEndpoint: IEndpointFS) { 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) validatePath(path)
const REAL_PATH = `${NGINX_TRACKED}/${path}` const REAL_PATH = `${NGINX_TRACKED}/${path}`
@ -305,7 +349,7 @@ export class EndpointBrokerManagerFS {
const fsHeader: FSHeader = { const fsHeader: FSHeader = {
name: filename!.split("/").pop()!, name: filename!.split("/").pop()!,
stats: stats, stats: stats,
path: RELATIVE_PATH, path: FULL_PATH,
hash: hash hash: hash
} }

View File

@ -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<void> {
// Do nothing?
}
public async getManualByPath(path: string): Promise<Manual| null> {
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<Manual[]> {
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<boolean> {
return await EndpointBrokerManagerFS.activateEndpoint(path)
}
public async deactivateEndpointByPath(path: string): Promise<boolean> {
return await EndpointBrokerManagerFS.deactivateEndpoint(path)
}
}

View File

@ -1,57 +1,139 @@
import { type ISSLTerminationBroker, type SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint"; import type { ISSLTerminationBroker, SSLTermination, SSLTerminationChanges } from "$lib/server/classes/endpoints/ssl-termination-endpoint"
import type { NginxProtocol } from "$lib/server/enums/protocols"; 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 { export class SSLTerminationFSBroker implements ISSLTerminationBroker {
private static initialized = false
async init(): Promise<void> {
if (SSLTerminationBroker.initialized) {
// UGLY: more specific
throw new Error("SSLTerminationBroker was already initialized")
}
public async init(): Promise<void> {
// Do nothing?
} }
async createSSLTerminationSimple( public async createSSLTermination(
name: string,
servicePort: number,
serviceEndpoint: string,
certificateURI: string,
privateKeyURI: string
): Promise<SSLTermination> {
throw new Error("Method not implemented.");
}
async createSSLTerminationComplete(
name: string, name: string,
sslPort: number, sslPort: number,
clearPort: number, clearPort: number,
servicePort: number, servicePort: number,
serviceEndpoint: string, serviceEndpoint: string,
protocol: NginxProtocol,
certificateURI: string, certificateURI: string,
privateKeyURI: string privateKeyURI: string
): Promise<SSLTermination> { ): Promise<SSLTermination> {
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 getSSLTerminationByPath(name: string): Promise<SSLTermination | null> {
throw new Error("Method not implemented."); public async activateEndpointByPath(path: string): Promise<boolean> {
return await EndpointBrokerManagerFS.activateEndpoint(path)
} }
async modifySSLTerminationByPath(name: string, changes: SSLTerminationChanges): Promise<SSLTermination> {
throw new Error("Method not implemented."); public async deactivateEndpointByPath(path: string): Promise<boolean> {
return await EndpointBrokerManagerFS.deactivateEndpoint(path)
} }
async deleteSSLTerminationByPath(name: string): Promise<SSLTermination | null> {
throw new Error("Method not implemented."); public async getSSLTerminationByPath(path: string): Promise<SSLTermination | null> {
const endpoint = await this.getSSLTerminationFSByPath(path)
return endpoint?.toIEndpoint() as SSLTermination
} }
async getAllSSLTerminations(): Promise<SSLTermination[]> {
throw new Error("Method not implemented."); public async modifySSLTerminationByPath(path: string, changes: SSLTerminationChanges): Promise<SSLTermination> {
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<SSLTermination | null> {
const candidate = await EndpointBrokerManagerFS.deleteEndpoint(path)
if (!candidate) {
return null
}
return candidate as SSLTerminationFS
}
// Technically expensive to implement
public async getAllSSLTerminations(): Promise<SSLTermination[]> {
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<SSLTerminationFS | null> {
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
} }
} }

View File

@ -16,7 +16,7 @@ export interface IEndpointFS {
* whether the file has been actually * whether the file has been actually
* parsed by nginx during last reload * parsed by nginx during last reload
*/ */
stats: FileStats stats: FileStats | null
/** File path */ /** File path */
path: string path: string
/** Hash of the file */ /** Hash of the file */

View File

@ -6,7 +6,8 @@ import type { FSHeader, IEndpointFS } from "./endpoints"
import { createHeader, parseDefaultHeader, parseGenericHeader } from "../utils" import { createHeader, parseDefaultHeader, parseGenericHeader } from "../utils"
import { hashUtil } from "$lib/server/utils/filesystem-utils" import { hashUtil } from "$lib/server/utils/filesystem-utils"
import type { IEndpoint } from "$lib/server/classes/endpoints/endpoints-interfaces" 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 // TODO: add broker implementation
@ -21,11 +22,15 @@ export class SSLTerminationFS implements IEndpointFS {
public name: string public name: string
public stats: Stats public stats: Stats|null
public get path() { public get path() {
return `${this.protocol}/name.conf` return `${this.protocol}/name.conf`
} }
public hash: string public get hash() {
return hashUtil(
this.toConf()
)
}
public sslPort: number public sslPort: number
public clearPort: number public clearPort: number
public servicePort: number public servicePort: number
@ -38,8 +43,7 @@ export class SSLTerminationFS implements IEndpointFS {
constructor( constructor(
name: string, name: string,
stats: Stats, stats: Stats|null,
hash: string,
sslPort: number, sslPort: number,
clearPort: number, clearPort: number,
servicePort: number, servicePort: number,
@ -51,7 +55,6 @@ export class SSLTerminationFS implements IEndpointFS {
this.name = name this.name = name
this.stats = stats this.stats = stats
this.hash = hash
this.sslPort = sslPort this.sslPort = sslPort
this.clearPort = clearPort this.clearPort = clearPort
this.servicePort = servicePort this.servicePort = servicePort
@ -164,7 +167,6 @@ export class SSLTerminationFS implements IEndpointFS {
return new SSLTerminationFS( return new SSLTerminationFS(
name, name,
fsHeader.stats, fsHeader.stats,
fsHeader.hash,
sslPort, sslPort,
clearPort, clearPort,
servicePort, servicePort,
@ -289,3 +291,4 @@ export class SSLTerminationFS implements IEndpointFS {
} }

View File

@ -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" import type { IEndpoint } from "./endpoints-interfaces"
@ -15,13 +15,8 @@ export class EndpointManagerApp {
return EndpointManagerApp.initialized return EndpointManagerApp.initialized
} }
public static init() { public static async init() {
// TODO: Read all files await EndpointBrokerManagerFS.init()
// TODO: QUICK parse them
// TODO: Initialize a file watcher
} }

View File

@ -6,7 +6,7 @@ export interface IManualBroker {
init(): Promise<void> init(): Promise<void>
getManualByPath(path: string): Promise<Manual> getManualByPath(path: string): Promise<Manual| null>
getAllManuals(): Promise<Manual[]> getAllManuals(): Promise<Manual[]>

View File

@ -14,22 +14,17 @@ export interface ISSLTerminationBroker {
// TODO: in the next version support // TODO: in the next version support
// TODO: creation of endpoints // TODO: creation of endpoints
// TODO: according to path // 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<SSLTermination>
createSSLTerminationComplete( // NOTES: it's useless to generate ports backend
// NOTES: generate them frontend and validate backend
createSSLTermination(
name: string, name: string,
sslPort: number, sslPort: number,
clearPort: number, clearPort: number,
servicePort: number, servicePort: number,
serviceEndpoint: string, serviceEndpoint: string,
protocol: NginxProtocol,
certificateURI: string, certificateURI: string,
privateKeyURI: string privateKeyURI: string
): Promise<SSLTermination> ): Promise<SSLTermination>
@ -150,47 +145,26 @@ export class SSLTerminationEndpointApp {
SSLTerminationEndpointApp.initialized = true SSLTerminationEndpointApp.initialized = true
} }
public static async createSSLTermination(
// 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<SSLTermination> {
SSLTerminationEndpointApp.assureInitialized()
return await this.broker.createSSLTerminationSimple(
name,
servicePort,
serviceEndpoint,
certificateURI,
privateKeyURI
)
}
public static async createSSLTerminationComplete(
name: string, name: string,
sslPort: number, sslPort: number,
clearPort: number, clearPort: number,
servicePort: number, servicePort: number,
serviceEndpoint: string, serviceEndpoint: string,
protocol: NginxProtocol,
certificateURI: string, certificateURI: string,
privateKeyURI: string privateKeyURI: string
): Promise<SSLTermination> { ): Promise<SSLTermination> {
SSLTerminationEndpointApp.assureInitialized() SSLTerminationEndpointApp.assureInitialized()
return await this.broker.createSSLTerminationComplete( return await this.broker.createSSLTermination(
name, name,
sslPort, sslPort,
clearPort, clearPort,
servicePort, servicePort,
serviceEndpoint, serviceEndpoint,
protocol,
certificateURI, certificateURI,
privateKeyURI privateKeyURI
) )

View File

@ -157,3 +157,18 @@ export function hashUtil(data: BinaryLike) {
).digest('base64') ).digest('base64')
return hash return hash
} }
export async function listFiles(path: string, recursive?: boolean): Promise<string[]> {
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})
}

View File

@ -1,4 +1,5 @@
// TODO: remove bun dependecies
import { shell } from "./shell-commands"
/** /**
* This methods runs `netstat -ltun` * This methods runs `netstat -ltun`
@ -14,8 +15,8 @@
export async function portScan(): Promise<Set<number>> { export async function portScan(): Promise<Set<number>> {
const portRegex = new RegExp("(?:\:)(?<port_number>[0-9]+)", "gm") const portRegex = new RegExp("(?:\:)(?<port_number>[0-9]+)", "gm")
const netstatOutput : string = await $`netstat -ltun`.text() const netstatOutput = await shell(`netstat -ltun`)
const ports = netstatOutput.matchAll(portRegex) const ports = netstatOutput.stdout.matchAll(portRegex)
const portArray : Set<number>= new Set() const portArray : Set<number>= new Set()