From 01eef7691c33627b4b9142828d85c2bd03fe1b0d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 5 Feb 2024 23:44:22 +0000 Subject: [PATCH] fix: import from old config file on each startup, as an editable overrides --- pi-image/satellite-config | 14 ++++-- pi-image/satellite.service | 3 +- src/config.ts | 14 ++++++ src/fixup-pi-config.ts | 98 ++++++++++++++++++++++++++++++++++++++ src/main.ts | 14 +----- 5 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 src/fixup-pi-config.ts diff --git a/pi-image/satellite-config b/pi-image/satellite-config index fbc88a5..f224af5 100644 --- a/pi-image/satellite-config +++ b/pi-image/satellite-config @@ -1,11 +1,19 @@ +# +# Since v1.7.0 of satellite this is no longer the main config file. +# It is read at startup to import configuration to the main config file. +# After it has been read, it gets reset back to defaults. +# +# More options are available in the web interface. +# + # Set this to the ip address or hostname of your companion installation # examples: # - COMPANION_IP=192.168.100.1 # - COMPANION_IP=companion.example.org -COMPANION_IP=127.0.0.1 +# COMPANION_IP=127.0.0.1 # If you are connecting through a router or firewall which has remapped the port, you will need to change that here to match -COMPANION_PORT=16622 +# COMPANION_PORT=16622 # Port for the REST server (0 to disable) -REST_PORT=9999 \ No newline at end of file +# REST_PORT=9999 diff --git a/pi-image/satellite.service b/pi-image/satellite.service index cc6fb1d..f0349a8 100644 --- a/pi-image/satellite.service +++ b/pi-image/satellite.service @@ -7,7 +7,8 @@ Wants=network-online.target Type=simple User=satellite WorkingDirectory=/usr/local/src/companion-satellite -ExecStart=/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/dist/main.js +ExecStartPre=+/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/dist/fixup-pi-config.js /home/satellite/satellite-config.json +ExecStart=/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/dist/main.js /home/satellite/satellite-config.json Restart=on-failure KillSignal=SIGINT TimeoutStopSec=60 diff --git a/src/config.ts b/src/config.ts index cd6c920..8d31668 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,5 @@ import Conf, { Schema } from 'conf' +import path from 'path' export interface SatelliteConfig { remoteIp: string @@ -44,3 +45,16 @@ export function ensureFieldsPopulated(store: Conf): void { } } } + +export function openHeadlessConfig(rawConfigPath: string): Conf { + const absoluteConfigPath = path.isAbsolute(rawConfigPath) ? rawConfigPath : path.join(process.cwd(), rawConfigPath) + + const appConfig = new Conf({ + schema: satelliteConfigSchema, + configName: path.parse(absoluteConfigPath).name, + projectName: 'companion-satellite', + cwd: path.dirname(absoluteConfigPath), + }) + ensureFieldsPopulated(appConfig) + return appConfig +} diff --git a/src/fixup-pi-config.ts b/src/fixup-pi-config.ts new file mode 100644 index 0000000..d6d19b5 --- /dev/null +++ b/src/fixup-pi-config.ts @@ -0,0 +1,98 @@ +/** + * This is a small pre-launch step that gets run on SatellitePi. + * It's purpose is to import user defined overrides from the 'boot' partition + * Note: This gets run as root! + */ + +import { stat, readFile, copyFile, chown } from 'fs/promises' +import { openHeadlessConfig } from './config' +import path from 'path' + +const configFilePath = process.argv[2] +if (!configFilePath) throw new Error(`Missing config file path parameter`) + +const appConfig = openHeadlessConfig(configFilePath) + +// Ensure the satellite user owns the file. This is a bit dodgey guessing the ids like this.. +chown(appConfig.path, 1000, 1000).catch(() => null) + +const templatePathName = path.join(__dirname, '../pi-image/satellite-config') + +const importFromPaths = [ + // Paths to search for a config file to 'import' from + '/boot/satellite-config', + '/boot/firmware/satellite-config', + '/satellite-config', + // templatePathName, // For testing +] + +Promise.resolve() + .then(async () => { + for (const importPath of importFromPaths) { + try { + const fileStat = await stat(importPath) + if (!fileStat.isFile()) throw new Error('Not a file') + + const fileContentStr = await readFile(importPath) + const lines = fileContentStr.toString().split('\n') + + console.log(`Importing config from ${importPath}`) + + for (let line of lines) { + line = line.trim() + + // Ignore any comments + if (line.startsWith('#')) continue + + const splitIndex = line.indexOf('=') + if (splitIndex == -1) continue + + const key = line.slice(0, splitIndex).trim() + const value = line.slice(splitIndex + 1).trim() + + switch (key.toUpperCase()) { + case 'COMPANION_IP': + appConfig.set('remoteIp', value) + break + case 'COMPANION_PORT': { + const port = Number(value) + if (isNaN(port)) { + console.log('COMPANION_PORT is not a number!') + break + } + appConfig.set('remotePort', port) + break + } + case 'REST_PORT': { + const port = Number(value) + if (isNaN(port)) { + console.log('REST_PORT is not a number!') + break + } + if (port > 0) { + appConfig.set('restPort', port) + appConfig.set('restEnabled', true) + } else { + appConfig.set('restEnabled', false) + } + break + } + default: + console.log(`Unknown value: ${key}=${value}`) + break + } + } + + if (templatePathName !== importPath) { + await copyFile(templatePathName, importPath) + } + } catch (e: any) { + if (e.code === 'ENOENT') continue + // Failed, try next file + console.log(`Unable to import from file "${importPath}"`, e) + } + } + }) + .catch(() => { + // Ignore + }) diff --git a/src/main.ts b/src/main.ts index d4b4f90..9670a78 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,10 +3,8 @@ import meow from 'meow' import { CompanionSatelliteClient } from './client' import { DeviceManager } from './devices' import { DEFAULT_PORT } from './lib' -import Conf from 'conf' -import path from 'path' import { RestServer } from './rest' -import { SatelliteConfig, ensureFieldsPopulated, satelliteConfigSchema } from './config' +import { openHeadlessConfig } from './config' const cli = meow( ` @@ -25,15 +23,7 @@ if (cli.input.length === 0) { } const rawConfigPath = cli.input[0] -const absoluteConfigPath = path.isAbsolute(rawConfigPath) ? rawConfigPath : path.join(process.cwd(), rawConfigPath) - -const appConfig = new Conf({ - schema: satelliteConfigSchema, - configName: path.parse(absoluteConfigPath).name, - projectName: 'companion-satellite', - cwd: path.dirname(absoluteConfigPath), -}) -ensureFieldsPopulated(appConfig) +const appConfig = openHeadlessConfig(rawConfigPath) console.log('Starting', appConfig.path)