Feat/global install (#4289) * Update npm dependencies * Add global paths mode Closes #4209 * Update src/command-line.js Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Async => Promise.resolve * Remove unnecessary Promise.resolve() --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

5d56992ea832657a148d948a33cf6a08cee34d4d

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
10 files changed, +236 -108Ignore whitespace
.github/readme.md+51 -18
@@ -330,12 +330,44 @@ chmod +x launcher.sh && ./launcher.sh
330330> \[!NOTE]
331331> **SillyTavern can be run natively on Android devices using Termux, but we do not provide official support for this use case.**
332332>
333333> **Please refer to thisthe guidedocumentation byfor ArroganceComplex#2659more information:**
334334>
335335> * <https://rentrydocs.orgsillytavern.app/STAIinstallation/android-Termux(termux)/>
336336
337337**Unsupported platform: android arm LEtime-web.** 32-bit Android requires an external dependency that can't be installed with npm. Use the following command to install it: `pkg install esbuild`. Then run the usual installation steps.
338338
339+## Global / Standalone mode
340+
341+There are two modes of running SillyTavern that differ in how they handle the configuration and data paths.
342+
343+* **Standalone mode** (default) - uses the `config.yaml` file and `data` directory in the server directory. All data will be constrained to the installation path. This is the recommended mode for most users.
344+* **Global mode** - uses the system-wide paths for configuration and data. This is useful for installing SillyTavern as a package or when you want to share the same configuration and data across multiple installations.
345+
346+> [!NOTE]
347+> Installations made by using the [official npm package](https://www.npmjs.com/package/sillytavern) (e.g. `npx sillytavern@latest`) will run in global mode by default.
348+
349+### Data paths
350+
351+**Standalone mode** paths are relative to the SillyTavern installation directory:
352+
353+* **Config path**: `./config.yaml`
354+* **Data root**: `./data/`
355+
356+**Global mode** paths are OS-dependent:
357+
358+* **Linux**: `~/.local/share/SillyTavern/config.yaml` and `~/.local/share/SillyTavern/data/`
359+* **Windows**: `%APPDATA%\SillyTavern\config.yaml` and `%APPDATA%\SillyTavern\data\`
360+* **MacOS**: `~/Library/Application Support/SillyTavern/config.yaml` and `~/Library/Application Support/SillyTavern/data/`
361+
362+### How to run in global mode
363+
364+> [!WARNING]
365+> `dataRoot` and `configPath` can't be overridden with CLI arguments or config.yaml when running in global mode.
366+
367+1. Pass the `--global` argument to the server startup command (e.g. `node server.js --global`).
368+2. Pass the `--global` argument to the shell startup script (e.g. `Start.bat --global` or `./start.sh --global`).
369+3. Use the `start:global` script in the `package.json` file (e.g. `npm run start:global`).
370+
339371## Command-line arguments
340372
341373You can pass command-line arguments to SillyTavern server startup to override some settings in `config.yaml`.
@@ -357,29 +389,30 @@ Start.bat --port 8000 --listen false
357389
358390| Option | Description | Type |
359391|---------------------------------|----------------------------------------------------------------------|----------|
360392| `--version` | ShowShows the version number | boolean |
361393| `--configPathglobal` | OverrideForces the pathuse toof thesystem-wide config.yamlpaths file for application data (see above) | string boolean |
362394| `--dataRootconfigPath` | RootOverrides directorythe forpath datato storage the config.yaml file (standalone mode only) | string |
395+| `--dataRoot` | Sets the root directory for data storage (standalone mode only) | string |
363396| `--port` | Sets the port under which SillyTavern will run | number |
364397| `--listen` | SillyTavernMakes willSillyTavern listen on all network interfaces | boolean |
365398| `--whitelist` | Enables whitelist mode | boolean |
366399| `--basicAuthMode` | Enables basic authentication | boolean |
367400| `--enableIPv4` | Enables the IPv4 protocol | boolean |
368401| `--enableIPv6` | Enables the IPv6 protocol | boolean |
369402| `--listenAddressIPv4` | SpecificSpecifies the IPv4 address to listen to on | string |
370403| `--listenAddressIPv6` | SpecificSpecifies the IPv6 address to listen to on | string |
371404| `--dnsPreferIPv6` | Prefers IPv6 for DNS | boolean |
372405| `--ssl` | Enables SSL | boolean |
373406| `--certPath` | PathSets the path to your certificate file | string |
374407| `--keyPath` | PathSets the path to your private key file | string |
375408| `--browserLaunchEnabled` | Automatically launchlaunches SillyTavern in the browser | boolean |
376409| `--browserLaunchHostname` | BrowserSets the browser launch hostname | string |
377410| `--browserLaunchPort` | Overrides the port for browser launch | string |
378411| `--browserLaunchAvoidLocalhost` | Avoids using 'localhost' for browser launch in auto mode | boolean |
379412| `--corsProxy` | Enables the CORS proxy | boolean |
380413| `--requestProxyEnabled` | Enables athe use of a proxy for outgoing requests | boolean |
381414| `--requestProxyUrl` | RequestSets the request proxy URL (HTTP or SOCKS protocols) | string |
382415| `--requestProxyBypass` | RequestSets the request proxy bypass list (space -separated list of hosts) | array |
383416| `--disableCsrf` | Disables CSRF protection (NOT RECOMMENDED) | boolean |
384417
385418## Remote connections
index.d.ts+5 -0
@@ -66,4 +66,9 @@ declare global {
6666 * Parsed command line arguments.
6767 */
6868 var COMMAND_LINE_ARGS: CommandLineArguments;
69+
70+ /**
71+ * Forces a global mode if set to `true` before parsing the CLI arguments.
72+ */
73+ var FORCE_GLOBAL_MODE: boolean;
6974}
package-lock.json+54 -26
@@ -40,7 +40,7 @@
4040 "@popperjs/core": "^2.11.8",
4141 "@zeldafan0225/ai_horde": "^5.2.0",
4242 "archiver": "^7.0.1",
4343 "bing-translate-api": "^4.01.20",
4444 "body-parser": "^1.20.2",
4545 "bowser": "^2.11.0",
4646 "bytes": "^3.1.2",
@@ -55,8 +55,9 @@
5555 "diff-match-patch": "^1.0.5",
5656 "dompurify": "^3.2.6",
5757 "droll": "^0.2.1",
58+ "env-paths": "^3.0.0",
5859 "express": "^4.21.0",
5960 "form-data": "^4.0.34",
6061 "fuse.js": "^7.1.0",
6162 "google-translate-api-browser": "^3.0.1",
6263 "google-translate-api-x": "^10.7.2",
@@ -77,7 +78,7 @@
7778 "multer": "^2.0.2",
7879 "node-fetch": "^3.3.2",
7980 "node-persist": "^4.0.4",
8081 "open": "^10.1.2.0",
8182 "png-chunk-text": "^1.0.0",
8283 "png-chunks-extract": "^1.0.0",
8384 "proxy-agent": "^6.5.0",
@@ -95,13 +96,13 @@
9596 "wavefile": "^11.0.0",
9697 "webpack": "^5.98.0",
9798 "write-file-atomic": "^5.0.1",
9899 "ws": "^8.18.23",
99100 "yaml": "^2.8.0",
100101 "yargs": "^17.7.1",
101102 "yauzl": "^3.2.0"
102103 },
103104 "bin": {
104105 "sillytavern": "src/server-global.js"
105106 },
106107 "devDependencies": {
107108 "@types/archiver": "^6.0.3",
@@ -117,9 +118,9 @@
117118 "@types/jquery-cropper": "^1.0.4",
118119 "@types/jquery.transit": "^0.9.33",
119120 "@types/jqueryui": "^1.12.24",
120121 "@types/lodash": "^4.17.1720",
121122 "@types/mime-types": "^3.0.1",
122123 "@types/multer": "^12.40.130",
123124 "@types/node": "^18.19.84",
124125 "@types/node-persist": "^3.1.8",
125126 "@types/png-chunk-text": "^1.0.3",
@@ -1972,9 +1973,9 @@
19721973 }
19731974 },
19741975 "node_modules/@types/lodash": {
19751976 "version": "4.17.1720",
19761977 "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.1720.tgz",
19771978 "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNoH3MHACvFUEiujabxhaI/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
19781979 "dev": true,
19791980 "license": "MIT"
19801981 },
@@ -1999,9 +2000,9 @@
19992000 "license": "MIT"
20002001 },
20012002 "node_modules/@types/multer": {
20022003 "version": "12.40.130",
20032004 "resolved": "https://registry.npmjs.org/@types/multer/-/multer-12.40.130.tgz",
20042005 "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcawC3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==",
20052006 "dev": true,
20062007 "license": "MIT",
20072008 "dependencies": {
@@ -2832,9 +2833,9 @@
28322833 }
28332834 },
28342835 "node_modules/bing-translate-api": {
28352836 "version": "4.01.20",
28362837 "resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-4.01.20.tgz",
28372838 "integrity": "sha512-JJ8XUehnxzOhHU91oy86xEtp8OOMjVEjCZJX042fKxoO19NNvxJ5omeCcxQNFoPbDqVpBJwqiGVquL0oPdQm1QoP2663Yd5MXX4kbB/3LdS9YgPiE+ls9+2iFZH2ZXigWhWyHT3R4m6aCup4TNJd3/U4gqHHnQoxTaIW7uOf4+vA==",
28382839 "license": "MIT",
28392840 "dependencies": {
28402841 "got": "^11.8.6"
@@ -4087,6 +4088,18 @@
40874088 "url": "https://github.com/fb55/entities?sponsor=1"
40884089 }
40894090 },
4091+ "node_modules/env-paths": {
4092+ "version": "3.0.0",
4093+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
4094+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
4095+ "license": "MIT",
4096+ "engines": {
4097+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
4098+ },
4099+ "funding": {
4100+ "url": "https://github.com/sponsors/sindresorhus"
4101+ }
4102+ },
40904103 "node_modules/es-define-property": {
40914104 "version": "1.0.1",
40924105 "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -4786,9 +4799,9 @@
47864799 }
47874800 },
47884801 "node_modules/form-data": {
47894802 "version": "4.0.34",
47904803 "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.34.tgz",
47914804 "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwuKrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA4IlGTMF0Ow==",
47924805 "license": "MIT",
47934806 "dependencies": {
47944807 "asynckit": "^0.4.0",
@@ -6420,15 +6433,15 @@
64206433 }
64216434 },
64226435 "node_modules/open": {
64236436 "version": "10.1.2.0",
64246437 "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.0.tgz",
64256438 "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oImYgBpdJHPyQ2UE5x+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXwhlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
64266439 "license": "MIT",
64276440 "dependencies": {
64286441 "default-browser": "^5.2.1",
64296442 "define-lazy-prop": "^3.0.0",
64306443 "is-inside-container": "^1.0.0",
64316444 "is-wsl-utils": "^30.1.0"
64326445 },
64336446 "engines": {
64346447 "node": ">=18"
@@ -8503,9 +8516,9 @@
85038516 }
85048517 },
85058518 "node_modules/ws": {
85068519 "version": "8.18.23",
85078520 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.23.tgz",
85088521 "integrity": "sha512-DMricUmwGZUVr+PEIGCY5tSlUt50cqyMXfCzX+AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lpoOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQGARUDw2XV2N4tvzg==",
85098522 "license": "MIT",
85108523 "engines": {
85118524 "node": ">=10.0.0"
@@ -8523,6 +8536,21 @@
85238536 }
85248537 }
85258538 },
8539+ "node_modules/wsl-utils": {
8540+ "version": "0.1.0",
8541+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
8542+ "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
8543+ "license": "MIT",
8544+ "dependencies": {
8545+ "is-wsl": "^3.1.0"
8546+ },
8547+ "engines": {
8548+ "node": ">=18"
8549+ },
8550+ "funding": {
8551+ "url": "https://github.com/sponsors/sindresorhus"
8552+ }
8553+ },
85268554 "node_modules/xhr": {
85278555 "version": "2.6.0",
85288556 "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
package.json+9 -7
@@ -30,7 +30,7 @@
3030 "@popperjs/core": "^2.11.8",
3131 "@zeldafan0225/ai_horde": "^5.2.0",
3232 "archiver": "^7.0.1",
3333 "bing-translate-api": "^4.01.20",
3434 "body-parser": "^1.20.2",
3535 "bowser": "^2.11.0",
3636 "bytes": "^3.1.2",
@@ -45,8 +45,9 @@
4545 "diff-match-patch": "^1.0.5",
4646 "dompurify": "^3.2.6",
4747 "droll": "^0.2.1",
48+ "env-paths": "^3.0.0",
4849 "express": "^4.21.0",
4950 "form-data": "^4.0.34",
5051 "fuse.js": "^7.1.0",
5152 "google-translate-api-browser": "^3.0.1",
5253 "google-translate-api-x": "^10.7.2",
@@ -67,7 +68,7 @@
6768 "multer": "^2.0.2",
6869 "node-fetch": "^3.3.2",
6970 "node-persist": "^4.0.4",
7071 "open": "^10.1.2.0",
7172 "png-chunk-text": "^1.0.0",
7273 "png-chunks-extract": "^1.0.0",
7374 "proxy-agent": "^6.5.0",
@@ -85,7 +86,7 @@
8586 "wavefile": "^11.0.0",
8687 "webpack": "^5.98.0",
8788 "write-file-atomic": "^5.0.1",
8889 "ws": "^8.18.23",
8990 "yaml": "^2.8.0",
9091 "yargs": "^17.7.1",
9192 "yauzl": "^3.2.0"
@@ -115,6 +116,7 @@
115116 "scripts": {
116117 "start": "node server.js",
117118 "debug": "node --inspect server.js",
119+ "start:global": "node server.js --global",
118120 "start:electron": "cd ./src/electron && npm run start",
119121 "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
120122 "start:bun": "bun server.js",
@@ -126,7 +128,7 @@
126128 "plugins:install": "node plugins install"
127129 },
128130 "bin": {
129131 "sillytavern": "./src/server-global.js"
130132 },
131133 "rules": {
132134 "no-path-concat": "off",
@@ -147,9 +149,9 @@
147149 "@types/jquery-cropper": "^1.0.4",
148150 "@types/jquery.transit": "^0.9.33",
149151 "@types/jqueryui": "^1.12.24",
150152 "@types/lodash": "^4.17.1720",
151153 "@types/mime-types": "^3.0.1",
152154 "@types/multer": "^12.40.130",
153155 "@types/node": "^18.19.84",
154156 "@types/node-persist": "^3.1.8",
155157 "@types/png-chunk-text": "^1.0.3",
server.js+2 -0
@@ -2,6 +2,8 @@
22import { CommandLineParser } from './src/command-line.js';
33import { serverDirectory } from './src/server-directory.js';
44
5+console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`);
6+
57// config.yaml will be set when parsing command line arguments
68const cliArgs = new CommandLineParser().parse(process.argv);
79globalThis.DATA_ROOT = cliArgs.dataRoot;
src/command-line.js+75 -34
@@ -1,6 +1,9 @@
1+import fs from 'node:fs';
2+import path from 'node:path';
13import yargs from 'yargs/yargs';
24import { hideBin } from 'yargs/helpers';
35import ipRegex from 'ip-regex';
6+import envPaths from 'env-paths';
47import { canResolve, color, getConfigValue, stringToBool } from './util.js';
58import { initConfig } from './config-init.js';
69
@@ -39,11 +42,18 @@ import { initConfig } from './config-init.js';
3942 * Provides a command line arguments parser.
4043 */
4144export class CommandLineParser {
42- constructor() {
45+ /**
43- /** @type {CommandLineArguments} */
46+ * Gets the default configuration values.
44- this.default = Object.freeze({
47+ * @param {boolean} isGlobal If the configuration is global or not
45- configPath: './config.yaml',
48+ * @returns {CommandLineArguments} Default configuration values
46- dataRoot: './data',
49+ */
50+ getDefaultConfig(isGlobal) {
51+ const appPaths = envPaths('SillyTavern', { suffix: '' });
52+ const configPath = isGlobal ? path.join(appPaths.data, 'config.yaml') : './config.yaml';
53+ const dataPath = isGlobal ? path.join(appPaths.data, 'data') : './data';
54+ return Object.freeze({
55+ configPath: configPath,
56+ dataRoot: dataPath,
4757 port: 8000,
4858 listen: false,
4959 listenAddressIPv6: '[::]',
@@ -78,7 +88,9 @@ export class CommandLineParser {
7888 throw new Error('getBrowserLaunchUrl is not implemented');
7989 },
8090 });
91+ }
8192
93+ constructor() {
8294 this.booleanAutoOptions = [true, false, 'auto'];
8395 }
8496
@@ -91,10 +103,15 @@ export class CommandLineParser {
91103 parse(args) {
92104 const cliArguments = yargs(hideBin(args))
93105 .usage('Usage: <your-start-script> [options]\nOptions that are not provided will be filled with config values.')
106+ .option('global', {
107+ type: 'boolean',
108+ default: null,
109+ describe: 'Use global data and config paths instead of the server directory',
110+ })
94111 .option('configPath', {
95112 type: 'string',
96113 default: null,
97114 describe: 'Path to the config file (only for standalone mode)',
98115 })
99116 .option('enableIPv6', {
100117 type: 'string',
@@ -184,7 +201,7 @@ export class CommandLineParser {
184201 .option('dataRoot', {
185202 type: 'string',
186203 default: null,
187204 describe: 'Root directory for data storage (only for standalone mode)',
188205 })
189206 .option('basicAuthMode', {
190207 type: 'boolean',
@@ -228,33 +245,57 @@ export class CommandLineParser {
228245 })
229246 .parseSync();
230247
231248 const configPathisGlobal = cliArgumentsglobalThis.configPathFORCE_GLOBAL_MODE ?? this.defaultcliArguments.configPathglobal ?? false;
249+ const defaultConfig = this.getDefaultConfig(isGlobal);
250+
251+ if (isGlobal && cliArguments.configPath) {
252+ console.warn(color.yellow('Warning: "--configPath" argument is ignored in global mode'));
253+ }
254+
255+ if (isGlobal && cliArguments.dataRoot) {
256+ console.warn(color.yellow('Warning: "--dataRoot" argument is ignored in global mode'));
257+ }
258+
259+ const configPath = isGlobal
260+ ? defaultConfig.configPath
261+ : (cliArguments.configPath ?? defaultConfig.configPath);
262+ if (isGlobal && !fs.existsSync(path.dirname(configPath))) {
263+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
264+ }
232265 initConfig(configPath);
266+
267+ const dataRoot = isGlobal
268+ ? defaultConfig.dataRoot
269+ : (cliArguments.dataRoot ?? getConfigValue('dataRoot', defaultConfig.dataRoot));
270+ if (isGlobal && !fs.existsSync(dataRoot)) {
271+ fs.mkdirSync(dataRoot, { recursive: true });
272+ }
273+
233274 /** @type {CommandLineArguments} */
234275 const result = {
235276 configPath: configPath,
236- dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot),
277+ dataRoot: dataRoot,
237278 port: cliArguments.port ?? getConfigValue('port', this.defaultdefaultConfig.port, 'number'),
238279 listen: cliArguments.listen ?? getConfigValue('listen', this.defaultdefaultConfig.listen, 'boolean'),
239280 listenAddressIPv6: cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', this.defaultdefaultConfig.listenAddressIPv6),
240281 listenAddressIPv4: cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', this.defaultdefaultConfig.listenAddressIPv4),
241282 enableIPv4: stringToBool(cliArguments.enableIPv4) ?? stringToBool(getConfigValue('protocol.ipv4', this.defaultdefaultConfig.enableIPv4)) ?? this.defaultdefaultConfig.enableIPv4,
242283 enableIPv6: stringToBool(cliArguments.enableIPv6) ?? stringToBool(getConfigValue('protocol.ipv6', this.defaultdefaultConfig.enableIPv6)) ?? this.defaultdefaultConfig.enableIPv6,
243284 dnsPreferIPv6: cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', this.defaultdefaultConfig.dnsPreferIPv6, 'boolean'),
244285 browserLaunchEnabled: cliArguments.browserLaunchEnabled ?? cliArguments.autorun ?? getConfigValue('browserLaunch.enabled', this.defaultdefaultConfig.browserLaunchEnabled, 'boolean'),
245286 browserLaunchHostname: cliArguments.browserLaunchHostname ?? cliArguments.autorunHostname ?? getConfigValue('browserLaunch.hostname', this.defaultdefaultConfig.browserLaunchHostname),
246287 browserLaunchPort: cliArguments.browserLaunchPort ?? cliArguments.autorunPortOverride ?? getConfigValue('browserLaunch.port', this.defaultdefaultConfig.browserLaunchPort, 'number'),
247288 browserLaunchAvoidLocalhost: cliArguments.browserLaunchAvoidLocalhost ?? cliArguments.avoidLocalhost ?? getConfigValue('browserLaunch.avoidLocalhost', this.defaultdefaultConfig.browserLaunchAvoidLocalhost, 'boolean'),
248289 enableCorsProxy: cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', this.defaultdefaultConfig.enableCorsProxy, 'boolean'),
249290 disableCsrf: cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', this.defaultdefaultConfig.disableCsrf, 'boolean'),
250291 ssl: cliArguments.ssl ?? getConfigValue('ssl.enabled', this.defaultdefaultConfig.ssl, 'boolean'),
251292 certPath: cliArguments.certPath ?? getConfigValue('ssl.certPath', this.defaultdefaultConfig.certPath),
252293 keyPath: cliArguments.keyPath ?? getConfigValue('ssl.keyPath', this.defaultdefaultConfig.keyPath),
253294 whitelistMode: cliArguments.whitelist ?? getConfigValue('whitelistMode', this.defaultdefaultConfig.whitelistMode, 'boolean'),
254295 basicAuthMode: cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', this.defaultdefaultConfig.basicAuthMode, 'boolean'),
255296 requestProxyEnabled: cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', this.defaultdefaultConfig.requestProxyEnabled, 'boolean'),
256297 requestProxyUrl: cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', this.defaultdefaultConfig.requestProxyUrl),
257298 requestProxyBypass: cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', this.defaultdefaultConfig.requestProxyBypass),
258299 getIPv4ListenUrl: function () {
259300 const isValid = ipRegex.v4({ exact: true }).test(this.listenAddressIPv4);
260301 return new URL(
@@ -302,13 +343,13 @@ export class CommandLineParser {
302343 };
303344
304345 if (!this.booleanAutoOptions.includes(result.enableIPv6)) {
305346 console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', this.booleanAutoOptions, '\n setting to:', this.defaultdefaultConfig.enableIPv6);
306347 result.enableIPv6 = this.defaultdefaultConfig.enableIPv6;
307348 }
308349
309350 if (!this.booleanAutoOptions.includes(result.enableIPv4)) {
310351 console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', this.booleanAutoOptions, '\n setting to:', this.defaultdefaultConfig.enableIPv4);
311352 result.enableIPv4 = this.defaultdefaultConfig.enableIPv4;
312353 }
313354
314355 return result;
src/config-init.js+8 -0
@@ -148,6 +148,13 @@ function getAllKeys(obj, prefix = '') {
148148export function addMissingConfigValues(configPath) {
149149 try {
150150 const defaultConfig = yaml.parse(fs.readFileSync(path.join(serverDirectory, './default/config.yaml'), 'utf8'));
151+
152+ if (!fs.existsSync(configPath)) {
153+ console.warn(color.yellow(`Warning: config.yaml not found at ${configPath}. Creating a new one with default values.`));
154+ fs.writeFileSync(configPath, yaml.stringify(defaultConfig));
155+ return;
156+ }
157+
151158 let config = yaml.parse(fs.readFileSync(configPath, 'utf8'));
152159
153160 // Migrate old keys to new keys
@@ -224,6 +231,7 @@ export function addMissingConfigValues(configPath) {
224231 * @param {string} configPath Path to config.yaml
225232 */
226233export function initConfig(configPath) {
234+ console.log('Using config path:', color.green(configPath));
227235 setConfigFilePath(configPath);
228236 addMissingConfigValues(configPath);
229237}
src/electron/index.js+1 -1
@@ -41,7 +41,7 @@ function startServer() {
4141 const sillyTavernRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
4242 process.chdir(sillyTavernRoot);
4343
4444 import('../../server-global.js');
4545 });
4646}
4747
src/server-global.js+5 -0
@@ -0,0 +1,5 @@
1+#!/usr/bin/env node
2+globalThis.FORCE_GLOBAL_MODE = true;
3+await import('../server.js');
4+
5+export {};
src/server-main.js+26 -22
@@ -19,16 +19,6 @@ import bodyParser from 'body-parser';
1919import './fetch-patch.js';
2020import { serverDirectory } from './server-directory.js';
2121
22-console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`);
23-
24-// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
25-// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
26-// Safe to remove once support for Node v20 is dropped.
27-if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) {
28- // @ts-ignore
29- if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
30-}
31-
3222import { serverEvents, EVENT_NAMES } from './server-events.js';
3323import { loadPlugins } from './plugin-loader.js';
3424import {
@@ -78,6 +68,14 @@ import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } fro
7868import { diskCache } from './endpoints/characters.js';
7969import { migrateFlatSecrets } from './endpoints/secrets.js';
8070
71+// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
72+// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
73+// Safe to remove once support for Node v20 is dropped.
74+if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) {
75+ // @ts-ignore
76+ if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
77+}
78+
8179// Unrestrict console logs display limit
8280util.inspect.defaultOptions.maxArrayLength = null;
8381util.inspect.defaultOptions.maxStringLength = null;
@@ -91,18 +89,6 @@ if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) {
9189 process.exit(1);
9290}
9391
94-try {
95- if (cliArgs.dnsPreferIPv6) {
96- dns.setDefaultResultOrder('ipv6first');
97- console.log('Preferring IPv6 for DNS resolution');
98- } else {
99- dns.setDefaultResultOrder('ipv4first');
100- console.log('Preferring IPv4 for DNS resolution');
101- }
102-} catch (error) {
103- console.warn('Failed to set DNS resolution order. Possibly unsupported in this Node version.');
104-}
105-
10692const app = express();
10793app.use(helmet({
10894 contentSecurityPolicy: false,
@@ -400,8 +386,26 @@ function apply404Middleware() {
400386 });
401387}
402388
389+/**
390+ * Sets the DNS resolution order based on the command line arguments.
391+ */
392+function setDnsResolutionOrder() {
393+ try {
394+ if (cliArgs.dnsPreferIPv6) {
395+ dns.setDefaultResultOrder('ipv6first');
396+ console.log('Preferring IPv6 for DNS resolution');
397+ } else {
398+ dns.setDefaultResultOrder('ipv4first');
399+ console.log('Preferring IPv4 for DNS resolution');
400+ }
401+ } catch (error) {
402+ console.warn('Failed to set DNS resolution order. Possibly unsupported in this Node version.');
403+ }
404+}
405+
403406// User storage module needs to be initialized before starting the server
404407initUserStorage(globalThis.DATA_ROOT)
408+ .then(setDnsResolutionOrder)
405409 .then(ensurePublicDirectoriesExist)
406410 .then(migrateUserData)
407411 .then(migrateSystemPrompts)