| @@ -20,10 +20,28 @@ import open from 'open'; | ||
| 20 | 20 | |
| 21 | 21 | // local library imports |
| 22 | 22 | import './src/fetch-patch.js'; |
| 23 | -import { serverEvents, EVENT_NAMES } from './src/server-events.js'; | |
| 24 | 23 | import { CommandLineParser } from './src/command-line.js'; |
| 25 | 24 | import { loadPluginsserverDirectory } from './src/pluginserver-loaderdirectory.js'; |
| 26 | -import { | |
| 25 | + | |
| 26 | +console.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. | |
| 31 | +if (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 | |
| 37 | +const cliArgs = new CommandLineParser().parse(process.argv); | |
| 38 | +globalThis.DATA_ROOT = cliArgs.dataRoot; | |
| 39 | +globalThis.COMMAND_LINE_ARGS = cliArgs; | |
| 40 | +process.chdir(serverDirectory); | |
| 41 | + | |
| 42 | +const { serverEvents, EVENT_NAMES } = await import('./src/server-events.js'); | |
| 43 | +const { loadPlugins } = await import('./src/plugin-loader.js'); | |
| 44 | +const { | |
| 27 | 45 | initUserStorage, |
| 28 | 46 | getCookieSecret, |
| 29 | 47 | getCookieSessionName, |
| @@ -38,17 +56,17 @@ import { | ||
| 38 | 56 | getSessionCookieAge, |
| 39 | 57 | verifySecuritySettings, |
| 40 | 58 | loginPageMiddleware, |
| 41 | 59 | } from= await import('./src/users.js'); |
| 42 | 60 | |
| 43 | 61 | importconst { default: getWebpackServeMiddleware from} = await import('./src/middleware/webpack-serve.js'); |
| 44 | 62 | importconst { default: basicAuthMiddleware from} = await import('./src/middleware/basicAuth.js'); |
| 45 | 63 | importconst { default: getWhitelistMiddleware from} = await import('./src/middleware/whitelist.js'); |
| 46 | 64 | importconst { default: accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from= await import('./src/middleware/accessLogWriter.js'); |
| 47 | 65 | importconst { default: multerMonkeyPatch from} = await import('./src/middleware/multerMonkeyPatch.js'); |
| 48 | 66 | importconst { default: initRequestProxy from} = await import('./src/request-proxy.js'); |
| 49 | 67 | importconst { default: getCacheBusterMiddleware from} = await import('./src/middleware/cacheBuster.js'); |
| 50 | 68 | importconst { default: corsProxyMiddleware from} = await import('./src/middleware/corsProxy.js'); |
| 51 | 69 | importconst { |
| 52 | 70 | getVersion, |
| 53 | 71 | color, |
| 54 | 72 | removeColorFormatting, |
| @@ -56,38 +74,23 @@ import { | ||
| 56 | 74 | safeReadFileSync, |
| 57 | 75 | setupLogLevel, |
| 58 | 76 | setWindowTitle, |
| 59 | 77 | } from= await import('./src/util.js'); |
| 60 | 78 | importconst { UPLOADS_DIRECTORY } from= await import('./src/constants.js'); |
| 61 | 79 | importconst { ensureThumbnailCache } from= await import('./src/endpoints/thumbnails.js'); |
| 62 | -import { serverDirectory } from './src/server-directory.js'; | |
| 63 | 80 | |
| 64 | 81 | // Routers |
| 65 | 82 | importconst { router as: usersPublicRouter } from= await import('./src/endpoints/users-public.js'); |
| 66 | 83 | importconst { init as: statsInit, onExit as: statsOnExit } from= await import('./src/endpoints/stats.js'); |
| 67 | 84 | importconst { checkForNewContent } from= await import('./src/endpoints/content-manager.js'); |
| 68 | 85 | importconst { init as: settingsInit } from= await import('./src/endpoints/settings.js'); |
| 69 | 86 | importconst { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } from= await import('./src/server-startup.js'); |
| 70 | 87 | importconst { diskCache } from= await import('./src/endpoints/characters.js'); |
| 71 | 88 | |
| 72 | 89 | // Unrestrict console logs display limit |
| 73 | 90 | util.inspect.defaultOptions.maxArrayLength = null; |
| 74 | 91 | util.inspect.defaultOptions.maxStringLength = null; |
| 75 | 92 | util.inspect.defaultOptions.depth = 4; |
| 76 | 93 | |
| 77 | -console.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. | |
| 82 | -if (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 | - | |
| 87 | -const cliArgs = new CommandLineParser().parse(process.argv); | |
| 88 | -globalThis.DATA_ROOT = cliArgs.dataRoot; | |
| 89 | -globalThis.COMMAND_LINE_ARGS = cliArgs; | |
| 90 | - | |
| 91 | 94 | if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) { |
| 92 | 95 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); |
| 93 | 96 | process.exit(1); |
| @@ -1,10 +1,11 @@ | ||
| 1 | 1 | import yargs from 'yargs/yargs'; |
| 2 | 2 | import { hideBin } from 'yargs/helpers'; |
| 3 | 3 | import ipRegex from 'ip-regex'; |
| 4 | 4 | import { canResolve, color, getConfigValue, setConfigFilePath, stringToBool } from './util.js'; |
| 5 | 5 | |
| 6 | 6 | /** |
| 7 | 7 | * @typedef {object} CommandLineArguments Parsed command line arguments |
| 8 | + * @property {string} configPath Path to the config file | |
| 8 | 9 | * @property {string} dataRoot Data root directory |
| 9 | 10 | * @property {number} port Port number |
| 10 | 11 | * @property {boolean} listen If SillyTavern is listening on all network interfaces |
| @@ -40,6 +41,7 @@ export class CommandLineParser { | ||
| 40 | 41 | constructor() { |
| 41 | 42 | /** @type {CommandLineArguments} */ |
| 42 | 43 | this.default = Object.freeze({ |
| 44 | + configPath: './config.yaml', | |
| 43 | 45 | dataRoot: './data', |
| 44 | 46 | port: 8000, |
| 45 | 47 | listen: false, |
| @@ -88,6 +90,11 @@ export class CommandLineParser { | ||
| 88 | 90 | parse(args) { |
| 89 | 91 | const cliArguments = yargs(hideBin(args)) |
| 90 | 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 | 98 | .option('enableIPv6', { |
| 92 | 99 | type: 'string', |
| 93 | 100 | default: null, |
| @@ -177,8 +184,11 @@ export class CommandLineParser { | ||
| 177 | 184 | describe: 'Request proxy bypass list (space separated list of hosts)', |
| 178 | 185 | }).parseSync(); |
| 179 | 186 | |
| 187 | + const configPath = cliArguments.configPath ?? this.default.configPath; | |
| 188 | + setConfigFilePath(configPath); | |
| 180 | 189 | /** @type {CommandLineArguments} */ |
| 181 | 190 | const result = { |
| 191 | + configPath: configPath, | |
| 182 | 192 | dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot), |
| 183 | 193 | port: cliArguments.port ?? getConfigValue('port', this.default.port, 'number'), |
| 184 | 194 | listen: cliArguments.listen ?? getConfigValue('listen', this.default.listen, 'boolean'), |
| @@ -23,6 +23,7 @@ import { serverDirectory } from './server-directory.js'; | ||
| 23 | 23 | * Parsed config object. |
| 24 | 24 | */ |
| 25 | 25 | let CACHED_CONFIG = null; |
| 26 | +let CONFIG_FILE = null; | |
| 26 | 27 | |
| 27 | 28 | /** |
| 28 | 29 | * Converts a configuration key to an environment variable key. |
| @@ -33,22 +34,37 @@ let CACHED_CONFIG = null; | ||
| 33 | 34 | export const keyToEnv = (key) => 'SILLYTAVERN_' + String(key).toUpperCase().replace(/\./g, '_'); |
| 34 | 35 | |
| 35 | 36 | /** |
| 37 | + * Set the config file path. | |
| 38 | + * @param {string} configFilePath Path to the config file | |
| 39 | + */ | |
| 40 | +export 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 | 48 | * Returns the config object from the config.yaml file. |
| 37 | 49 | * @returns {object} Config object |
| 38 | 50 | */ |
| 39 | 51 | export 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 | 57 | if (CACHED_CONFIG) { |
| 41 | 58 | return CACHED_CONFIG; |
| 42 | 59 | } |
| 43 | - | |
| 60 | + if (!fs.existsSync(CONFIG_FILE)) { | |
| 44 | - if (!fs.existsSync('./config.yaml')) { | |
| 45 | 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 | 62 | console.error(color.red('The program will now exit.')); |
| 47 | 63 | process.exit(1); |
| 48 | 64 | } |
| 49 | 65 | |
| 50 | 66 | try { |
| 51 | 67 | const config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml')CONFIG_FILE, 'utf8')); |
| 52 | 68 | CACHED_CONFIG = config; |
| 53 | 69 | return config; |
| 54 | 70 | } catch (error) { |