🆕 Setup config loading

This commit is contained in:
steelbrain
2024-02-15 05:21:46 +02:00
parent f1e41d5cfd
commit d7c01d3446
7 changed files with 136 additions and 19 deletions

3
lib/package.json Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -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"
}
}

View File

@@ -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<T extends Runtime.Client | Runtime.Server>(
runtime: Runtime
): Promise<
T extends Runtime.Server ? z.infer<typeof configSchemaServer> : z.infer<typeof configSchemaClient>
> {
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<typeof schemaToUse>
let configLogger: ReturnType<typeof createLogger> | 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<typeof configSchemaServer>
: z.infer<typeof configSchemaClient>
}

View File

@@ -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]
)

View File

@@ -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 => {

View File

@@ -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
}

View File

@@ -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==