Support specifing `config.yaml` in cli

26a520af1075f053982b8dab718e5a4b4ef09d1c

wrvsrx <wrvsrx@outlook.com>

Signed
3 files changed, +71 -42Showing whitespace changes
server.js+41 -38
@@ -20,10 +20,28 @@ import open from 'open';
2020
21// local library imports21// local library imports
22import './src/fetch-patch.js';22import './src/fetch-patch.js';
23import { serverEvents, EVENT_NAMES } from './src/server-events.js';
24import { CommandLineParser } from './src/command-line.js';23import { CommandLineParser } from './src/command-line.js';
25import { loadPlugins } from './src/plugin-loader.js';24import { serverDirectory } from './src/server-directory.js';
26import {25
26console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`);
27
28// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
29// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
30// Safe to remove once support for Node v20 is dropped.
31if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) {
32 // @ts-ignore
33 if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
34}
35
36// config.yaml will be set when parsing command line arguments
37const cliArgs = new CommandLineParser().parse(process.argv);
38globalThis.DATA_ROOT = cliArgs.dataRoot;
39globalThis.COMMAND_LINE_ARGS = cliArgs;
40process.chdir(serverDirectory);
41
42const { serverEvents, EVENT_NAMES } = await import('./src/server-events.js');
43const { loadPlugins } = await import('./src/plugin-loader.js');
44const {
27 initUserStorage,45 initUserStorage,
28 getCookieSecret,46 getCookieSecret,
29 getCookieSessionName,47 getCookieSessionName,
@@ -38,17 +56,17 @@ import {
38 getSessionCookieAge,56 getSessionCookieAge,
39 verifySecuritySettings,57 verifySecuritySettings,
40 loginPageMiddleware,58 loginPageMiddleware,
41} from './src/users.js';59} = await import('./src/users.js');
4260
43import getWebpackServeMiddleware from './src/middleware/webpack-serve.js';61const { default: getWebpackServeMiddleware } = await import('./src/middleware/webpack-serve.js');
44import basicAuthMiddleware from './src/middleware/basicAuth.js';62const { default: basicAuthMiddleware } = await import('./src/middleware/basicAuth.js');
45import getWhitelistMiddleware from './src/middleware/whitelist.js';63const { default: getWhitelistMiddleware } = await import('./src/middleware/whitelist.js');
46import accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from './src/middleware/accessLogWriter.js';64const { default: accessLoggerMiddleware, getAccessLogPath, migrateAccessLog } = await import('./src/middleware/accessLogWriter.js');
47import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js';65const { default: multerMonkeyPatch } = await import('./src/middleware/multerMonkeyPatch.js');
48import initRequestProxy from './src/request-proxy.js';66const { default: initRequestProxy } = await import('./src/request-proxy.js');
49import getCacheBusterMiddleware from './src/middleware/cacheBuster.js';67const { default: getCacheBusterMiddleware } = await import('./src/middleware/cacheBuster.js');
50import corsProxyMiddleware from './src/middleware/corsProxy.js';68const { default: corsProxyMiddleware } = await import('./src/middleware/corsProxy.js');
51import {69const {
52 getVersion,70 getVersion,
53 color,71 color,
54 removeColorFormatting,72 removeColorFormatting,
@@ -56,38 +74,23 @@ import {
56 safeReadFileSync,74 safeReadFileSync,
57 setupLogLevel,75 setupLogLevel,
58 setWindowTitle,76 setWindowTitle,
59} from './src/util.js';77} = await import('./src/util.js');
60import { UPLOADS_DIRECTORY } from './src/constants.js';78const { UPLOADS_DIRECTORY } = await import('./src/constants.js');
61import { ensureThumbnailCache } from './src/endpoints/thumbnails.js';79const { ensureThumbnailCache } = await import('./src/endpoints/thumbnails.js');
62import { serverDirectory } from './src/server-directory.js';
6380
64// Routers81// Routers
65import { router as usersPublicRouter } from './src/endpoints/users-public.js';82const { router : usersPublicRouter } = await import('./src/endpoints/users-public.js');
66import { init as statsInit, onExit as statsOnExit } from './src/endpoints/stats.js';83const { init : statsInit, onExit : statsOnExit } = await import('./src/endpoints/stats.js');
67import { checkForNewContent } from './src/endpoints/content-manager.js';84const { checkForNewContent } = await import('./src/endpoints/content-manager.js');
68import { init as settingsInit } from './src/endpoints/settings.js';85const { init : settingsInit } = await import('./src/endpoints/settings.js');
69import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from './src/server-startup.js';86const { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } = await import('./src/server-startup.js');
70import { diskCache } from './src/endpoints/characters.js';87const { diskCache } = await import('./src/endpoints/characters.js');
7188
72// Unrestrict console logs display limit89// Unrestrict console logs display limit
73util.inspect.defaultOptions.maxArrayLength = null;90util.inspect.defaultOptions.maxArrayLength = null;
74util.inspect.defaultOptions.maxStringLength = null;91util.inspect.defaultOptions.maxStringLength = null;
75util.inspect.defaultOptions.depth = 4;92util.inspect.defaultOptions.depth = 4;
7693
77console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`);
78
79// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
80// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
81// Safe to remove once support for Node v20 is dropped.
82if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) {
83 // @ts-ignore
84 if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
85}
86
87const cliArgs = new CommandLineParser().parse(process.argv);
88globalThis.DATA_ROOT = cliArgs.dataRoot;
89globalThis.COMMAND_LINE_ARGS = cliArgs;
90
91if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) {94if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) {
92 console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');95 console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');
93 process.exit(1);96 process.exit(1);
src/command-line.js+11 -1
@@ -1,10 +1,11 @@
1import yargs from 'yargs/yargs';1import yargs from 'yargs/yargs';
2import { hideBin } from 'yargs/helpers';2import { hideBin } from 'yargs/helpers';
3import ipRegex from 'ip-regex';3import ipRegex from 'ip-regex';
4import { canResolve, color, getConfigValue, stringToBool } from './util.js';4import { canResolve, color, getConfigValue, setConfigFilePath, stringToBool } from './util.js';
55
6/**6/**
7 * @typedef {object} CommandLineArguments Parsed command line arguments7 * @typedef {object} CommandLineArguments Parsed command line arguments
8 * @property {string} configPath Path to the config file
8 * @property {string} dataRoot Data root directory9 * @property {string} dataRoot Data root directory
9 * @property {number} port Port number10 * @property {number} port Port number
10 * @property {boolean} listen If SillyTavern is listening on all network interfaces11 * @property {boolean} listen If SillyTavern is listening on all network interfaces
@@ -40,6 +41,7 @@ export class CommandLineParser {
40 constructor() {41 constructor() {
41 /** @type {CommandLineArguments} */42 /** @type {CommandLineArguments} */
42 this.default = Object.freeze({43 this.default = Object.freeze({
44 configPath: './config.yaml',
43 dataRoot: './data',45 dataRoot: './data',
44 port: 8000,46 port: 8000,
45 listen: false,47 listen: false,
@@ -88,6 +90,11 @@ export class CommandLineParser {
88 parse(args) {90 parse(args) {
89 const cliArguments = yargs(hideBin(args))91 const cliArguments = yargs(hideBin(args))
90 .usage('Usage: <your-start-script> [options]\nOptions that are not provided will be filled with config values.')92 .usage('Usage: <your-start-script> [options]\nOptions that are not provided will be filled with config values.')
93 .option('configPath', {
94 type: 'string',
95 default: null,
96 describe: 'Path to the config file',
97 })
91 .option('enableIPv6', {98 .option('enableIPv6', {
92 type: 'string',99 type: 'string',
93 default: null,100 default: null,
@@ -177,8 +184,11 @@ export class CommandLineParser {
177 describe: 'Request proxy bypass list (space separated list of hosts)',184 describe: 'Request proxy bypass list (space separated list of hosts)',
178 }).parseSync();185 }).parseSync();
179186
187 const configPath = cliArguments.configPath ?? this.default.configPath;
188 setConfigFilePath(configPath);
180 /** @type {CommandLineArguments} */189 /** @type {CommandLineArguments} */
181 const result = {190 const result = {
191 configPath: configPath,
182 dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot),192 dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot),
183 port: cliArguments.port ?? getConfigValue('port', this.default.port, 'number'),193 port: cliArguments.port ?? getConfigValue('port', this.default.port, 'number'),
184 listen: cliArguments.listen ?? getConfigValue('listen', this.default.listen, 'boolean'),194 listen: cliArguments.listen ?? getConfigValue('listen', this.default.listen, 'boolean'),
src/util.js+19 -3
@@ -23,6 +23,7 @@ import { serverDirectory } from './server-directory.js';
23 * Parsed config object.23 * Parsed config object.
24 */24 */
25let CACHED_CONFIG = null;25let CACHED_CONFIG = null;
26let CONFIG_FILE = null;
2627
27/**28/**
28 * Converts a configuration key to an environment variable key.29 * Converts a configuration key to an environment variable key.
@@ -33,22 +34,37 @@ let CACHED_CONFIG = null;
33export const keyToEnv = (key) => 'SILLYTAVERN_' + String(key).toUpperCase().replace(/\./g, '_');34export const keyToEnv = (key) => 'SILLYTAVERN_' + String(key).toUpperCase().replace(/\./g, '_');
3435
35/**36/**
37 * Set the config file path.
38 * @param {string} configFilePath Path to the config file
39 */
40export function setConfigFilePath(configFilePath) {
41 if (CONFIG_FILE !== null) {
42 console.error(color.red('Config file path already set. Please restart the server to change the config file path.'));
43 }
44 CONFIG_FILE = path.resolve(configFilePath);
45}
46
47/**
36 * Returns the config object from the config.yaml file.48 * Returns the config object from the config.yaml file.
37 * @returns {object} Config object49 * @returns {object} Config object
38 */50 */
39export function getConfig() {51export function getConfig() {
52 if (CONFIG_FILE === null) {
53 console.trace();
54 console.error(color.red('No config file path set. Please set the config file path using setConfigFilePath().'));
55 process.exit(1);
56 }
40 if (CACHED_CONFIG) {57 if (CACHED_CONFIG) {
41 return CACHED_CONFIG;58 return CACHED_CONFIG;
42 }59 }
4360 if (!fs.existsSync(CONFIG_FILE)) {
44 if (!fs.existsSync('./config.yaml')) {
45 console.error(color.red('No config file found. Please create a config.yaml file. The default config file can be found in the /default folder.'));61 console.error(color.red('No config file found. Please create a config.yaml file. The default config file can be found in the /default folder.'));
46 console.error(color.red('The program will now exit.'));62 console.error(color.red('The program will now exit.'));
47 process.exit(1);63 process.exit(1);
48 }64 }
4965
50 try {66 try {
51 const config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8'));67 const config = yaml.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
52 CACHED_CONFIG = config;68 CACHED_CONFIG = config;
53 return config;69 return config;
54 } catch (error) {70 } catch (error) {