Allow user to configure an address to listen to
Signed| @@ -6,6 +6,8 @@ cardsCacheCapacity: 100 | ||
| 6 | 6 | # -- SERVER CONFIGURATION -- |
| 7 | 7 | # Listen for incoming connections |
| 8 | 8 | listen: false |
| 9 | +# Listen on a specific address, supports IPv4 and IPv6 | |
| 10 | +listenAddress: 127.0.0.1 | |
| 9 | 11 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! |
| 10 | 12 | # - Use option "auto" to automatically detect support |
| 11 | 13 | # - Use true or false (no qoutes) to enable or disable each protocol |
| @@ -130,6 +130,7 @@ if (process.versions && process.versions.node && process.versions.node.match(/20 | ||
| 130 | 130 | const DEFAULT_PORT = 8000; |
| 131 | 131 | const DEFAULT_AUTORUN = false; |
| 132 | 132 | const DEFAULT_LISTEN = false; |
| 133 | +const DEFAULT_LISTEN_ADDRESS = ''; | |
| 133 | 134 | const DEFAULT_CORS_PROXY = false; |
| 134 | 135 | const DEFAULT_WHITELIST = true; |
| 135 | 136 | const DEFAULT_ACCOUNTS = false; |
| @@ -185,6 +186,10 @@ const cliArguments = yargs(hideBin(process.argv)) | ||
| 185 | 186 | type: 'boolean', |
| 186 | 187 | default: null, |
| 187 | 188 | describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`, |
| 189 | + }).option('listenAddress', { | |
| 190 | + type: 'string', | |
| 191 | + default: null, | |
| 192 | + describe: 'Set SillyTavern to listen to a specific address. If not set, it will fallback to listen to all.\n[config default: empty ]', | |
| 188 | 193 | }).option('corsProxy', { |
| 189 | 194 | type: 'boolean', |
| 190 | 195 | default: null, |
| @@ -254,6 +259,8 @@ const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getCon | ||
| 254 | 259 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; |
| 255 | 260 | /** @type {boolean} */ |
| 256 | 261 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); |
| 262 | +/** @type {string} */ | |
| 263 | +const listenAddress = cliArguments.listenAddress ?? getConfigValue('listenAddress', DEFAULT_LISTEN_ADDRESS); | |
| 257 | 264 | /** @type {boolean} */ |
| 258 | 265 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); |
| 259 | 266 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); |
| @@ -706,15 +713,17 @@ app.use('/api/backends/scale-alt', scaleAltRouter); | ||
| 706 | 713 | app.use('/api/speech', speechRouter); |
| 707 | 714 | app.use('/api/azure', azureRouter); |
| 708 | 715 | |
| 716 | +const ipv6_regex = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/m; | |
| 709 | 717 | const tavernUrlV6 = new URL( |
| 710 | 718 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 711 | 719 | (listen ? (ipv6_regex.test(listenAddress) ? listenAddress : '[::]') : '[::1]') + |
| 712 | 720 | (':' + server_port), |
| 713 | 721 | ); |
| 714 | 722 | |
| 723 | +const ipv4_regex = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/m; | |
| 715 | 724 | const tavernUrl = new URL( |
| 716 | 725 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 717 | 726 | (listen ? (ipv4_regex.test(listenAddress) ? listenAddress : '0.0.0.0') : '127.0.0.1') + |
| 718 | 727 | (':' + server_port), |
| 719 | 728 | ); |
| 720 | 729 | |
| @@ -780,6 +789,10 @@ const preSetupTasks = async function () { | ||
| 780 | 789 | */ |
| 781 | 790 | async function getAutorunHostname(useIPv6, useIPv4) { |
| 782 | 791 | if (autorunHostname === 'auto') { |
| 792 | + if (listen && (ipv4_regex.test(listenAddress) || ipv6_regex.test(listenAddress))) { | |
| 793 | + return listenAddress; | |
| 794 | + } | |
| 795 | + | |
| 783 | 796 | let localhostResolve = await canResolve('localhost', useIPv6, useIPv4); |
| 784 | 797 | |
| 785 | 798 | if (useIPv6 && useIPv4) { |
| @@ -842,9 +855,15 @@ const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { | ||
| 842 | 855 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); |
| 843 | 856 | |
| 844 | 857 | if (listen) { |
| 845 | - console.log( | |
| 858 | + if (ipv4_regex.test(listenAddress) || ipv6_regex.test(listenAddress)) { | |
| 846 | - '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n', | |
| 859 | + console.log( | |
| 847 | - ); | |
| 860 | + `SillyTavern is listening on the address ${listenAddress}. If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n`, | |
| 861 | + ); | |
| 862 | + } else { | |
| 863 | + console.log( | |
| 864 | + '[::] or 0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost ([::1] or 127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n', | |
| 865 | + ); | |
| 866 | + } | |
| 848 | 867 | } |
| 849 | 868 | |
| 850 | 869 | if (basicAuthMode) { |
| @@ -909,6 +928,19 @@ function logSecurityAlert(message) { | ||
| 909 | 928 | } |
| 910 | 929 | |
| 911 | 930 | /** |
| 931 | + * Prints a warning message | |
| 932 | + * @param {string} message The warning message to print | |
| 933 | + * @returns {void} | |
| 934 | + */ | |
| 935 | +function logSecurityWarning(message) { | |
| 936 | + if (basicAuthMode || enableWhitelist) return; // safe! | |
| 937 | + console.error(color.yellow(message)); | |
| 938 | + if (getConfigValue('securityOverride', false)) { | |
| 939 | + console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.')); | |
| 940 | + } | |
| 941 | +} | |
| 942 | + | |
| 943 | +/** | |
| 912 | 944 | * Handles the case where the server failed to start on one or both protocols. |
| 913 | 945 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 914 | 946 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| @@ -1083,7 +1115,7 @@ async function verifySecuritySettings() { | ||
| 1083 | 1115 | } |
| 1084 | 1116 | |
| 1085 | 1117 | if (!enableAccounts) { |
| 1086 | 1118 | logSecurityAlert('Your current SillyTavern isconfiguration currentlyis insecurelyinsecure open(listening to the publicnon-localhost). Enable whitelisting, basic authentication or user accounts.'); |
| 1087 | 1119 | } |
| 1088 | 1120 | |
| 1089 | 1121 | const users = await getAllEnabledUsers(); |