mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #3431 from SillyTavern/ipv6_auto
Better IPv6 support
This commit is contained in:
@ -7,6 +7,8 @@ cardsCacheCapacity: 100
|
|||||||
# Listen for incoming connections
|
# Listen for incoming connections
|
||||||
listen: false
|
listen: false
|
||||||
# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!
|
# Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled!
|
||||||
|
# - Use option "auto" to automatically detect support
|
||||||
|
# - Use true or false (no qoutes) to enable or disable each protocol
|
||||||
protocol:
|
protocol:
|
||||||
ipv4: true
|
ipv4: true
|
||||||
ipv6: false
|
ipv6: false
|
||||||
|
242
server.js
242
server.js
@ -4,6 +4,7 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import http from 'node:http';
|
import http from 'node:http';
|
||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
|
import os from 'os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import util from 'node:util';
|
import util from 'node:util';
|
||||||
import net from 'node:net';
|
import net from 'node:net';
|
||||||
@ -65,6 +66,9 @@ import {
|
|||||||
forwardFetchResponse,
|
forwardFetchResponse,
|
||||||
removeColorFormatting,
|
removeColorFormatting,
|
||||||
getSeparator,
|
getSeparator,
|
||||||
|
stringToBool,
|
||||||
|
urlHostnameToIPv6,
|
||||||
|
canResolve,
|
||||||
safeReadFileSync,
|
safeReadFileSync,
|
||||||
setupLogLevel,
|
setupLogLevel,
|
||||||
} from './src/util.js';
|
} from './src/util.js';
|
||||||
@ -150,11 +154,11 @@ const DEFAULT_PROXY_BYPASS = [];
|
|||||||
const cliArguments = yargs(hideBin(process.argv))
|
const cliArguments = yargs(hideBin(process.argv))
|
||||||
.usage('Usage: <your-start-script> <command> [options]')
|
.usage('Usage: <your-start-script> <command> [options]')
|
||||||
.option('enableIPv6', {
|
.option('enableIPv6', {
|
||||||
type: 'boolean',
|
type: 'string',
|
||||||
default: null,
|
default: null,
|
||||||
describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`,
|
describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`,
|
||||||
}).option('enableIPv4', {
|
}).option('enableIPv4', {
|
||||||
type: 'boolean',
|
type: 'string',
|
||||||
default: null,
|
default: null,
|
||||||
describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`,
|
describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`,
|
||||||
}).option('port', {
|
}).option('port', {
|
||||||
@ -243,27 +247,42 @@ app.use(helmet({
|
|||||||
app.use(compression());
|
app.use(compression());
|
||||||
app.use(responseTime());
|
app.use(responseTime());
|
||||||
|
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);
|
const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);
|
||||||
|
/** @type {boolean} */
|
||||||
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
|
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
|
||||||
|
/** @type {boolean} */
|
||||||
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
|
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
|
||||||
|
/** @type {boolean} */
|
||||||
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
|
const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
|
||||||
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
|
const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST);
|
||||||
|
/** @type {string} */
|
||||||
const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
|
const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
|
||||||
|
/** @type {boolean} */
|
||||||
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
|
const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED);
|
||||||
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
|
const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH);
|
||||||
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
|
const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH);
|
||||||
|
/** @type {boolean} */
|
||||||
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
|
const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS);
|
||||||
|
|
||||||
const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY);
|
const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY);
|
||||||
|
|
||||||
const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6);
|
|
||||||
const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4);
|
|
||||||
|
|
||||||
|
/** @type {boolean | "auto"} */
|
||||||
|
let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6);
|
||||||
|
/** @type {boolean | "auto"} */
|
||||||
|
let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4);
|
||||||
|
|
||||||
|
/** @type {string} */
|
||||||
const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
|
const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME);
|
||||||
|
/** @type {number} */
|
||||||
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);
|
const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT);
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);
|
const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6);
|
||||||
|
|
||||||
|
/** @type {boolean} */
|
||||||
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);
|
const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST);
|
||||||
|
|
||||||
const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED);
|
const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED);
|
||||||
@ -280,7 +299,19 @@ if (dnsPreferIPv6) {
|
|||||||
console.log('Preferring IPv4 for DNS resolution');
|
console.log('Preferring IPv4 for DNS resolution');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enableIPv6 && !enableIPv4) {
|
|
||||||
|
const ipOptions = [true, 'auto', false];
|
||||||
|
|
||||||
|
if (!ipOptions.includes(enableIPv6)) {
|
||||||
|
console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV6);
|
||||||
|
enableIPv6 = DEFAULT_ENABLE_IPV6;
|
||||||
|
}
|
||||||
|
if (!ipOptions.includes(enableIPv4)) {
|
||||||
|
console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV4);
|
||||||
|
enableIPv4 = DEFAULT_ENABLE_IPV4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableIPv6 === false && enableIPv4 === false) {
|
||||||
console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');
|
console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -365,6 +396,55 @@ function getSessionCookieAge() {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the network interfaces to determine the presence of IPv6 and IPv4 addresses.
|
||||||
|
*
|
||||||
|
* @returns {Promise<[boolean, boolean, boolean, boolean]>} A promise that resolves to an array containing:
|
||||||
|
* - [0]: `hasIPv6` (boolean) - Whether the computer has any IPv6 address, including (`::1`).
|
||||||
|
* - [1]: `hasIPv4` (boolean) - Whether the computer has any IPv4 address, including (`127.0.0.1`).
|
||||||
|
* - [2]: `hasIPv6Local` (boolean) - Whether the computer has local IPv6 address (`::1`).
|
||||||
|
* - [3]: `hasIPv4Local` (boolean) - Whether the computer has local IPv4 address (`127.0.0.1`).
|
||||||
|
*/
|
||||||
|
async function getHasIP() {
|
||||||
|
let hasIPv6 = false;
|
||||||
|
let hasIPv6Local = false;
|
||||||
|
|
||||||
|
let hasIPv4 = false;
|
||||||
|
let hasIPv4Local = false;
|
||||||
|
|
||||||
|
const interfaces = os.networkInterfaces();
|
||||||
|
|
||||||
|
for (const iface of Object.values(interfaces)) {
|
||||||
|
if (iface === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const info of iface) {
|
||||||
|
if (info.family === 'IPv6') {
|
||||||
|
hasIPv6 = true;
|
||||||
|
if (info.address === '::1') {
|
||||||
|
hasIPv6Local = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.family === 'IPv4') {
|
||||||
|
hasIPv4 = true;
|
||||||
|
if (info.address === '127.0.0.1') {
|
||||||
|
hasIPv4Local = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break;
|
||||||
|
}
|
||||||
|
if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
hasIPv6,
|
||||||
|
hasIPv4,
|
||||||
|
hasIPv6Local,
|
||||||
|
hasIPv4Local,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
app.use(cookieSession({
|
app.use(cookieSession({
|
||||||
name: getCookieSessionName(),
|
name: getCookieSessionName(),
|
||||||
sameSite: 'strict',
|
sameSite: 'strict',
|
||||||
@ -694,21 +774,23 @@ const preSetupTasks = async function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the hostname to use for autorun in the browser.
|
* Gets the hostname to use for autorun in the browser.
|
||||||
* @returns {string} The hostname to use for autorun
|
* @param {boolean} useIPv6 If use IPv6
|
||||||
|
* @param {boolean} useIPv4 If use IPv4
|
||||||
|
* @returns Promise<string> The hostname to use for autorun
|
||||||
*/
|
*/
|
||||||
function getAutorunHostname() {
|
async function getAutorunHostname(useIPv6, useIPv4) {
|
||||||
if (autorunHostname === 'auto') {
|
if (autorunHostname === 'auto') {
|
||||||
if (enableIPv6 && enableIPv4) {
|
let localhostResolve = await canResolve('localhost', useIPv6, useIPv4);
|
||||||
return avoidLocalhost
|
|
||||||
? '[::1]'
|
if (useIPv6 && useIPv4) {
|
||||||
: 'localhost';
|
return (avoidLocalhost || !localhostResolve) ? '[::1]' : 'localhost';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableIPv6) {
|
if (useIPv6) {
|
||||||
return '[::1]';
|
return '[::1]';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableIPv4) {
|
if (useIPv4) {
|
||||||
return '127.0.0.1';
|
return '127.0.0.1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -720,11 +802,13 @@ function getAutorunHostname() {
|
|||||||
* Tasks that need to be run after the server starts listening.
|
* Tasks that need to be run after the server starts listening.
|
||||||
* @param {boolean} v6Failed If the server failed to start on IPv6
|
* @param {boolean} v6Failed If the server failed to start on IPv6
|
||||||
* @param {boolean} v4Failed If the server failed to start on IPv4
|
* @param {boolean} v4Failed If the server failed to start on IPv4
|
||||||
|
* @param {boolean} useIPv6 If the server is using IPv6
|
||||||
|
* @param {boolean} useIPv4 If the server is using IPv4
|
||||||
*/
|
*/
|
||||||
const postSetupTasks = async function (v6Failed, v4Failed) {
|
const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) {
|
||||||
const autorunUrl = new URL(
|
const autorunUrl = new URL(
|
||||||
(cliArguments.ssl ? 'https://' : 'http://') +
|
(cliArguments.ssl ? 'https://' : 'http://') +
|
||||||
(getAutorunHostname()) +
|
(await getAutorunHostname(useIPv6, useIPv4)) +
|
||||||
(':') +
|
(':') +
|
||||||
((autorunPortOverride >= 0) ? autorunPortOverride : server_port),
|
((autorunPortOverride >= 0) ? autorunPortOverride : server_port),
|
||||||
);
|
);
|
||||||
@ -737,12 +821,16 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
|
|||||||
|
|
||||||
let logListen = 'SillyTavern is listening on';
|
let logListen = 'SillyTavern is listening on';
|
||||||
|
|
||||||
if (enableIPv6 && !v6Failed) {
|
if (useIPv6 && !v6Failed) {
|
||||||
logListen += color.green(' IPv6: ' + tavernUrlV6.host);
|
logListen += color.green(
|
||||||
|
' IPv6: ' + tavernUrlV6.host,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableIPv4 && !v4Failed) {
|
if (useIPv4 && !v4Failed) {
|
||||||
logListen += color.green(' IPv4: ' + tavernUrl.host);
|
logListen += color.green(
|
||||||
|
' IPv4: ' + tavernUrl.host,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern';
|
const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern';
|
||||||
@ -754,16 +842,22 @@ const postSetupTasks = async function (v6Failed, v4Failed) {
|
|||||||
console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
|
console.log('\n' + getSeparator(plainGoToLog.length) + '\n');
|
||||||
|
|
||||||
if (listen) {
|
if (listen) {
|
||||||
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');
|
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) {
|
if (basicAuthMode) {
|
||||||
if (perUserBasicAuth && !enableAccounts) {
|
if (perUserBasicAuth && !enableAccounts) {
|
||||||
console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.'));
|
console.error(color.red(
|
||||||
|
'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.',
|
||||||
|
));
|
||||||
} else if (!perUserBasicAuth) {
|
} else if (!perUserBasicAuth) {
|
||||||
const basicAuthUser = getConfigValue('basicAuthUser', {});
|
const basicAuthUser = getConfigValue('basicAuthUser', {});
|
||||||
if (!basicAuthUser?.username || !basicAuthUser?.password) {
|
if (!basicAuthUser?.username || !basicAuthUser?.password) {
|
||||||
console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
|
console.warn(color.yellow(
|
||||||
|
'Basic Authentication is enabled, but username or password is not set or empty!',
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -818,14 +912,16 @@ function logSecurityAlert(message) {
|
|||||||
* Handles the case where the server failed to start on one or both protocols.
|
* Handles the case where the server failed to start on one or both protocols.
|
||||||
* @param {boolean} v6Failed If the server failed to start on IPv6
|
* @param {boolean} v6Failed If the server failed to start on IPv6
|
||||||
* @param {boolean} v4Failed If the server failed to start on IPv4
|
* @param {boolean} v4Failed If the server failed to start on IPv4
|
||||||
|
* @param {boolean} useIPv6 If use IPv6
|
||||||
|
* @param {boolean} useIPv4 If use IPv4
|
||||||
*/
|
*/
|
||||||
function handleServerListenFail(v6Failed, v4Failed) {
|
function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) {
|
||||||
if (v6Failed && !enableIPv4) {
|
if (v6Failed && !useIPv4) {
|
||||||
console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled'));
|
console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v4Failed && !enableIPv6) {
|
if (v4Failed && !useIPv6) {
|
||||||
console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled'));
|
console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled'));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -839,10 +935,11 @@ function handleServerListenFail(v6Failed, v4Failed) {
|
|||||||
/**
|
/**
|
||||||
* Creates an HTTPS server.
|
* Creates an HTTPS server.
|
||||||
* @param {URL} url The URL to listen on
|
* @param {URL} url The URL to listen on
|
||||||
|
* @param {number} ipVersion the ip version to use
|
||||||
* @returns {Promise<void>} A promise that resolves when the server is listening
|
* @returns {Promise<void>} A promise that resolves when the server is listening
|
||||||
* @throws {Error} If the server fails to start
|
* @throws {Error} If the server fails to start
|
||||||
*/
|
*/
|
||||||
function createHttpsServer(url) {
|
function createHttpsServer(url, ipVersion) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const server = https.createServer(
|
const server = https.createServer(
|
||||||
{
|
{
|
||||||
@ -851,34 +948,56 @@ function createHttpsServer(url) {
|
|||||||
}, app);
|
}, app);
|
||||||
server.on('error', reject);
|
server.on('error', reject);
|
||||||
server.on('listening', resolve);
|
server.on('listening', resolve);
|
||||||
server.listen(Number(url.port || 443), url.hostname);
|
|
||||||
|
let host = url.hostname;
|
||||||
|
if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname);
|
||||||
|
server.listen({
|
||||||
|
host: host,
|
||||||
|
port: Number(url.port || 443),
|
||||||
|
// see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used
|
||||||
|
ipv6Only: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an HTTP server.
|
* Creates an HTTP server.
|
||||||
* @param {URL} url The URL to listen on
|
* @param {URL} url The URL to listen on
|
||||||
|
* @param {number} ipVersion the ip version to use
|
||||||
* @returns {Promise<void>} A promise that resolves when the server is listening
|
* @returns {Promise<void>} A promise that resolves when the server is listening
|
||||||
* @throws {Error} If the server fails to start
|
* @throws {Error} If the server fails to start
|
||||||
*/
|
*/
|
||||||
function createHttpServer(url) {
|
function createHttpServer(url, ipVersion) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
server.on('error', reject);
|
server.on('error', reject);
|
||||||
server.on('listening', resolve);
|
server.on('listening', resolve);
|
||||||
server.listen(Number(url.port || 80), url.hostname);
|
|
||||||
|
let host = url.hostname;
|
||||||
|
if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname);
|
||||||
|
server.listen({
|
||||||
|
host: host,
|
||||||
|
port: Number(url.port || 80),
|
||||||
|
// see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used
|
||||||
|
ipv6Only: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startHTTPorHTTPS() {
|
/**
|
||||||
|
* Starts the server using http or https depending on config
|
||||||
|
* @param {boolean} useIPv6 If use IPv6
|
||||||
|
* @param {boolean} useIPv4 If use IPv4
|
||||||
|
*/
|
||||||
|
async function startHTTPorHTTPS(useIPv6, useIPv4) {
|
||||||
let v6Failed = false;
|
let v6Failed = false;
|
||||||
let v4Failed = false;
|
let v4Failed = false;
|
||||||
|
|
||||||
const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer;
|
const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer;
|
||||||
|
|
||||||
if (enableIPv6) {
|
if (useIPv6) {
|
||||||
try {
|
try {
|
||||||
await createFunc(tavernUrlV6);
|
await createFunc(tavernUrlV6, 6);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('non-fatal error: failed to start server on IPv6');
|
console.error('non-fatal error: failed to start server on IPv6');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -887,9 +1006,9 @@ async function startHTTPorHTTPS() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableIPv4) {
|
if (useIPv4) {
|
||||||
try {
|
try {
|
||||||
await createFunc(tavernUrl);
|
await createFunc(tavernUrl, 4);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('non-fatal error: failed to start server on IPv4');
|
console.error('non-fatal error: failed to start server on IPv4');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -902,10 +1021,59 @@ async function startHTTPorHTTPS() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
const [v6Failed, v4Failed] = await startHTTPorHTTPS();
|
let useIPv6 = (enableIPv6 === true);
|
||||||
|
let useIPv4 = (enableIPv4 === true);
|
||||||
|
|
||||||
handleServerListenFail(v6Failed, v4Failed);
|
let hasIPv6 = false,
|
||||||
postSetupTasks(v6Failed, v4Failed);
|
hasIPv4 = false,
|
||||||
|
hasIPv6Local = false,
|
||||||
|
hasIPv4Local = false,
|
||||||
|
hasIPv6Any = false,
|
||||||
|
hasIPv4Any = false;
|
||||||
|
|
||||||
|
if (enableIPv6 === 'auto' || enableIPv4 === 'auto') {
|
||||||
|
[hasIPv6Any, hasIPv4Any, hasIPv6Local, hasIPv4Local] = await getHasIP();
|
||||||
|
|
||||||
|
hasIPv6 = listen ? hasIPv6Any : hasIPv6Local;
|
||||||
|
if (enableIPv6 === 'auto') {
|
||||||
|
useIPv6 = hasIPv6;
|
||||||
|
}
|
||||||
|
if (hasIPv6) {
|
||||||
|
if (useIPv6) {
|
||||||
|
console.log(color.green('IPv6 support detected'));
|
||||||
|
} else {
|
||||||
|
console.log('IPv6 support detected (but disabled)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasIPv4 = listen ? hasIPv4Any : hasIPv4Local;
|
||||||
|
if (enableIPv4 === 'auto') {
|
||||||
|
useIPv4 = hasIPv4;
|
||||||
|
}
|
||||||
|
if (hasIPv4) {
|
||||||
|
if (useIPv4) {
|
||||||
|
console.log(color.green('IPv4 support detected'));
|
||||||
|
} else {
|
||||||
|
console.log('IPv4 support detected (but disabled)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableIPv6 === 'auto' && enableIPv4 === 'auto') {
|
||||||
|
if (!hasIPv6 && !hasIPv4) {
|
||||||
|
console.error('Both IPv6 and IPv4 are not detected');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useIPv6 && !useIPv4) {
|
||||||
|
console.error('Both IPv6 and IPv4 are disabled,\nP.S. you should never see this error, at least at one point it was checked for before this, with the rest of the config options');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4);
|
||||||
|
handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4);
|
||||||
|
postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifySecuritySettings() {
|
async function verifySecuritySettings() {
|
||||||
|
65
src/util.js
65
src/util.js
@ -5,6 +5,7 @@ import process from 'node:process';
|
|||||||
import { Readable } from 'node:stream';
|
import { Readable } from 'node:stream';
|
||||||
import { createRequire } from 'node:module';
|
import { createRequire } from 'node:module';
|
||||||
import { Buffer } from 'node:buffer';
|
import { Buffer } from 'node:buffer';
|
||||||
|
import { promises as dnsPromise } from 'node:dns';
|
||||||
|
|
||||||
import yaml from 'yaml';
|
import yaml from 'yaml';
|
||||||
import { sync as commandExistsSync } from 'command-exists';
|
import { sync as commandExistsSync } from 'command-exists';
|
||||||
@ -694,6 +695,70 @@ export function isValidUrl(url) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* removes starting `[` or ending `]` from hostname.
|
||||||
|
* @param {string} hostname hostname to use
|
||||||
|
* @returns {string} hostname plus the modifications
|
||||||
|
*/
|
||||||
|
export function urlHostnameToIPv6(hostname) {
|
||||||
|
if (hostname.startsWith('[')) {
|
||||||
|
hostname = hostname.slice(1);
|
||||||
|
}
|
||||||
|
if (hostname.endsWith(']')) {
|
||||||
|
hostname = hostname.slice(0, -1);
|
||||||
|
}
|
||||||
|
return hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if can resolve a dns name.
|
||||||
|
* @param {string} name Domain name to use
|
||||||
|
* @param {boolean} useIPv6 If use IPv6
|
||||||
|
* @param {boolean} useIPv4 If use IPv4
|
||||||
|
* @returns Promise<boolean> If the URL is valid
|
||||||
|
*/
|
||||||
|
export async function canResolve(name, useIPv6 = true, useIPv4 = true) {
|
||||||
|
try {
|
||||||
|
let v6Resolved = false;
|
||||||
|
let v4Resolved = false;
|
||||||
|
|
||||||
|
if (useIPv6) {
|
||||||
|
try {
|
||||||
|
await dnsPromise.resolve6(name);
|
||||||
|
v6Resolved = true;
|
||||||
|
} catch (error) {
|
||||||
|
v6Resolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useIPv4) {
|
||||||
|
try {
|
||||||
|
await dnsPromise.resolve(name);
|
||||||
|
v4Resolved = true;
|
||||||
|
} catch (error) {
|
||||||
|
v4Resolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v6Resolved || v4Resolved;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts string to boolean accepts 'true' or 'false' else it returns the string put in
|
||||||
|
* @param {string|null} str Input string or null
|
||||||
|
* @returns {boolean|string|null} boolean else original input string or null if input is
|
||||||
|
*/
|
||||||
|
export function stringToBool(str) {
|
||||||
|
if (str === 'true') return true;
|
||||||
|
if (str === 'false') return false;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the minimum log level
|
* Setup the minimum log level
|
||||||
*/
|
*/
|
||||||
|
Reference in New Issue
Block a user