From d7c01d344639abd42a2bb8310d383f5cf76f58a2 Mon Sep 17 00:00:00 2001 From: steelbrain Date: Thu, 15 Feb 2024 05:21:46 +0200 Subject: [PATCH] :new: Setup config loading --- lib/package.json | 3 + package.json | 5 ++ src/config.ts | 96 ++++++++++++++++++++++++++-- src/constants.ts | 16 ++--- src/server.ts | 12 +++- template.ffmpeg-over-ip.server.jsonc | 13 +++- yarn.lock | 10 +++ 7 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 lib/package.json diff --git a/lib/package.json b/lib/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/lib/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/package.json b/package.json index 6aa7e88..6a9c02c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "description": "TODO", "main": "index.js", + "type": "module", "scripts": { "build:server": "esbuild src/server.ts --platform=node --bundle --outdir=lib", "watch:server": "esbuild src/server.ts --platform=node --bundle --outdir=lib --watch" @@ -14,5 +15,9 @@ "@types/node": "20", "esbuild": "^0.20.0", "typescript": "^5.3.3" + }, + "dependencies": { + "strip-json-comments": "^5.0.1", + "zod": "^3.22.4" } } diff --git a/src/config.ts b/src/config.ts index 6c5cebe..8ef096a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,93 @@ -import { CONFIG_FILE_ENV, CONFIG_FILE_NAMES, Runtime } from './constants' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { z } from 'zod' -export function loadConfig(runtime: Runtime): Config { - const configFile = process.env[CONFIG_FILE_ENV] || CONFIG_FILE_NAMES[runtime] - const config = require(`./${configFile}`) - return config +import stripJsonComments from 'strip-json-comments' + +import { + CONFIG_FILE_SEARCH_PATHS_CLIENT, + CONFIG_FILE_SEARCH_PATHS_SERVER, + Runtime, +} from './constants.js' +import createLogger from './logger.js' + +const configSchemaServer = z + .object({ + log: z.union([z.literal(false), z.string()]), + listenAddress: z.string(), + listenPort: z.number(), + authSecret: z.string().min(15).max(100), + }) + .strict() + +const configSchemaClient = z.object({ + log: z.union([z.literal(false), z.string()]), + connectAddress: z.string(), + connectPort: z.number(), + authSecret: z.string().min(15).max(100), +}) + +export async function loadConfig( + runtime: Runtime +): Promise< + T extends Runtime.Server ? z.infer : z.infer +> { + const configFilePaths = + runtime === Runtime.Server ? CONFIG_FILE_SEARCH_PATHS_SERVER : CONFIG_FILE_SEARCH_PATHS_CLIENT + + let selectedConfigFilePath: string | null = null + for (const [directory, files] of configFilePaths) { + let directoryToUse = directory + if (directoryToUse.includes('$TMPDIR')) { + directoryToUse = directoryToUse.replace('$TMPDIR', os.tmpdir()) + } + for (const file of files) { + const filePath = path.join(directory, file) + const fileStat = await fs.promises.stat(filePath).catch(() => null) + if (fileStat != null) { + selectedConfigFilePath = filePath + break + } + } + } + + if (selectedConfigFilePath == null) { + throw new Error( + 'No config file found. Try running with --debug-print-search-paths to print search paths' + ) + } + const schemaToUse = runtime === Runtime.Server ? configSchemaServer : configSchemaClient + let configContent: z.infer + + let configLogger: ReturnType | null = null + + try { + const textContents = await fs.promises.readFile(selectedConfigFilePath, 'utf-8') + let parsed: unknown + try { + parsed = JSON.parse(stripJsonComments(textContents)) + } catch (err) { + throw new Error('Malformed JSON in config file') + } + if ( + parsed != null && + typeof parsed === 'object' && + 'log' in parsed && + typeof parsed.log === 'string' + ) { + configLogger = createLogger(parsed.log) + } + + configContent = schemaToUse.parse(parsed) + } catch (err) { + const message = `Failed to read config file at ${selectedConfigFilePath}: ${err}` + configLogger?.error(message) + + throw new Error(message) + } + + return configContent as T extends Runtime.Server + ? z.infer + : z.infer } diff --git a/src/constants.ts b/src/constants.ts index 44349e3..0005ee4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,7 +14,7 @@ const CONFIG_FILE_NAMES_SERVER_SHORT = ['config.server.json', 'config.server.jso const CONFIG_FILE_NAMES_CLIENT_FULL = ['ffmpeg-over-ip.client.json', 'ffmpeg-over-ip.client.jsonc'] const CONFIG_FILE_NAMES_CLIENT_SHORT = ['config.client.json', 'config.client.jsonc'] -const CONFIG_FILE_SEARCH_PATHS = [ +const CONFIG_FILE_SEARCH_PATHS: [string, string[], string[]][] = [ ['/etc/ffmpeg-over-ip', CONFIG_FILE_NAMES_SERVER_SHORT, CONFIG_FILE_NAMES_CLIENT_SHORT], ['/etc', CONFIG_FILE_NAMES_SERVER_FULL, CONFIG_FILE_NAMES_CLIENT_FULL], [ @@ -39,12 +39,10 @@ if (process.argv.length > 1) { ]) } -export const CONFIG_FILE_SEARCH_PATHS_SERVER = CONFIG_FILE_SEARCH_PATHS.map(([path, server]) => [ - path, - server, -]) +export const CONFIG_FILE_SEARCH_PATHS_SERVER: [string, string[]][] = CONFIG_FILE_SEARCH_PATHS.map( + ([path, server]) => [path, server] +) -export const CONFIG_FILE_SEARCH_PATHS_CLIENT = CONFIG_FILE_SEARCH_PATHS.map(([path, , client]) => [ - path, - client, -]) +export const CONFIG_FILE_SEARCH_PATHS_CLIENT: [string, string[]][] = CONFIG_FILE_SEARCH_PATHS.map( + ([path, , client]) => [path, client] +) diff --git a/src/server.ts b/src/server.ts index ba5c747..cd56887 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,10 +1,18 @@ -import { CONFIG_FILE_SEARCH_PATHS_SERVER } from './constants' +import { loadConfig } from './config.js' +import { CONFIG_FILE_SEARCH_PATHS_SERVER, Runtime } from './constants.js' async function main() { - if (process.argv.includes('--internal-print-search-paths')) { + if (process.argv.includes('--debug-print-search-paths')) { console.log(CONFIG_FILE_SEARCH_PATHS_SERVER) process.exit(1) } + + const config = await loadConfig(Runtime.Server) + + if (process.argv.includes('--debug-print-config')) { + console.log(config) + process.exit(1) + } } main().catch(err => { diff --git a/template.ffmpeg-over-ip.server.jsonc b/template.ffmpeg-over-ip.server.jsonc index 3ef56d7..ff450d7 100644 --- a/template.ffmpeg-over-ip.server.jsonc +++ b/template.ffmpeg-over-ip.server.jsonc @@ -3,12 +3,19 @@ "log": "stdout", // type: "stdout" | "stderr" | any string (file path) | false // Other possibilities: - "log": "$TMP/ffmpeg-over-ip.server.log", - // ^ $TMP is a special variable here, only supported in "log" config where it uses the operating system + "log": "$TMPDIR/ffmpeg-over-ip.server.log", + // ^ $TMPDIR is a special variable here, only supported in "log" config where it uses the operating system // temp folder "log": false, // ^ This turns off logging completely "log": "stdout", "log": "stderr", - "log": "/var/log/messages.log" + "log": "/var/log/messages.log", + + "listenAddress": "0.0.0.0", // type: string + // You can specify a specific address to listen to, by default, listens on all addresses + "listenPort": 5050, // type: number + + "authSecret": "YOUR-CLIENT-PASSWORD-HERE" // type: string + // ^ Ideally keep this within reason (It'll be received in an HTTP header) but definitely not less than 15 characters } diff --git a/yarn.lock b/yarn.lock index c0109f3..515839a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -207,6 +207,11 @@ esbuild@^0.20.0: "@esbuild/win32-ia32" "0.20.0" "@esbuild/win32-x64" "0.20.0" +strip-json-comments@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.1.tgz#0d8b7d01b23848ed7dbdf4baaaa31a8250d8cfa0" + integrity sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw== + typescript@^5.3.3: version "5.3.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" @@ -216,3 +221,8 @@ undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +zod@^3.22.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==