support for Ipv6 (#2593)

* started adding v6 support

* added error checking and change messages to the user

* fixed lsp caused issue

* fixed formatting error

* added error handling to https

* fixed formatting errors

* brought server starting into different func and added enable v6 and v4

* added error checking for disabling both v6 and v4. added option to prefer v6 for dns. added that stuff to the default config

* fixed dumb bug

* changed to settings named disable ipvx

* fixed failed ips still showing as listening

* fixed error handling

* changed ip protocol config layout

* small const name changes

* fixed no error if only available protocol fails, and changed wording of some errors

* fixed error handling saying 'non-fatal error' for protocol fail even when it's the only one enabled

* moved more logic to listen error handler

* fixed eslint issues

* added more info on when to prefer ipv6 for dns

* in conf changed one 'ipv6' to 'IPv6' for consistency

* changed error message and redid how starting the server works

* removed unneeded log

* removed unneeded log

* removed unneeded comments

* fixed errors

* fixed errors

* fixed errors

* changed the wording of ip related error messages

* removed empty lines

* changed to .finally(startServer);

* removed some whitespace

* disabled ipv6 by default ╯︿╰ and changed some message wording

* added auto mode for autorun hostname and changed formatting for listening log and added goto message with autorun url

* added autorun port override

* removed debug log

* changed formatting

* added cli args to ipv6 and autorun stuff

* moved cli args around

* changed formatting

* changed colors for ip

* added avoidLocalhost cli arg

* changed formatting

* changed to not print protocol on listening

* added config option for avoid localhost and changed formatting of messages

* fixed avoid localhost config option

* Fix ipv4 color

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
BPplays 2024-08-15 10:12:12 -07:00 committed by GitHub
parent 3b46b5a46d
commit da5581e20e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 269 additions and 52 deletions

View File

@ -4,8 +4,18 @@ dataRoot: ./data
# -- SERVER CONFIGURATION --
# Listen for incoming connections
listen: false
# Enables IPv6 and/or IPv4
protocol:
ipv4: true
ipv6: false
# Prefers IPv6 for dns, you should probably enable this on ISPs that don't have issues with IPv6
dnsPreferIPv6: false
# the hostname that autorun opens probably best left on auto. use options like 'localhost', 'st.example.com'
autorunHostname: "auto"
# Server port
port: 8000
# overrides the port for autorun with open your browser with this port and ignore what port the server is running on. -1 is use server port
autorunPortOverride: -1
# -- SECURITY CONFIGURATION --
# Toggle whitelist mode
whitelistMode: true
@ -13,6 +23,7 @@ whitelistMode: true
enableForwardedWhitelist: true
# Whitelist of allowed IP addresses
whitelist:
- ::1
- 127.0.0.1
# Toggle basic authentication for endpoints
basicAuthMode: false
@ -35,6 +46,9 @@ securityOverride: false
# -- ADVANCED CONFIGURATION --
# Open the browser automatically
autorun: true
# Avoids using 'localhost' for autorun in auto mode.
# use if you don't have 'localhost' in your hosts file
avoidLocalhost: false
# Disable thumbnail generation
disableThumbnails: false
# Thumbnail quality (0-100)

42
package-lock.json generated
View File

@ -58,7 +58,7 @@
},
"devDependencies": {
"@types/jquery": "^3.5.29",
"eslint": "^8.55.0",
"eslint": "^8.57.0",
"jquery": "^3.6.4"
},
"engines": {
@ -166,9 +166,9 @@
"license": "MIT"
},
"node_modules/@eslint/js": {
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
"integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
"integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
"dev": true,
"license": "MIT",
"engines": {
@ -185,14 +185,15 @@
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"deprecated": "Use @eslint/config-array instead",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
"@humanwhocodes/object-schema": "^2.0.2",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
@ -200,9 +201,9 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -239,9 +240,10 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
"integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead",
"dev": true,
"license": "BSD-3-Clause"
},
@ -2455,17 +2457,17 @@
}
},
"node_modules/eslint": {
"version": "8.55.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
"integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.55.0",
"@humanwhocodes/config-array": "^0.11.13",
"@eslint/js": "8.57.0",
"@humanwhocodes/config-array": "^0.11.14",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"@ungap/structured-clone": "^1.2.0",

View File

@ -90,7 +90,7 @@
"main": "server.js",
"devDependencies": {
"@types/jquery": "^3.5.29",
"eslint": "^8.55.0",
"eslint": "^8.57.0",
"jquery": "^3.6.4"
}
}

263
server.js
View File

@ -54,8 +54,9 @@ if (process.versions && process.versions.node && process.versions.node.match(/20
if (net.setDefaultAutoSelectFamily) net.setDefaultAutoSelectFamily(false);
}
// Set default DNS resolution order to IPv4 first
dns.setDefaultResultOrder('ipv4first');
const DEFAULT_PORT = 8000;
const DEFAULT_AUTORUN = false;
@ -66,16 +67,46 @@ const DEFAULT_ACCOUNTS = false;
const DEFAULT_CSRF_DISABLED = false;
const DEFAULT_BASIC_AUTH = false;
const DEFAULT_ENABLE_IPV6 = false;
const DEFAULT_ENABLE_IPV4 = true;
const DEFAULT_PREFER_IPV6 = false;
const DEFAULT_AVOID_LOCALHOST = false;
const DEFAULT_AUTORUN_HOSTNAME = 'auto';
const DEFAULT_AUTORUN_PORT = -1;
const cliArguments = yargs(hideBin(process.argv))
.usage('Usage: <your-start-script> <command> [options]')
.option('port', {
.option('enableIPv6', {
type: 'boolean',
default: null,
describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`,
}).option('enableIPv4', {
type: 'boolean',
default: null,
describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`,
}).option('port', {
type: 'number',
default: null,
describe: `Sets the port under which SillyTavern will run.\nIf not provided falls back to yaml config 'port'.\n[config default: ${DEFAULT_PORT}]`,
}).option('dnsPreferIPv6', {
type: 'boolean',
default: null,
describe: `Prefers IPv6 for dns\nyou should probably have the enabled if you're on an IPv6 only network\nIf not provided falls back to yaml config 'preferIPv6'.\n[config default: ${DEFAULT_PREFER_IPV6}]`,
}).option('autorun', {
type: 'boolean',
default: null,
describe: `Automatically launch SillyTavern in the browser.\nAutorun is automatically disabled if --ssl is set to true.\nIf not provided falls back to yaml config 'autorun'.\n[config default: ${DEFAULT_AUTORUN}]`,
}).option('autorunHostname', {
type: 'string',
default: null,
describe: 'the autorun hostname, probably best left on \'auto\'.\nuse values like \'localhost\', \'st.example.com\'',
}).option('autorunPortOverride', {
type: 'string',
default: null,
describe: 'Overrides the port for autorun with open your browser with this port and ignore what port the server is running on. -1 is use server port',
}).option('listen', {
type: 'boolean',
default: null,
@ -108,6 +139,10 @@ const cliArguments = yargs(hideBin(process.argv))
type: 'string',
default: null,
describe: 'Root directory for data storage',
}).option('avoidLocalhost', {
type: 'boolean',
default: null,
describe: 'Avoids using \'localhost\' for autorun in auto mode.\nuse if you don\'t have \'localhost\' in your hosts file',
}).option('basicAuthMode', {
type: 'boolean',
default: null,
@ -138,6 +173,31 @@ const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
const uploadsPath = path.join(dataRoot, require('./src/constants').UPLOADS_DIRECTORY);
const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6);
const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4);
const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);
if (dnsPreferIPv6) {
// Set default DNS resolution order to IPv6 first
dns.setDefaultResultOrder('ipv6first');
console.log('Preferring IPv6 for DNS resolution');
} else {
// Set default DNS resolution order to IPv4 first
dns.setDefaultResultOrder('ipv4first');
console.log('Preferring IPv4 for DNS resolution');
}
if (!enableIPv6 && !enableIPv4) {
console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');
process.exit(1);
}
// CORS Settings //
const CORS = cors({
origin: 'null',
@ -546,17 +606,18 @@ app.use('/api/speech', require('./src/endpoints/speech').router);
// Azure TTS
app.use('/api/azure', require('./src/endpoints/azure').router);
const tavernUrlV6 = new URL(
(cliArguments.ssl ? 'https://' : 'http://') +
(listen ? '[::]' : '[::1]') +
(':' + server_port),
);
const tavernUrl = new URL(
(cliArguments.ssl ? 'https://' : 'http://') +
(listen ? '0.0.0.0' : '127.0.0.1') +
(':' + server_port),
);
const autorunUrl = new URL(
(cliArguments.ssl ? 'https://' : 'http://') +
('127.0.0.1') +
(':' + server_port),
);
/**
* Tasks that need to be run before the server starts listening.
@ -606,20 +667,83 @@ const preSetupTasks = async function () {
});
};
function removeColorFormatting(text) {
// ANSI escape codes for colors are usually in the format \x1b[<codes>m
return text.replace(/\x1b\[\d{1,2}(;\d{1,2})*m/g, '');
}
function getSeparator(n) {
return '='.repeat(n);
}
function getAutorunHostname() {
if (autorunHostname === 'auto') {
if (enableIPv6 && enableIPv4) {
if (avoidLocalhost) return '[::1]';
return 'localhost';
}
if (enableIPv6) {
return '[::1]';
}
if (enableIPv4) {
return '127.0.0.1';
}
}
return autorunHostname;
}
/**
* Tasks that need to be run after the server starts listening.
*/
const postSetupTasks = async function () {
const postSetupTasks = async function (v6Failed, v4Failed) {
const autorunUrl = new URL(
(cliArguments.ssl ? 'https://' : 'http://') +
(getAutorunHostname()) +
(':') +
((autorunPortOverride >= 0) ? autorunPortOverride : server_port),
);
console.log('Launching...');
if (autorun) open(autorunUrl.toString());
setWindowTitle('SillyTavern WebServer');
console.log(color.green('SillyTavern is listening on: ' + tavernUrl));
let ipv6Color = color.green;
let ipv4Color = color.green;
let autorunColor = color.blue;
let logListen = 'SillyTavern is listening on';
if (enableIPv6 && !v6Failed) {
logListen += ipv6Color(' IPv6: ' + tavernUrlV6.host);
}
if (enableIPv4 && !v4Failed) {
logListen += ipv4Color(' IPv4: ' + tavernUrl.host);
}
let goToLog = 'Go to: ' + autorunColor(autorunUrl) + ' to open SillyTavern';
let plainGoToLog = removeColorFormatting(goToLog);
console.log(logListen);
console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
console.log(goToLog);
console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
if (listen) {
console.log('\n0.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 (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');
console.log('[::] 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');
}
if (basicAuthMode) {
@ -674,6 +798,102 @@ function logSecurityAlert(message) {
process.exit(1);
}
function handleServerListenFail(v6Failed, v4Failed) {
if (v6Failed && !enableIPv4) {
console.error('fatal error: Failed to start server on IPv6 and IPv4 disabled');
process.exit(1);
}
if (v4Failed && !enableIPv6) {
console.error('fatal error: Failed to start server on IPv4 and IPv6 disabled');
process.exit(1);
}
if (v6Failed && v4Failed) {
console.error('fatal error: Failed to start server on both IPv6 and IPv4');
process.exit(1);
}
}
function createHttpsServer(url) {
return new Promise((resolve, reject) => {
const server = https.createServer(
{
cert: fs.readFileSync(cliArguments.certPath),
key: fs.readFileSync(cliArguments.keyPath),
}, app);
server.on('error', reject);
server.on('listening', resolve);
server.listen(url.port, url.hostname);
});
}
function createHttpServer(url) {
return new Promise((resolve, reject) => {
const server = http.createServer(app);
server.on('error', reject);
server.on('listening', resolve);
server.listen(url.port, url.hostname);
});
}
async function startHTTPorHTTPS() {
let v6Failed = false;
let v4Failed = false;
let createFunc = createHttpServer;
if (cliArguments.ssl) {
createFunc = createHttpsServer;
}
if (enableIPv6) {
try {
await createFunc(tavernUrlV6);
} catch(error) {
if (enableIPv4) {
console.error('non-fatal error: failed to start server on IPv6', error);
}
v6Failed = true;
}
}
if (enableIPv4) {
try {
await createFunc(tavernUrl);
} catch(error) {
if (enableIPv6) {
console.error('non-fatal error: failed to start server on IPv4', error);
}
v4Failed = true;
}
}
return [v6Failed, v4Failed];
}
async function startServer() {
let v6Failed = false;
let v4Failed = false;
[v6Failed, v4Failed] = await startHTTPorHTTPS();
handleServerListenFail(v6Failed, v4Failed);
postSetupTasks(v6Failed, v4Failed);
}
async function verifySecuritySettings() {
// Skip all security checks as listen is set to false
if (!listen) {
@ -707,23 +927,4 @@ userModule.initUserStorage(dataRoot)
.then(userModule.migrateUserData)
.then(verifySecuritySettings)
.then(preSetupTasks)
.finally(() => {
if (cliArguments.ssl) {
https.createServer(
{
cert: fs.readFileSync(cliArguments.certPath),
key: fs.readFileSync(cliArguments.keyPath),
}, app)
.listen(
Number(tavernUrl.port) || 443,
tavernUrl.hostname,
postSetupTasks,
);
} else {
http.createServer(app).listen(
Number(tavernUrl.port) || 80,
tavernUrl.hostname,
postSetupTasks,
);
}
});
.finally(startServer);