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>
This commit is contained in:
+51
-18
@@ -330,12 +330,44 @@ chmod +x launcher.sh && ./launcher.sh
|
||||
> \[!NOTE]
|
||||
> **SillyTavern can be run natively on Android devices using Termux, but we do not provide official support for this use case.**
|
||||
>
|
||||
> **Please refer to this guide by ArroganceComplex#2659:**
|
||||
> **Please refer to the documentation for more information:**
|
||||
>
|
||||
> * <https://rentry.org/STAI-Termux>
|
||||
> * <https://docs.sillytavern.app/installation/android-(termux)/>
|
||||
|
||||
**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.
|
||||
|
||||
## Global / Standalone mode
|
||||
|
||||
There are two modes of running SillyTavern that differ in how they handle the configuration and data paths.
|
||||
|
||||
* **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.
|
||||
* **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.
|
||||
|
||||
> [!NOTE]
|
||||
> 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.
|
||||
|
||||
### Data paths
|
||||
|
||||
**Standalone mode** paths are relative to the SillyTavern installation directory:
|
||||
|
||||
* **Config path**: `./config.yaml`
|
||||
* **Data root**: `./data/`
|
||||
|
||||
**Global mode** paths are OS-dependent:
|
||||
|
||||
* **Linux**: `~/.local/share/SillyTavern/config.yaml` and `~/.local/share/SillyTavern/data/`
|
||||
* **Windows**: `%APPDATA%\SillyTavern\config.yaml` and `%APPDATA%\SillyTavern\data\`
|
||||
* **MacOS**: `~/Library/Application Support/SillyTavern/config.yaml` and `~/Library/Application Support/SillyTavern/data/`
|
||||
|
||||
### How to run in global mode
|
||||
|
||||
> [!WARNING]
|
||||
> `dataRoot` and `configPath` can't be overridden with CLI arguments or config.yaml when running in global mode.
|
||||
|
||||
1. Pass the `--global` argument to the server startup command (e.g. `node server.js --global`).
|
||||
2. Pass the `--global` argument to the shell startup script (e.g. `Start.bat --global` or `./start.sh --global`).
|
||||
3. Use the `start:global` script in the `package.json` file (e.g. `npm run start:global`).
|
||||
|
||||
## Command-line arguments
|
||||
|
||||
You 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
|
||||
|
||||
| Option | Description | Type |
|
||||
|---------------------------------|----------------------------------------------------------------------|----------|
|
||||
| `--version` | Show version number | boolean |
|
||||
| `--configPath` | Override the path to the config.yaml file | string |
|
||||
| `--dataRoot` | Root directory for data storage | string |
|
||||
| `--version` | Shows the version number | boolean |
|
||||
| `--global` | Forces the use of system-wide paths for application data (see above) | boolean |
|
||||
| `--configPath` | Overrides the path to the config.yaml file (standalone mode only) | string |
|
||||
| `--dataRoot` | Sets the root directory for data storage (standalone mode only) | string |
|
||||
| `--port` | Sets the port under which SillyTavern will run | number |
|
||||
| `--listen` | SillyTavern will listen on all network interfaces | boolean |
|
||||
| `--listen` | Makes SillyTavern listen on all network interfaces | boolean |
|
||||
| `--whitelist` | Enables whitelist mode | boolean |
|
||||
| `--basicAuthMode` | Enables basic authentication | boolean |
|
||||
| `--enableIPv4` | Enables IPv4 protocol | boolean |
|
||||
| `--enableIPv6` | Enables IPv6 protocol | boolean |
|
||||
| `--listenAddressIPv4` | Specific IPv4 address to listen to | string |
|
||||
| `--listenAddressIPv6` | Specific IPv6 address to listen to | string |
|
||||
| `--enableIPv4` | Enables the IPv4 protocol | boolean |
|
||||
| `--enableIPv6` | Enables the IPv6 protocol | boolean |
|
||||
| `--listenAddressIPv4` | Specifies the IPv4 address to listen on | string |
|
||||
| `--listenAddressIPv6` | Specifies the IPv6 address to listen on | string |
|
||||
| `--dnsPreferIPv6` | Prefers IPv6 for DNS | boolean |
|
||||
| `--ssl` | Enables SSL | boolean |
|
||||
| `--certPath` | Path to your certificate file | string |
|
||||
| `--keyPath` | Path to your private key file | string |
|
||||
| `--browserLaunchEnabled` | Automatically launch SillyTavern in the browser | boolean |
|
||||
| `--browserLaunchHostname` | Browser launch hostname | string |
|
||||
| `--certPath` | Sets the path to your certificate file | string |
|
||||
| `--keyPath` | Sets the path to your private key file | string |
|
||||
| `--browserLaunchEnabled` | Automatically launches SillyTavern in the browser | boolean |
|
||||
| `--browserLaunchHostname` | Sets the browser launch hostname | string |
|
||||
| `--browserLaunchPort` | Overrides the port for browser launch | string |
|
||||
| `--browserLaunchAvoidLocalhost` | Avoids using 'localhost' for browser launch in auto mode | boolean |
|
||||
| `--corsProxy` | Enables CORS proxy | boolean |
|
||||
| `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean |
|
||||
| `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string |
|
||||
| `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array |
|
||||
| `--corsProxy` | Enables the CORS proxy | boolean |
|
||||
| `--requestProxyEnabled` | Enables the use of a proxy for outgoing requests | boolean |
|
||||
| `--requestProxyUrl` | Sets the request proxy URL (HTTP or SOCKS protocols) | string |
|
||||
| `--requestProxyBypass` | Sets the request proxy bypass list (space-separated list of hosts) | array |
|
||||
| `--disableCsrf` | Disables CSRF protection (NOT RECOMMENDED) | boolean |
|
||||
|
||||
## Remote connections
|
||||
|
||||
Vendored
+5
@@ -66,4 +66,9 @@ declare global {
|
||||
* Parsed command line arguments.
|
||||
*/
|
||||
var COMMAND_LINE_ARGS: CommandLineArguments;
|
||||
|
||||
/**
|
||||
* Forces a global mode if set to `true` before parsing the CLI arguments.
|
||||
*/
|
||||
var FORCE_GLOBAL_MODE: boolean;
|
||||
}
|
||||
|
||||
Generated
+54
-26
@@ -40,7 +40,7 @@
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zeldafan0225/ai_horde": "^5.2.0",
|
||||
"archiver": "^7.0.1",
|
||||
"bing-translate-api": "^4.0.2",
|
||||
"bing-translate-api": "^4.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"bowser": "^2.11.0",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -55,8 +55,9 @@
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"dompurify": "^3.2.6",
|
||||
"droll": "^0.2.1",
|
||||
"env-paths": "^3.0.0",
|
||||
"express": "^4.21.0",
|
||||
"form-data": "^4.0.3",
|
||||
"form-data": "^4.0.4",
|
||||
"fuse.js": "^7.1.0",
|
||||
"google-translate-api-browser": "^3.0.1",
|
||||
"google-translate-api-x": "^10.7.2",
|
||||
@@ -77,7 +78,7 @@
|
||||
"multer": "^2.0.2",
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-persist": "^4.0.4",
|
||||
"open": "^10.1.2",
|
||||
"open": "^10.2.0",
|
||||
"png-chunk-text": "^1.0.0",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"proxy-agent": "^6.5.0",
|
||||
@@ -95,13 +96,13 @@
|
||||
"wavefile": "^11.0.0",
|
||||
"webpack": "^5.98.0",
|
||||
"write-file-atomic": "^5.0.1",
|
||||
"ws": "^8.18.2",
|
||||
"ws": "^8.18.3",
|
||||
"yaml": "^2.8.0",
|
||||
"yargs": "^17.7.1",
|
||||
"yauzl": "^3.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"sillytavern": "server.js"
|
||||
"sillytavern": "src/server-global.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.3",
|
||||
@@ -117,9 +118,9 @@
|
||||
"@types/jquery-cropper": "^1.0.4",
|
||||
"@types/jquery.transit": "^0.9.33",
|
||||
"@types/jqueryui": "^1.12.24",
|
||||
"@types/lodash": "^4.17.17",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/multer": "^1.4.13",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^18.19.84",
|
||||
"@types/node-persist": "^3.1.8",
|
||||
"@types/png-chunk-text": "^1.0.3",
|
||||
@@ -1972,9 +1973,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz",
|
||||
"integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
"integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1999,9 +2000,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/multer": {
|
||||
"version": "1.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz",
|
||||
"integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz",
|
||||
"integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2832,9 +2833,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bing-translate-api": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-4.0.2.tgz",
|
||||
"integrity": "sha512-JJ8XUehnxzOhHU91oy86xEtp8OOMjVEjCZJX042fKxoO19NNvxJ5omeCcxQNFoPbDqVpBJwqiGVquL0oPdQm1Q==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-4.1.0.tgz",
|
||||
"integrity": "sha512-oP2663Yd5MXX4kbB/3LdS9YgPiE+ls9+2iFZH2ZXigWhWyHT3R4m6aCup4TNJd3/U4gqHHnQoxTaIW7uOf4+vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"got": "^11.8.6"
|
||||
@@ -4087,6 +4088,18 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
|
||||
"integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
@@ -4786,9 +4799,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
|
||||
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -6420,15 +6433,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz",
|
||||
"integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==",
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
|
||||
"integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"default-browser": "^5.2.1",
|
||||
"define-lazy-prop": "^3.0.0",
|
||||
"is-inside-container": "^1.0.0",
|
||||
"is-wsl": "^3.1.0"
|
||||
"wsl-utils": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -8503,9 +8516,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -8523,6 +8536,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/wsl-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
|
||||
"integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-wsl": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/xhr": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
|
||||
|
||||
+9
-7
@@ -30,7 +30,7 @@
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zeldafan0225/ai_horde": "^5.2.0",
|
||||
"archiver": "^7.0.1",
|
||||
"bing-translate-api": "^4.0.2",
|
||||
"bing-translate-api": "^4.1.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"bowser": "^2.11.0",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -45,8 +45,9 @@
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"dompurify": "^3.2.6",
|
||||
"droll": "^0.2.1",
|
||||
"env-paths": "^3.0.0",
|
||||
"express": "^4.21.0",
|
||||
"form-data": "^4.0.3",
|
||||
"form-data": "^4.0.4",
|
||||
"fuse.js": "^7.1.0",
|
||||
"google-translate-api-browser": "^3.0.1",
|
||||
"google-translate-api-x": "^10.7.2",
|
||||
@@ -67,7 +68,7 @@
|
||||
"multer": "^2.0.2",
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-persist": "^4.0.4",
|
||||
"open": "^10.1.2",
|
||||
"open": "^10.2.0",
|
||||
"png-chunk-text": "^1.0.0",
|
||||
"png-chunks-extract": "^1.0.0",
|
||||
"proxy-agent": "^6.5.0",
|
||||
@@ -85,7 +86,7 @@
|
||||
"wavefile": "^11.0.0",
|
||||
"webpack": "^5.98.0",
|
||||
"write-file-atomic": "^5.0.1",
|
||||
"ws": "^8.18.2",
|
||||
"ws": "^8.18.3",
|
||||
"yaml": "^2.8.0",
|
||||
"yargs": "^17.7.1",
|
||||
"yauzl": "^3.2.0"
|
||||
@@ -115,6 +116,7 @@
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"debug": "node --inspect server.js",
|
||||
"start:global": "node server.js --global",
|
||||
"start:electron": "cd ./src/electron && npm run start",
|
||||
"start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js",
|
||||
"start:bun": "bun server.js",
|
||||
@@ -126,7 +128,7 @@
|
||||
"plugins:install": "node plugins install"
|
||||
},
|
||||
"bin": {
|
||||
"sillytavern": "./server.js"
|
||||
"sillytavern": "./src/server-global.js"
|
||||
},
|
||||
"rules": {
|
||||
"no-path-concat": "off",
|
||||
@@ -147,9 +149,9 @@
|
||||
"@types/jquery-cropper": "^1.0.4",
|
||||
"@types/jquery.transit": "^0.9.33",
|
||||
"@types/jqueryui": "^1.12.24",
|
||||
"@types/lodash": "^4.17.17",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/mime-types": "^3.0.1",
|
||||
"@types/multer": "^1.4.13",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^18.19.84",
|
||||
"@types/node-persist": "^3.1.8",
|
||||
"@types/png-chunk-text": "^1.0.3",
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { CommandLineParser } from './src/command-line.js';
|
||||
import { serverDirectory } from './src/server-directory.js';
|
||||
|
||||
console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`);
|
||||
|
||||
// config.yaml will be set when parsing command line arguments
|
||||
const cliArgs = new CommandLineParser().parse(process.argv);
|
||||
globalThis.DATA_ROOT = cliArgs.dataRoot;
|
||||
|
||||
+75
-34
@@ -1,6 +1,9 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import yargs from 'yargs/yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
import ipRegex from 'ip-regex';
|
||||
import envPaths from 'env-paths';
|
||||
import { canResolve, color, getConfigValue, stringToBool } from './util.js';
|
||||
import { initConfig } from './config-init.js';
|
||||
|
||||
@@ -39,11 +42,18 @@ import { initConfig } from './config-init.js';
|
||||
* Provides a command line arguments parser.
|
||||
*/
|
||||
export class CommandLineParser {
|
||||
constructor() {
|
||||
/** @type {CommandLineArguments} */
|
||||
this.default = Object.freeze({
|
||||
configPath: './config.yaml',
|
||||
dataRoot: './data',
|
||||
/**
|
||||
* Gets the default configuration values.
|
||||
* @param {boolean} isGlobal If the configuration is global or not
|
||||
* @returns {CommandLineArguments} Default configuration values
|
||||
*/
|
||||
getDefaultConfig(isGlobal) {
|
||||
const appPaths = envPaths('SillyTavern', { suffix: '' });
|
||||
const configPath = isGlobal ? path.join(appPaths.data, 'config.yaml') : './config.yaml';
|
||||
const dataPath = isGlobal ? path.join(appPaths.data, 'data') : './data';
|
||||
return Object.freeze({
|
||||
configPath: configPath,
|
||||
dataRoot: dataPath,
|
||||
port: 8000,
|
||||
listen: false,
|
||||
listenAddressIPv6: '[::]',
|
||||
@@ -78,7 +88,9 @@ export class CommandLineParser {
|
||||
throw new Error('getBrowserLaunchUrl is not implemented');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.booleanAutoOptions = [true, false, 'auto'];
|
||||
}
|
||||
|
||||
@@ -91,10 +103,15 @@ export class CommandLineParser {
|
||||
parse(args) {
|
||||
const cliArguments = yargs(hideBin(args))
|
||||
.usage('Usage: <your-start-script> [options]\nOptions that are not provided will be filled with config values.')
|
||||
.option('global', {
|
||||
type: 'boolean',
|
||||
default: null,
|
||||
describe: 'Use global data and config paths instead of the server directory',
|
||||
})
|
||||
.option('configPath', {
|
||||
type: 'string',
|
||||
default: null,
|
||||
describe: 'Path to the config file',
|
||||
describe: 'Path to the config file (only for standalone mode)',
|
||||
})
|
||||
.option('enableIPv6', {
|
||||
type: 'string',
|
||||
@@ -184,7 +201,7 @@ export class CommandLineParser {
|
||||
.option('dataRoot', {
|
||||
type: 'string',
|
||||
default: null,
|
||||
describe: 'Root directory for data storage',
|
||||
describe: 'Root directory for data storage (only for standalone mode)',
|
||||
})
|
||||
.option('basicAuthMode', {
|
||||
type: 'boolean',
|
||||
@@ -228,33 +245,57 @@ export class CommandLineParser {
|
||||
})
|
||||
.parseSync();
|
||||
|
||||
const configPath = cliArguments.configPath ?? this.default.configPath;
|
||||
const isGlobal = globalThis.FORCE_GLOBAL_MODE ?? cliArguments.global ?? false;
|
||||
const defaultConfig = this.getDefaultConfig(isGlobal);
|
||||
|
||||
if (isGlobal && cliArguments.configPath) {
|
||||
console.warn(color.yellow('Warning: "--configPath" argument is ignored in global mode'));
|
||||
}
|
||||
|
||||
if (isGlobal && cliArguments.dataRoot) {
|
||||
console.warn(color.yellow('Warning: "--dataRoot" argument is ignored in global mode'));
|
||||
}
|
||||
|
||||
const configPath = isGlobal
|
||||
? defaultConfig.configPath
|
||||
: (cliArguments.configPath ?? defaultConfig.configPath);
|
||||
if (isGlobal && !fs.existsSync(path.dirname(configPath))) {
|
||||
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
||||
}
|
||||
initConfig(configPath);
|
||||
|
||||
const dataRoot = isGlobal
|
||||
? defaultConfig.dataRoot
|
||||
: (cliArguments.dataRoot ?? getConfigValue('dataRoot', defaultConfig.dataRoot));
|
||||
if (isGlobal && !fs.existsSync(dataRoot)) {
|
||||
fs.mkdirSync(dataRoot, { recursive: true });
|
||||
}
|
||||
|
||||
/** @type {CommandLineArguments} */
|
||||
const result = {
|
||||
configPath: configPath,
|
||||
dataRoot: cliArguments.dataRoot ?? getConfigValue('dataRoot', this.default.dataRoot),
|
||||
port: cliArguments.port ?? getConfigValue('port', this.default.port, 'number'),
|
||||
listen: cliArguments.listen ?? getConfigValue('listen', this.default.listen, 'boolean'),
|
||||
listenAddressIPv6: cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', this.default.listenAddressIPv6),
|
||||
listenAddressIPv4: cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', this.default.listenAddressIPv4),
|
||||
enableIPv4: stringToBool(cliArguments.enableIPv4) ?? stringToBool(getConfigValue('protocol.ipv4', this.default.enableIPv4)) ?? this.default.enableIPv4,
|
||||
enableIPv6: stringToBool(cliArguments.enableIPv6) ?? stringToBool(getConfigValue('protocol.ipv6', this.default.enableIPv6)) ?? this.default.enableIPv6,
|
||||
dnsPreferIPv6: cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', this.default.dnsPreferIPv6, 'boolean'),
|
||||
browserLaunchEnabled: cliArguments.browserLaunchEnabled ?? cliArguments.autorun ?? getConfigValue('browserLaunch.enabled', this.default.browserLaunchEnabled, 'boolean'),
|
||||
browserLaunchHostname: cliArguments.browserLaunchHostname ?? cliArguments.autorunHostname ?? getConfigValue('browserLaunch.hostname', this.default.browserLaunchHostname),
|
||||
browserLaunchPort: cliArguments.browserLaunchPort ?? cliArguments.autorunPortOverride ?? getConfigValue('browserLaunch.port', this.default.browserLaunchPort, 'number'),
|
||||
browserLaunchAvoidLocalhost: cliArguments.browserLaunchAvoidLocalhost ?? cliArguments.avoidLocalhost ?? getConfigValue('browserLaunch.avoidLocalhost', this.default.browserLaunchAvoidLocalhost, 'boolean'),
|
||||
enableCorsProxy: cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', this.default.enableCorsProxy, 'boolean'),
|
||||
disableCsrf: cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', this.default.disableCsrf, 'boolean'),
|
||||
ssl: cliArguments.ssl ?? getConfigValue('ssl.enabled', this.default.ssl, 'boolean'),
|
||||
certPath: cliArguments.certPath ?? getConfigValue('ssl.certPath', this.default.certPath),
|
||||
keyPath: cliArguments.keyPath ?? getConfigValue('ssl.keyPath', this.default.keyPath),
|
||||
whitelistMode: cliArguments.whitelist ?? getConfigValue('whitelistMode', this.default.whitelistMode, 'boolean'),
|
||||
basicAuthMode: cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', this.default.basicAuthMode, 'boolean'),
|
||||
requestProxyEnabled: cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', this.default.requestProxyEnabled, 'boolean'),
|
||||
requestProxyUrl: cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', this.default.requestProxyUrl),
|
||||
requestProxyBypass: cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', this.default.requestProxyBypass),
|
||||
dataRoot: dataRoot,
|
||||
port: cliArguments.port ?? getConfigValue('port', defaultConfig.port, 'number'),
|
||||
listen: cliArguments.listen ?? getConfigValue('listen', defaultConfig.listen, 'boolean'),
|
||||
listenAddressIPv6: cliArguments.listenAddressIPv6 ?? getConfigValue('listenAddress.ipv6', defaultConfig.listenAddressIPv6),
|
||||
listenAddressIPv4: cliArguments.listenAddressIPv4 ?? getConfigValue('listenAddress.ipv4', defaultConfig.listenAddressIPv4),
|
||||
enableIPv4: stringToBool(cliArguments.enableIPv4) ?? stringToBool(getConfigValue('protocol.ipv4', defaultConfig.enableIPv4)) ?? defaultConfig.enableIPv4,
|
||||
enableIPv6: stringToBool(cliArguments.enableIPv6) ?? stringToBool(getConfigValue('protocol.ipv6', defaultConfig.enableIPv6)) ?? defaultConfig.enableIPv6,
|
||||
dnsPreferIPv6: cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', defaultConfig.dnsPreferIPv6, 'boolean'),
|
||||
browserLaunchEnabled: cliArguments.browserLaunchEnabled ?? cliArguments.autorun ?? getConfigValue('browserLaunch.enabled', defaultConfig.browserLaunchEnabled, 'boolean'),
|
||||
browserLaunchHostname: cliArguments.browserLaunchHostname ?? cliArguments.autorunHostname ?? getConfigValue('browserLaunch.hostname', defaultConfig.browserLaunchHostname),
|
||||
browserLaunchPort: cliArguments.browserLaunchPort ?? cliArguments.autorunPortOverride ?? getConfigValue('browserLaunch.port', defaultConfig.browserLaunchPort, 'number'),
|
||||
browserLaunchAvoidLocalhost: cliArguments.browserLaunchAvoidLocalhost ?? cliArguments.avoidLocalhost ?? getConfigValue('browserLaunch.avoidLocalhost', defaultConfig.browserLaunchAvoidLocalhost, 'boolean'),
|
||||
enableCorsProxy: cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', defaultConfig.enableCorsProxy, 'boolean'),
|
||||
disableCsrf: cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', defaultConfig.disableCsrf, 'boolean'),
|
||||
ssl: cliArguments.ssl ?? getConfigValue('ssl.enabled', defaultConfig.ssl, 'boolean'),
|
||||
certPath: cliArguments.certPath ?? getConfigValue('ssl.certPath', defaultConfig.certPath),
|
||||
keyPath: cliArguments.keyPath ?? getConfigValue('ssl.keyPath', defaultConfig.keyPath),
|
||||
whitelistMode: cliArguments.whitelist ?? getConfigValue('whitelistMode', defaultConfig.whitelistMode, 'boolean'),
|
||||
basicAuthMode: cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', defaultConfig.basicAuthMode, 'boolean'),
|
||||
requestProxyEnabled: cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', defaultConfig.requestProxyEnabled, 'boolean'),
|
||||
requestProxyUrl: cliArguments.requestProxyUrl ?? getConfigValue('requestProxy.url', defaultConfig.requestProxyUrl),
|
||||
requestProxyBypass: cliArguments.requestProxyBypass ?? getConfigValue('requestProxy.bypass', defaultConfig.requestProxyBypass),
|
||||
getIPv4ListenUrl: function () {
|
||||
const isValid = ipRegex.v4({ exact: true }).test(this.listenAddressIPv4);
|
||||
return new URL(
|
||||
@@ -302,13 +343,13 @@ export class CommandLineParser {
|
||||
};
|
||||
|
||||
if (!this.booleanAutoOptions.includes(result.enableIPv6)) {
|
||||
console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', this.booleanAutoOptions, '\n setting to:', this.default.enableIPv6);
|
||||
result.enableIPv6 = this.default.enableIPv6;
|
||||
console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', this.booleanAutoOptions, '\n setting to:', defaultConfig.enableIPv6);
|
||||
result.enableIPv6 = defaultConfig.enableIPv6;
|
||||
}
|
||||
|
||||
if (!this.booleanAutoOptions.includes(result.enableIPv4)) {
|
||||
console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', this.booleanAutoOptions, '\n setting to:', this.default.enableIPv4);
|
||||
result.enableIPv4 = this.default.enableIPv4;
|
||||
console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', this.booleanAutoOptions, '\n setting to:', defaultConfig.enableIPv4);
|
||||
result.enableIPv4 = defaultConfig.enableIPv4;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -148,6 +148,13 @@ function getAllKeys(obj, prefix = '') {
|
||||
export function addMissingConfigValues(configPath) {
|
||||
try {
|
||||
const defaultConfig = yaml.parse(fs.readFileSync(path.join(serverDirectory, './default/config.yaml'), 'utf8'));
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.warn(color.yellow(`Warning: config.yaml not found at ${configPath}. Creating a new one with default values.`));
|
||||
fs.writeFileSync(configPath, yaml.stringify(defaultConfig));
|
||||
return;
|
||||
}
|
||||
|
||||
let config = yaml.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
|
||||
// Migrate old keys to new keys
|
||||
@@ -224,6 +231,7 @@ export function addMissingConfigValues(configPath) {
|
||||
* @param {string} configPath Path to config.yaml
|
||||
*/
|
||||
export function initConfig(configPath) {
|
||||
console.log('Using config path:', color.green(configPath));
|
||||
setConfigFilePath(configPath);
|
||||
addMissingConfigValues(configPath);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ function startServer() {
|
||||
const sillyTavernRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
process.chdir(sillyTavernRoot);
|
||||
|
||||
import('../../server.js');
|
||||
import('../server-global.js');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
globalThis.FORCE_GLOBAL_MODE = true;
|
||||
await import('../server.js');
|
||||
|
||||
export {};
|
||||
+26
-22
@@ -19,16 +19,6 @@ import bodyParser from 'body-parser';
|
||||
import './fetch-patch.js';
|
||||
import { serverDirectory } from './server-directory.js';
|
||||
|
||||
console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`);
|
||||
|
||||
// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
|
||||
// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
|
||||
// Safe to remove once support for Node v20 is dropped.
|
||||
if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) {
|
||||
// @ts-ignore
|
||||
if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
|
||||
}
|
||||
|
||||
import { serverEvents, EVENT_NAMES } from './server-events.js';
|
||||
import { loadPlugins } from './plugin-loader.js';
|
||||
import {
|
||||
@@ -78,6 +68,14 @@ import { redirectDeprecatedEndpoints, ServerStartup, setupPrivateEndpoints } fro
|
||||
import { diskCache } from './endpoints/characters.js';
|
||||
import { migrateFlatSecrets } from './endpoints/secrets.js';
|
||||
|
||||
// Work around a node v20.0.0, v20.1.0, and v20.2.0 bug. The issue was fixed in v20.3.0.
|
||||
// https://github.com/nodejs/node/issues/47822#issuecomment-1564708870
|
||||
// Safe to remove once support for Node v20 is dropped.
|
||||
if (process.versions && process.versions.node && process.versions.node.match(/20\.[0-2]\.0/)) {
|
||||
// @ts-ignore
|
||||
if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
|
||||
}
|
||||
|
||||
// Unrestrict console logs display limit
|
||||
util.inspect.defaultOptions.maxArrayLength = null;
|
||||
util.inspect.defaultOptions.maxStringLength = null;
|
||||
@@ -91,18 +89,6 @@ if (!cliArgs.enableIPv6 && !cliArgs.enableIPv4) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
if (cliArgs.dnsPreferIPv6) {
|
||||
dns.setDefaultResultOrder('ipv6first');
|
||||
console.log('Preferring IPv6 for DNS resolution');
|
||||
} else {
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
console.log('Preferring IPv4 for DNS resolution');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to set DNS resolution order. Possibly unsupported in this Node version.');
|
||||
}
|
||||
|
||||
const app = express();
|
||||
app.use(helmet({
|
||||
contentSecurityPolicy: false,
|
||||
@@ -400,8 +386,26 @@ function apply404Middleware() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the DNS resolution order based on the command line arguments.
|
||||
*/
|
||||
function setDnsResolutionOrder() {
|
||||
try {
|
||||
if (cliArgs.dnsPreferIPv6) {
|
||||
dns.setDefaultResultOrder('ipv6first');
|
||||
console.log('Preferring IPv6 for DNS resolution');
|
||||
} else {
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
console.log('Preferring IPv4 for DNS resolution');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to set DNS resolution order. Possibly unsupported in this Node version.');
|
||||
}
|
||||
}
|
||||
|
||||
// User storage module needs to be initialized before starting the server
|
||||
initUserStorage(globalThis.DATA_ROOT)
|
||||
.then(setDnsResolutionOrder)
|
||||
.then(ensurePublicDirectoriesExist)
|
||||
.then(migrateUserData)
|
||||
.then(migrateSystemPrompts)
|
||||
|
||||
Reference in New Issue
Block a user