Merge pull request #3431 from SillyTavern/ipv6_auto Better IPv6 support
Signed| @@ -7,6 +7,8 @@ cardsCacheCapacity: 100 | ||
| 7 | 7 | # Listen for incoming connections |
| 8 | 8 | listen: false |
| 9 | 9 | # Enables IPv6 and/or IPv4 protocols. Need to have at least one enabled! |
| 10 | +# - Use option "auto" to automatically detect support | |
| 11 | +# - Use true or false (no qoutes) to enable or disable each protocol | |
| 10 | 12 | protocol: |
| 11 | 13 | ipv4: true |
| 12 | 14 | ipv6: false |
| @@ -4,6 +4,7 @@ | ||
| 4 | 4 | import fs from 'node:fs'; |
| 5 | 5 | import http from 'node:http'; |
| 6 | 6 | import https from 'node:https'; |
| 7 | +import os from 'os'; | |
| 7 | 8 | import path from 'node:path'; |
| 8 | 9 | import util from 'node:util'; |
| 9 | 10 | import net from 'node:net'; |
| @@ -65,6 +66,9 @@ import { | ||
| 65 | 66 | forwardFetchResponse, |
| 66 | 67 | removeColorFormatting, |
| 67 | 68 | getSeparator, |
| 69 | + stringToBool, | |
| 70 | + urlHostnameToIPv6, | |
| 71 | + canResolve, | |
| 68 | 72 | safeReadFileSync, |
| 69 | 73 | setupLogLevel, |
| 70 | 74 | } from './src/util.js'; |
| @@ -150,11 +154,11 @@ const DEFAULT_PROXY_BYPASS = []; | ||
| 150 | 154 | const cliArguments = yargs(hideBin(process.argv)) |
| 151 | 155 | .usage('Usage: <your-start-script> <command> [options]') |
| 152 | 156 | .option('enableIPv6', { |
| 153 | 157 | type: 'booleanstring', |
| 154 | 158 | default: null, |
| 155 | 159 | describe: `Enables IPv6.\n[config default: ${DEFAULT_ENABLE_IPV6}]`, |
| 156 | 160 | }).option('enableIPv4', { |
| 157 | 161 | type: 'booleanstring', |
| 158 | 162 | default: null, |
| 159 | 163 | describe: `Enables IPv4.\n[config default: ${DEFAULT_ENABLE_IPV4}]`, |
| 160 | 164 | }).option('port', { |
| @@ -243,27 +247,42 @@ app.use(helmet({ | ||
| 243 | 247 | app.use(compression()); |
| 244 | 248 | app.use(responseTime()); |
| 245 | 249 | |
| 250 | + | |
| 251 | +/** @type {number} */ | |
| 246 | 252 | const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT); |
| 253 | +/** @type {boolean} */ | |
| 247 | 254 | const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl; |
| 255 | +/** @type {boolean} */ | |
| 248 | 256 | const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN); |
| 257 | +/** @type {boolean} */ | |
| 249 | 258 | const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY); |
| 250 | 259 | const enableWhitelist = cliArguments.whitelist ?? getConfigValue('whitelistMode', DEFAULT_WHITELIST); |
| 260 | +/** @type {string} */ | |
| 251 | 261 | const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data'); |
| 262 | +/** @type {boolean} */ | |
| 252 | 263 | const disableCsrf = cliArguments.disableCsrf ?? getConfigValue('disableCsrfProtection', DEFAULT_CSRF_DISABLED); |
| 253 | 264 | const basicAuthMode = cliArguments.basicAuthMode ?? getConfigValue('basicAuthMode', DEFAULT_BASIC_AUTH); |
| 254 | 265 | const perUserBasicAuth = getConfigValue('perUserBasicAuth', DEFAULT_PER_USER_BASIC_AUTH); |
| 266 | +/** @type {boolean} */ | |
| 255 | 267 | const enableAccounts = getConfigValue('enableUserAccounts', DEFAULT_ACCOUNTS); |
| 256 | 268 | |
| 257 | 269 | const uploadsPath = path.join(dataRoot, UPLOADS_DIRECTORY); |
| 258 | 270 | |
| 259 | -const enableIPv6 = cliArguments.enableIPv6 ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); | |
| 260 | -const enableIPv4 = cliArguments.enableIPv4 ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); | |
| 261 | 271 | |
| 272 | +/** @type {boolean | "auto"} */ | |
| 273 | +let enableIPv6 = stringToBool(cliArguments.enableIPv6) ?? getConfigValue('protocol.ipv6', DEFAULT_ENABLE_IPV6); | |
| 274 | +/** @type {boolean | "auto"} */ | |
| 275 | +let enableIPv4 = stringToBool(cliArguments.enableIPv4) ?? getConfigValue('protocol.ipv4', DEFAULT_ENABLE_IPV4); | |
| 276 | + | |
| 277 | +/** @type {string} */ | |
| 262 | 278 | const autorunHostname = cliArguments.autorunHostname ?? getConfigValue('autorunHostname', DEFAULT_AUTORUN_HOSTNAME); |
| 279 | +/** @type {number} */ | |
| 263 | 280 | const autorunPortOverride = cliArguments.autorunPortOverride ?? getConfigValue('autorunPortOverride', DEFAULT_AUTORUN_PORT); |
| 264 | 281 | |
| 282 | +/** @type {boolean} */ | |
| 265 | 283 | const dnsPreferIPv6 = cliArguments.dnsPreferIPv6 ?? getConfigValue('dnsPreferIPv6', DEFAULT_PREFER_IPV6); |
| 266 | 284 | |
| 285 | +/** @type {boolean} */ | |
| 267 | 286 | const avoidLocalhost = cliArguments.avoidLocalhost ?? getConfigValue('avoidLocalhost', DEFAULT_AVOID_LOCALHOST); |
| 268 | 287 | |
| 269 | 288 | const proxyEnabled = cliArguments.requestProxyEnabled ?? getConfigValue('requestProxy.enabled', DEFAULT_PROXY_ENABLED); |
| @@ -280,7 +299,19 @@ if (dnsPreferIPv6) { | ||
| 280 | 299 | console.log('Preferring IPv4 for DNS resolution'); |
| 281 | 300 | } |
| 282 | 301 | |
| 283 | -if (!enableIPv6 && !enableIPv4) { | |
| 302 | + | |
| 303 | +const ipOptions = [true, 'auto', false]; | |
| 304 | + | |
| 305 | +if (!ipOptions.includes(enableIPv6)) { | |
| 306 | + console.warn(color.red('`protocol: ipv6` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV6); | |
| 307 | + enableIPv6 = DEFAULT_ENABLE_IPV6; | |
| 308 | +} | |
| 309 | +if (!ipOptions.includes(enableIPv4)) { | |
| 310 | + console.warn(color.red('`protocol: ipv4` option invalid'), '\n use:', ipOptions, '\n setting to:', DEFAULT_ENABLE_IPV4); | |
| 311 | + enableIPv4 = DEFAULT_ENABLE_IPV4; | |
| 312 | +} | |
| 313 | + | |
| 314 | +if (enableIPv6 === false && enableIPv4 === false) { | |
| 284 | 315 | console.error('error: You can\'t disable all internet protocols: at least IPv6 or IPv4 must be enabled.'); |
| 285 | 316 | process.exit(1); |
| 286 | 317 | } |
| @@ -365,6 +396,55 @@ function getSessionCookieAge() { | ||
| 365 | 396 | return undefined; |
| 366 | 397 | } |
| 367 | 398 | |
| 399 | +/** | |
| 400 | + * Checks the network interfaces to determine the presence of IPv6 and IPv4 addresses. | |
| 401 | + * | |
| 402 | + * @returns {Promise<[boolean, boolean, boolean, boolean]>} A promise that resolves to an array containing: | |
| 403 | + * - [0]: `hasIPv6` (boolean) - Whether the computer has any IPv6 address, including (`::1`). | |
| 404 | + * - [1]: `hasIPv4` (boolean) - Whether the computer has any IPv4 address, including (`127.0.0.1`). | |
| 405 | + * - [2]: `hasIPv6Local` (boolean) - Whether the computer has local IPv6 address (`::1`). | |
| 406 | + * - [3]: `hasIPv4Local` (boolean) - Whether the computer has local IPv4 address (`127.0.0.1`). | |
| 407 | + */ | |
| 408 | +async function getHasIP() { | |
| 409 | + let hasIPv6 = false; | |
| 410 | + let hasIPv6Local = false; | |
| 411 | + | |
| 412 | + let hasIPv4 = false; | |
| 413 | + let hasIPv4Local = false; | |
| 414 | + | |
| 415 | + const interfaces = os.networkInterfaces(); | |
| 416 | + | |
| 417 | + for (const iface of Object.values(interfaces)) { | |
| 418 | + if (iface === undefined) { | |
| 419 | + continue; | |
| 420 | + } | |
| 421 | + | |
| 422 | + for (const info of iface) { | |
| 423 | + if (info.family === 'IPv6') { | |
| 424 | + hasIPv6 = true; | |
| 425 | + if (info.address === '::1') { | |
| 426 | + hasIPv6Local = true; | |
| 427 | + } | |
| 428 | + } | |
| 429 | + | |
| 430 | + if (info.family === 'IPv4') { | |
| 431 | + hasIPv4 = true; | |
| 432 | + if (info.address === '127.0.0.1') { | |
| 433 | + hasIPv4Local = true; | |
| 434 | + } | |
| 435 | + } | |
| 436 | + if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break; | |
| 437 | + } | |
| 438 | + if (hasIPv6 && hasIPv4 && hasIPv6Local && hasIPv4Local) break; | |
| 439 | + } | |
| 440 | + return [ | |
| 441 | + hasIPv6, | |
| 442 | + hasIPv4, | |
| 443 | + hasIPv6Local, | |
| 444 | + hasIPv4Local, | |
| 445 | + ]; | |
| 446 | +} | |
| 447 | + | |
| 368 | 448 | app.use(cookieSession({ |
| 369 | 449 | name: getCookieSessionName(), |
| 370 | 450 | sameSite: 'strict', |
| @@ -694,21 +774,23 @@ const preSetupTasks = async function () { | ||
| 694 | 774 | |
| 695 | 775 | /** |
| 696 | 776 | * Gets the hostname to use for autorun in the browser. |
| 697 | 777 | * @returnsparam {stringboolean} The hostnameuseIPv6 toIf use for autorunIPv6 |
| 778 | + * @param {boolean} useIPv4 If use IPv4 | |
| 779 | + * @returns Promise<string> The hostname to use for autorun | |
| 698 | 780 | */ |
| 699 | 781 | async function getAutorunHostname(useIPv6, useIPv4) { |
| 700 | 782 | if (autorunHostname === 'auto') { |
| 701 | - if (enableIPv6 && enableIPv4) { | |
| 783 | + let localhostResolve = await canResolve('localhost', useIPv6, useIPv4); | |
| 702 | - return avoidLocalhost | |
| 784 | + | |
| 703 | - ? '[::1]' | |
| 785 | + if (useIPv6 && useIPv4) { | |
| 704 | - : 'localhost'; | |
| 786 | + return (avoidLocalhost || !localhostResolve) ? '[::1]' : 'localhost'; | |
| 705 | 787 | } |
| 706 | 788 | |
| 707 | 789 | if (enableIPv6useIPv6) { |
| 708 | 790 | return '[::1]'; |
| 709 | 791 | } |
| 710 | 792 | |
| 711 | 793 | if (enableIPv4useIPv4) { |
| 712 | 794 | return '127.0.0.1'; |
| 713 | 795 | } |
| 714 | 796 | } |
| @@ -720,11 +802,13 @@ function getAutorunHostname() { | ||
| 720 | 802 | * Tasks that need to be run after the server starts listening. |
| 721 | 803 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 722 | 804 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 805 | + * @param {boolean} useIPv6 If the server is using IPv6 | |
| 806 | + * @param {boolean} useIPv4 If the server is using IPv4 | |
| 723 | 807 | */ |
| 724 | 808 | const postSetupTasks = async function (v6Failed, v4Failed, useIPv6, useIPv4) { |
| 725 | 809 | const autorunUrl = new URL( |
| 726 | 810 | (cliArguments.ssl ? 'https://' : 'http://') + |
| 727 | 811 | (await getAutorunHostname(useIPv6, useIPv4)) + |
| 728 | 812 | (':') + |
| 729 | 813 | ((autorunPortOverride >= 0) ? autorunPortOverride : server_port), |
| 730 | 814 | ); |
| @@ -737,12 +821,16 @@ const postSetupTasks = async function (v6Failed, v4Failed) { | ||
| 737 | 821 | |
| 738 | 822 | let logListen = 'SillyTavern is listening on'; |
| 739 | 823 | |
| 740 | 824 | if (enableIPv6useIPv6 && !v6Failed) { |
| 741 | 825 | logListen += color.green(' IPv6: ' + tavernUrlV6.host); |
| 826 | + ' IPv6: ' + tavernUrlV6.host, | |
| 827 | + ); | |
| 742 | 828 | } |
| 743 | 829 | |
| 744 | 830 | if (enableIPv4useIPv4 && !v4Failed) { |
| 745 | 831 | logListen += color.green(' IPv4: ' + tavernUrl.host); |
| 832 | + ' IPv4: ' + tavernUrl.host, | |
| 833 | + ); | |
| 746 | 834 | } |
| 747 | 835 | |
| 748 | 836 | const goToLog = 'Go to: ' + color.blue(autorunUrl) + ' to open SillyTavern'; |
| @@ -754,16 +842,22 @@ const postSetupTasks = async function (v6Failed, v4Failed) { | ||
| 754 | 842 | console.log('\n' + getSeparator(plainGoToLog.length) + '\n'); |
| 755 | 843 | |
| 756 | 844 | if (listen) { |
| 757 | - 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'); | |
| 845 | + console.log( | |
| 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', | |
| 847 | + ); | |
| 758 | 848 | } |
| 759 | 849 | |
| 760 | 850 | if (basicAuthMode) { |
| 761 | 851 | if (perUserBasicAuth && !enableAccounts) { |
| 762 | - console.error(color.red('Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.')); | |
| 852 | + console.error(color.red( | |
| 853 | + 'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.', | |
| 854 | + )); | |
| 763 | 855 | } else if (!perUserBasicAuth) { |
| 764 | 856 | const basicAuthUser = getConfigValue('basicAuthUser', {}); |
| 765 | 857 | if (!basicAuthUser?.username || !basicAuthUser?.password) { |
| 766 | - console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!')); | |
| 858 | + console.warn(color.yellow( | |
| 859 | + 'Basic Authentication is enabled, but username or password is not set or empty!', | |
| 860 | + )); | |
| 767 | 861 | } |
| 768 | 862 | } |
| 769 | 863 | } |
| @@ -818,14 +912,16 @@ function logSecurityAlert(message) { | ||
| 818 | 912 | * Handles the case where the server failed to start on one or both protocols. |
| 819 | 913 | * @param {boolean} v6Failed If the server failed to start on IPv6 |
| 820 | 914 | * @param {boolean} v4Failed If the server failed to start on IPv4 |
| 915 | + * @param {boolean} useIPv6 If use IPv6 | |
| 916 | + * @param {boolean} useIPv4 If use IPv4 | |
| 821 | 917 | */ |
| 822 | 918 | function handleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4) { |
| 823 | 919 | if (v6Failed && !enableIPv4useIPv4) { |
| 824 | 920 | console.error(color.red('fatal error: Failed to start server on IPv6 and IPv4 disabled')); |
| 825 | 921 | process.exit(1); |
| 826 | 922 | } |
| 827 | 923 | |
| 828 | 924 | if (v4Failed && !enableIPv6useIPv6) { |
| 829 | 925 | console.error(color.red('fatal error: Failed to start server on IPv4 and IPv6 disabled')); |
| 830 | 926 | process.exit(1); |
| 831 | 927 | } |
| @@ -839,10 +935,11 @@ function handleServerListenFail(v6Failed, v4Failed) { | ||
| 839 | 935 | /** |
| 840 | 936 | * Creates an HTTPS server. |
| 841 | 937 | * @param {URL} url The URL to listen on |
| 938 | + * @param {number} ipVersion the ip version to use | |
| 842 | 939 | * @returns {Promise<void>} A promise that resolves when the server is listening |
| 843 | 940 | * @throws {Error} If the server fails to start |
| 844 | 941 | */ |
| 845 | 942 | function createHttpsServer(url, ipVersion) { |
| 846 | 943 | return new Promise((resolve, reject) => { |
| 847 | 944 | const server = https.createServer( |
| 848 | 945 | { |
| @@ -851,34 +948,56 @@ function createHttpsServer(url) { | ||
| 851 | 948 | }, app); |
| 852 | 949 | server.on('error', reject); |
| 853 | 950 | server.on('listening', resolve); |
| 854 | - server.listen(Number(url.port || 443), url.hostname); | |
| 951 | + | |
| 952 | + let host = url.hostname; | |
| 953 | + if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname); | |
| 954 | + server.listen({ | |
| 955 | + host: host, | |
| 956 | + port: Number(url.port || 443), | |
| 957 | + // see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used | |
| 958 | + ipv6Only: true, | |
| 959 | + }); | |
| 855 | 960 | }); |
| 856 | 961 | } |
| 857 | 962 | |
| 858 | 963 | /** |
| 859 | 964 | * Creates an HTTP server. |
| 860 | 965 | * @param {URL} url The URL to listen on |
| 966 | + * @param {number} ipVersion the ip version to use | |
| 861 | 967 | * @returns {Promise<void>} A promise that resolves when the server is listening |
| 862 | 968 | * @throws {Error} If the server fails to start |
| 863 | 969 | */ |
| 864 | 970 | function createHttpServer(url, ipVersion) { |
| 865 | 971 | return new Promise((resolve, reject) => { |
| 866 | 972 | const server = http.createServer(app); |
| 867 | 973 | server.on('error', reject); |
| 868 | 974 | server.on('listening', resolve); |
| 869 | - server.listen(Number(url.port || 80), url.hostname); | |
| 975 | + | |
| 976 | + let host = url.hostname; | |
| 977 | + if (ipVersion === 6) host = urlHostnameToIPv6(url.hostname); | |
| 978 | + server.listen({ | |
| 979 | + host: host, | |
| 980 | + port: Number(url.port || 80), | |
| 981 | + // see https://nodejs.org/api/net.html#serverlisten for why ipv6Only is used | |
| 982 | + ipv6Only: true, | |
| 983 | + }); | |
| 870 | 984 | }); |
| 871 | 985 | } |
| 872 | 986 | |
| 873 | -async function startHTTPorHTTPS() { | |
| 987 | +/** | |
| 988 | + * Starts the server using http or https depending on config | |
| 989 | + * @param {boolean} useIPv6 If use IPv6 | |
| 990 | + * @param {boolean} useIPv4 If use IPv4 | |
| 991 | + */ | |
| 992 | +async function startHTTPorHTTPS(useIPv6, useIPv4) { | |
| 874 | 993 | let v6Failed = false; |
| 875 | 994 | let v4Failed = false; |
| 876 | 995 | |
| 877 | 996 | const createFunc = cliArguments.ssl ? createHttpsServer : createHttpServer; |
| 878 | 997 | |
| 879 | 998 | if (enableIPv6useIPv6) { |
| 880 | 999 | try { |
| 881 | 1000 | await createFunc(tavernUrlV6, 6); |
| 882 | 1001 | } catch (error) { |
| 883 | 1002 | console.error('non-fatal error: failed to start server on IPv6'); |
| 884 | 1003 | console.error(error); |
| @@ -887,9 +1006,9 @@ async function startHTTPorHTTPS() { | ||
| 887 | 1006 | } |
| 888 | 1007 | } |
| 889 | 1008 | |
| 890 | 1009 | if (enableIPv4useIPv4) { |
| 891 | 1010 | try { |
| 892 | 1011 | await createFunc(tavernUrl, 4); |
| 893 | 1012 | } catch (error) { |
| 894 | 1013 | console.error('non-fatal error: failed to start server on IPv4'); |
| 895 | 1014 | console.error(error); |
| @@ -902,10 +1021,59 @@ async function startHTTPorHTTPS() { | ||
| 902 | 1021 | } |
| 903 | 1022 | |
| 904 | 1023 | async function startServer() { |
| 905 | - const [v6Failed, v4Failed] = await startHTTPorHTTPS(); | |
| 1024 | + let useIPv6 = (enableIPv6 === true); | |
| 1025 | + let useIPv4 = (enableIPv4 === true); | |
| 1026 | + | |
| 1027 | + let hasIPv6 = false, | |
| 1028 | + hasIPv4 = false, | |
| 1029 | + hasIPv6Local = false, | |
| 1030 | + hasIPv4Local = false, | |
| 1031 | + hasIPv6Any = false, | |
| 1032 | + hasIPv4Any = false; | |
| 1033 | + | |
| 1034 | + if (enableIPv6 === 'auto' || enableIPv4 === 'auto') { | |
| 1035 | + [hasIPv6Any, hasIPv4Any, hasIPv6Local, hasIPv4Local] = await getHasIP(); | |
| 1036 | + | |
| 1037 | + hasIPv6 = listen ? hasIPv6Any : hasIPv6Local; | |
| 1038 | + if (enableIPv6 === 'auto') { | |
| 1039 | + useIPv6 = hasIPv6; | |
| 1040 | + } | |
| 1041 | + if (hasIPv6) { | |
| 1042 | + if (useIPv6) { | |
| 1043 | + console.log(color.green('IPv6 support detected')); | |
| 1044 | + } else { | |
| 1045 | + console.log('IPv6 support detected (but disabled)'); | |
| 1046 | + } | |
| 1047 | + } | |
| 1048 | + | |
| 1049 | + hasIPv4 = listen ? hasIPv4Any : hasIPv4Local; | |
| 1050 | + if (enableIPv4 === 'auto') { | |
| 1051 | + useIPv4 = hasIPv4; | |
| 1052 | + } | |
| 1053 | + if (hasIPv4) { | |
| 1054 | + if (useIPv4) { | |
| 1055 | + console.log(color.green('IPv4 support detected')); | |
| 1056 | + } else { | |
| 1057 | + console.log('IPv4 support detected (but disabled)'); | |
| 1058 | + } | |
| 1059 | + } | |
| 1060 | + | |
| 1061 | + if (enableIPv6 === 'auto' && enableIPv4 === 'auto') { | |
| 1062 | + if (!hasIPv6 && !hasIPv4) { | |
| 1063 | + console.error('Both IPv6 and IPv4 are not detected'); | |
| 1064 | + process.exit(1); | |
| 1065 | + } | |
| 1066 | + } | |
| 1067 | + } | |
| 1068 | + | |
| 1069 | + if (!useIPv6 && !useIPv4) { | |
| 1070 | + 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'); | |
| 1071 | + process.exit(1); | |
| 1072 | + } | |
| 906 | 1073 | |
| 907 | - handleServerListenFail(v6Failed, v4Failed); | |
| 1074 | + const [v6Failed, v4Failed] = await startHTTPorHTTPS(useIPv6, useIPv4); | |
| 908 | 1075 | postSetupTaskshandleServerListenFail(v6Failed, v4Failed, useIPv6, useIPv4); |
| 1076 | + postSetupTasks(v6Failed, v4Failed, useIPv6, useIPv4); | |
| 909 | 1077 | } |
| 910 | 1078 | |
| 911 | 1079 | async function verifySecuritySettings() { |
| @@ -5,6 +5,7 @@ import process from 'node:process'; | ||
| 5 | 5 | import { Readable } from 'node:stream'; |
| 6 | 6 | import { createRequire } from 'node:module'; |
| 7 | 7 | import { Buffer } from 'node:buffer'; |
| 8 | +import { promises as dnsPromise } from 'node:dns'; | |
| 8 | 9 | |
| 9 | 10 | import yaml from 'yaml'; |
| 10 | 11 | import { sync as commandExistsSync } from 'command-exists'; |
| @@ -695,6 +696,70 @@ export function isValidUrl(url) { | ||
| 695 | 696 | } |
| 696 | 697 | |
| 697 | 698 | /** |
| 699 | + * removes starting `[` or ending `]` from hostname. | |
| 700 | + * @param {string} hostname hostname to use | |
| 701 | + * @returns {string} hostname plus the modifications | |
| 702 | + */ | |
| 703 | +export function urlHostnameToIPv6(hostname) { | |
| 704 | + if (hostname.startsWith('[')) { | |
| 705 | + hostname = hostname.slice(1); | |
| 706 | + } | |
| 707 | + if (hostname.endsWith(']')) { | |
| 708 | + hostname = hostname.slice(0, -1); | |
| 709 | + } | |
| 710 | + return hostname; | |
| 711 | +} | |
| 712 | + | |
| 713 | +/** | |
| 714 | + * Test if can resolve a dns name. | |
| 715 | + * @param {string} name Domain name to use | |
| 716 | + * @param {boolean} useIPv6 If use IPv6 | |
| 717 | + * @param {boolean} useIPv4 If use IPv4 | |
| 718 | + * @returns Promise<boolean> If the URL is valid | |
| 719 | + */ | |
| 720 | +export async function canResolve(name, useIPv6 = true, useIPv4 = true) { | |
| 721 | + try { | |
| 722 | + let v6Resolved = false; | |
| 723 | + let v4Resolved = false; | |
| 724 | + | |
| 725 | + if (useIPv6) { | |
| 726 | + try { | |
| 727 | + await dnsPromise.resolve6(name); | |
| 728 | + v6Resolved = true; | |
| 729 | + } catch (error) { | |
| 730 | + v6Resolved = false; | |
| 731 | + } | |
| 732 | + } | |
| 733 | + | |
| 734 | + if (useIPv4) { | |
| 735 | + try { | |
| 736 | + await dnsPromise.resolve(name); | |
| 737 | + v4Resolved = true; | |
| 738 | + } catch (error) { | |
| 739 | + v4Resolved = false; | |
| 740 | + } | |
| 741 | + } | |
| 742 | + | |
| 743 | + return v6Resolved || v4Resolved; | |
| 744 | + | |
| 745 | + } catch (error) { | |
| 746 | + return false; | |
| 747 | + } | |
| 748 | +} | |
| 749 | + | |
| 750 | + | |
| 751 | +/** | |
| 752 | + * converts string to boolean accepts 'true' or 'false' else it returns the string put in | |
| 753 | + * @param {string|null} str Input string or null | |
| 754 | + * @returns {boolean|string|null} boolean else original input string or null if input is | |
| 755 | + */ | |
| 756 | +export function stringToBool(str) { | |
| 757 | + if (str === 'true') return true; | |
| 758 | + if (str === 'false') return false; | |
| 759 | + return str; | |
| 760 | +} | |
| 761 | + | |
| 762 | +/** | |
| 698 | 763 | * Setup the minimum log level |
| 699 | 764 | */ |
| 700 | 765 | export function setupLogLevel() { |