2023-07-20 19:32:15 +02:00
#!/usr/bin/env node
2023-08-29 23:05:18 +02:00
// native node modules
2024-10-10 23:28:17 +02:00
import fs from 'node:fs' ;
import http from 'node:http' ;
import https from 'node:https' ;
import path from 'node:path' ;
import util from 'node:util' ;
import net from 'node:net' ;
import dns from 'node:dns' ;
2024-10-11 09:43:29 +02:00
import process from 'node:process' ;
2024-10-10 23:06:02 +02:00
import { fileURLToPath } from 'node:url' ;
2023-08-29 23:05:18 +02:00
2023-08-29 23:16:39 +02:00
// cli/fs related library imports
2024-10-10 22:50:51 +02:00
import open from 'open' ;
2024-10-10 21:37:22 +02:00
import yargs from 'yargs/yargs' ;
import { hideBin } from 'yargs/helpers' ;
2023-08-29 23:16:39 +02:00
2023-08-29 23:34:41 +02:00
// express/server related library imports
2024-10-10 21:37:22 +02:00
import cors from 'cors' ;
import { doubleCsrf } from 'csrf-csrf' ;
import express from 'express' ;
import compression from 'compression' ;
import cookieParser from 'cookie-parser' ;
import cookieSession from 'cookie-session' ;
import multer from 'multer' ;
import responseTime from 'response-time' ;
import helmet from 'helmet' ;
import bodyParser from 'body-parser' ;
2023-08-29 23:26:59 +02:00
// net related library imports
2024-10-10 21:37:22 +02:00
import fetch from 'node-fetch' ;
2023-08-29 23:10:40 +02:00
2023-09-10 03:12:14 +02:00
// Unrestrict console logs display limit
util . inspect . defaultOptions . maxArrayLength = null ;
util . inspect . defaultOptions . maxStringLength = null ;
2024-03-30 21:38:09 +01:00
util . inspect . defaultOptions . depth = 4 ;
2023-09-10 03:12:14 +02:00
2023-08-29 23:26:59 +02:00
// local library imports
2024-10-17 13:04:34 +02:00
import { loadPlugins } from './src/plugin-loader.js' ;
2024-10-10 23:41:08 +02:00
import {
initUserStorage ,
getCsrfSecret ,
getCookieSecret ,
getCookieSessionName ,
getAllEnabledUsers ,
ensurePublicDirectoriesExist ,
getUserDirectoriesList ,
migrateSystemPrompts ,
migrateUserData ,
requireLoginMiddleware ,
setUserDataMiddleware ,
shouldRedirectToLogin ,
tryAutoLogin ,
router as userDataRouter ,
} from './src/users.js' ;
2024-10-16 21:00:14 +02:00
import getWebpackServeMiddleware from './src/middleware/webpack-serve.js' ;
2024-10-10 21:37:22 +02:00
import basicAuthMiddleware from './src/middleware/basicAuth.js' ;
import whitelistMiddleware from './src/middleware/whitelist.js' ;
import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js' ;
import initRequestProxy from './src/request-proxy.js' ;
import {
2023-12-08 00:06:17 +01:00
getVersion ,
getConfigValue ,
color ,
forwardFetchResponse ,
2024-08-15 19:29:17 +02:00
removeColorFormatting ,
getSeparator ,
2024-10-10 21:37:22 +02:00
} from './src/util.js' ;
import { UPLOADS _DIRECTORY } from './src/constants.js' ;
import { ensureThumbnailCache } from './src/endpoints/thumbnails.js' ;
// Routers
import { router as usersPublicRouter } from './src/endpoints/users-public.js' ;
import { router as usersPrivateRouter } from './src/endpoints/users-private.js' ;
import { router as usersAdminRouter } from './src/endpoints/users-admin.js' ;
import { router as movingUIRouter } from './src/endpoints/moving-ui.js' ;
import { router as imagesRouter } from './src/endpoints/images.js' ;
import { router as quickRepliesRouter } from './src/endpoints/quick-replies.js' ;
import { router as avatarsRouter } from './src/endpoints/avatars.js' ;
import { router as themesRouter } from './src/endpoints/themes.js' ;
import { router as openAiRouter } from './src/endpoints/openai.js' ;
import { router as googleRouter } from './src/endpoints/google.js' ;
import { router as anthropicRouter } from './src/endpoints/anthropic.js' ;
import { router as tokenizersRouter } from './src/endpoints/tokenizers.js' ;
import { router as presetsRouter } from './src/endpoints/presets.js' ;
import { router as secretsRouter } from './src/endpoints/secrets.js' ;
import { router as thumbnailRouter } from './src/endpoints/thumbnails.js' ;
import { router as novelAiRouter } from './src/endpoints/novelai.js' ;
import { router as extensionsRouter } from './src/endpoints/extensions.js' ;
import { router as assetsRouter } from './src/endpoints/assets.js' ;
import { router as filesRouter } from './src/endpoints/files.js' ;
import { router as charactersRouter } from './src/endpoints/characters.js' ;
import { router as chatsRouter } from './src/endpoints/chats.js' ;
import { router as groupsRouter } from './src/endpoints/groups.js' ;
import { router as worldInfoRouter } from './src/endpoints/worldinfo.js' ;
import { router as statsRouter , init as statsInit , onExit as statsOnExit } from './src/endpoints/stats.js' ;
import { router as backgroundsRouter } from './src/endpoints/backgrounds.js' ;
import { router as spritesRouter } from './src/endpoints/sprites.js' ;
2024-10-10 23:41:08 +02:00
import { router as contentManagerRouter , checkForNewContent } from './src/endpoints/content-manager.js' ;
2024-10-10 21:37:22 +02:00
import { router as settingsRouter , init as settingsInit } from './src/endpoints/settings.js' ;
import { router as stableDiffusionRouter } from './src/endpoints/stable-diffusion.js' ;
import { router as hordeRouter } from './src/endpoints/horde.js' ;
import { router as vectorsRouter } from './src/endpoints/vectors.js' ;
import { router as translateRouter } from './src/endpoints/translate.js' ;
import { router as classifyRouter } from './src/endpoints/classify.js' ;
import { router as captionRouter } from './src/endpoints/caption.js' ;
import { router as searchRouter } from './src/endpoints/search.js' ;
import { router as openRouterRouter } from './src/endpoints/openrouter.js' ;
import { router as chatCompletionsRouter } from './src/endpoints/backends/chat-completions.js' ;
import { router as koboldRouter } from './src/endpoints/backends/kobold.js' ;
import { router as textCompletionsRouter } from './src/endpoints/backends/text-completions.js' ;
import { router as scaleAltRouter } from './src/endpoints/backends/scale-alt.js' ;
import { router as speechRouter } from './src/endpoints/speech.js' ;
import { router as azureRouter } from './src/endpoints/azure.js' ;
2023-08-29 23:12:47 +02:00
2023-09-10 17:22:39 +02:00
// 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 ) ;
}
2023-07-20 19:32:15 +02:00
2024-03-29 02:20:16 +01:00
const DEFAULT _PORT = 8000 ;
const DEFAULT _AUTORUN = false ;
const DEFAULT _LISTEN = false ;
const DEFAULT _CORS _PROXY = false ;
2024-04-12 00:33:39 +02:00
const DEFAULT _WHITELIST = true ;
2024-04-13 18:35:27 +02:00
const DEFAULT _ACCOUNTS = false ;
const DEFAULT _CSRF _DISABLED = false ;
const DEFAULT _BASIC _AUTH = false ;
2024-10-09 00:37:34 +02:00
const DEFAULT _PER _USER _BASIC _AUTH = false ;
2024-03-29 02:20:16 +01:00
2024-08-15 19:12:12 +02:00
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 ;
2024-09-12 19:25:58 +02:00
const DEFAULT _PROXY _ENABLED = false ;
const DEFAULT _PROXY _URL = '' ;
const DEFAULT _PROXY _BYPASS = [ ] ;
2023-07-20 19:32:15 +02:00
const cliArguments = yargs ( hideBin ( process . argv ) )
2024-03-29 02:20:16 +01:00
. usage ( 'Usage: <your-start-script> <command> [options]' )
2024-08-15 19:12:12 +02:00
. 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' , {
2024-03-29 02:20:16 +01:00
type : 'number' ,
default : null ,
describe : ` Sets the port under which SillyTavern will run. \n If not provided falls back to yaml config 'port'. \n [config default: ${ DEFAULT _PORT } ] ` ,
2024-08-15 19:12:12 +02:00
} ) . option ( 'dnsPreferIPv6' , {
type : 'boolean' ,
default : null ,
describe : ` Prefers IPv6 for dns \n you should probably have the enabled if you're on an IPv6 only network \n If not provided falls back to yaml config 'preferIPv6'. \n [config default: ${ DEFAULT _PREFER _IPV6 } ] ` ,
2024-03-29 02:20:16 +01:00
} ) . option ( 'autorun' , {
2023-11-18 01:09:42 +01:00
type : 'boolean' ,
2024-03-29 02:20:16 +01:00
default : null ,
describe : ` Automatically launch SillyTavern in the browser. \n Autorun is automatically disabled if --ssl is set to true. \n If not provided falls back to yaml config 'autorun'. \n [config default: ${ DEFAULT _AUTORUN } ] ` ,
2024-08-15 19:12:12 +02:00
} ) . 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' ,
2024-03-29 02:20:16 +01:00
} ) . option ( 'listen' , {
type : 'boolean' ,
default : null ,
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). \n If not provided falls back to yaml config 'listen'. \n [config default: ${ DEFAULT _LISTEN } ] ` ,
2023-11-25 20:56:57 +01:00
} ) . option ( 'corsProxy' , {
type : 'boolean' ,
2024-03-29 02:20:16 +01:00
default : null ,
describe : ` Enables CORS proxy \n If not provided falls back to yaml config 'enableCorsProxy'. \n [config default: ${ DEFAULT _CORS _PROXY } ] ` ,
2023-11-25 20:56:57 +01:00
} ) . option ( 'disableCsrf' , {
2023-08-06 15:42:15 +02:00
type : 'boolean' ,
2024-04-13 18:35:27 +02:00
default : null ,
2023-12-02 21:06:57 +01:00
describe : 'Disables CSRF protection' ,
2023-08-06 15:42:15 +02:00
} ) . option ( 'ssl' , {
2023-07-20 19:32:15 +02:00
type : 'boolean' ,
default : false ,
2023-12-02 21:06:57 +01:00
describe : 'Enables SSL' ,
2023-07-20 19:32:15 +02:00
} ) . option ( 'certPath' , {
type : 'string' ,
default : 'certs/cert.pem' ,
2023-12-02 21:06:57 +01:00
describe : 'Path to your certificate file.' ,
2023-07-20 19:32:15 +02:00
} ) . option ( 'keyPath' , {
type : 'string' ,
default : 'certs/privkey.pem' ,
2023-12-02 21:06:57 +01:00
describe : 'Path to your private key file.' ,
2024-04-12 00:33:39 +02:00
} ) . option ( 'whitelist' , {
type : 'boolean' ,
2024-04-13 01:23:38 +02:00
default : null ,
2024-04-12 00:33:39 +02:00
describe : 'Enables whitelist mode' ,
2024-04-12 18:53:46 +02:00
} ) . option ( 'dataRoot' , {
type : 'string' ,
default : null ,
describe : 'Root directory for data storage' ,
2024-08-15 19:12:12 +02:00
} ) . 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' ,
2024-04-13 18:35:27 +02:00
} ) . option ( 'basicAuthMode' , {
type : 'boolean' ,
default : null ,
describe : 'Enables basic authentication' ,
2024-09-12 19:25:58 +02:00
} ) . option ( 'requestProxyEnabled' , {
type : 'boolean' ,
default : null ,
2024-09-12 19:30:52 +02:00
describe : 'Enables a use of proxy for outgoing requests' ,
2024-09-12 19:25:58 +02:00
} ) . option ( 'requestProxyUrl' , {
type : 'string' ,
default : null ,
2024-09-12 19:29:46 +02:00
describe : 'Request proxy URL (HTTP or SOCKS protocols)' ,
} ) . option ( 'requestProxyBypass' , {
2024-09-12 19:25:58 +02:00
type : 'array' ,
default : null ,
2024-09-12 19:29:46 +02:00
describe : 'Request proxy bypass list (space separated list of hosts)' ,
2023-08-30 20:34:45 +02:00
} ) . parseSync ( ) ;
2023-07-20 19:32:15 +02:00
// change all relative paths
2024-10-10 23:06:02 +02:00
const serverDirectory = import . meta . dirname ? ? path . dirname ( fileURLToPath ( import . meta . url ) ) ;
2024-10-10 21:37:22 +02:00
console . log ( ` Node version: ${ process . version } . Running in ${ process . env . NODE _ENV } environment. Server directory: ${ serverDirectory } ` ) ;
2023-12-16 21:21:40 +01:00
process . chdir ( serverDirectory ) ;
2023-07-20 19:32:15 +02:00
const app = express ( ) ;
2024-04-07 17:11:23 +02:00
app . use ( helmet ( {
contentSecurityPolicy : false ,
} ) ) ;
2023-07-20 19:32:15 +02:00
app . use ( compression ( ) ) ;
app . use ( responseTime ( ) ) ;
2024-03-29 02:20:16 +01:00
const server _port = cliArguments . port ? ? process . env . SILLY _TAVERN _PORT ? ? getConfigValue ( 'port' , DEFAULT _PORT ) ;
const autorun = ( cliArguments . autorun ? ? getConfigValue ( 'autorun' , DEFAULT _AUTORUN ) ) && ! cliArguments . ssl ;
const listen = cliArguments . listen ? ? getConfigValue ( 'listen' , DEFAULT _LISTEN ) ;
2024-03-30 21:46:18 +01:00
const enableCorsProxy = cliArguments . corsProxy ? ? getConfigValue ( 'enableCorsProxy' , DEFAULT _CORS _PROXY ) ;
2024-04-12 00:33:39 +02:00
const enableWhitelist = cliArguments . whitelist ? ? getConfigValue ( 'whitelistMode' , DEFAULT _WHITELIST ) ;
2024-04-12 18:53:46 +02:00
const dataRoot = cliArguments . dataRoot ? ? getConfigValue ( 'dataRoot' , './data' ) ;
2024-04-13 18:35:27 +02:00
const disableCsrf = cliArguments . disableCsrf ? ? getConfigValue ( 'disableCsrfProtection' , DEFAULT _CSRF _DISABLED ) ;
const basicAuthMode = cliArguments . basicAuthMode ? ? getConfigValue ( 'basicAuthMode' , DEFAULT _BASIC _AUTH ) ;
2024-10-09 00:37:34 +02:00
const perUserBasicAuth = getConfigValue ( 'perUserBasicAuth' , DEFAULT _PER _USER _BASIC _AUTH ) ;
2024-04-13 18:35:27 +02:00
const enableAccounts = getConfigValue ( 'enableUserAccounts' , DEFAULT _ACCOUNTS ) ;
2023-07-20 19:32:15 +02:00
2024-10-10 21:37:22 +02:00
const uploadsPath = path . join ( dataRoot , UPLOADS _DIRECTORY ) ;
2023-07-20 19:32:15 +02:00
2024-08-15 19:12:12 +02:00
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 ) ;
2024-09-12 19:25:58 +02:00
const proxyEnabled = cliArguments . requestProxyEnabled ? ? getConfigValue ( 'requestProxy.enabled' , DEFAULT _PROXY _ENABLED ) ;
const proxyUrl = cliArguments . requestProxyUrl ? ? getConfigValue ( 'requestProxy.url' , DEFAULT _PROXY _URL ) ;
const proxyBypass = cliArguments . requestProxyBypass ? ? getConfigValue ( 'requestProxy.bypass' , DEFAULT _PROXY _BYPASS ) ;
2024-08-15 19:12:12 +02:00
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 ) ;
}
2023-07-20 19:32:15 +02:00
// CORS Settings //
const CORS = cors ( {
origin : 'null' ,
2023-12-02 21:06:57 +01:00
methods : [ 'OPTIONS' ] ,
2023-07-20 19:32:15 +02:00
} ) ;
app . use ( CORS ) ;
2024-03-30 21:46:18 +01:00
if ( listen && basicAuthMode ) app . use ( basicAuthMiddleware ) ;
2023-07-20 19:32:15 +02:00
2024-04-12 00:33:39 +02:00
app . use ( whitelistMiddleware ( enableWhitelist , listen ) ) ;
2023-12-09 18:56:26 +01:00
2024-03-29 02:20:16 +01:00
if ( enableCorsProxy ) {
2023-12-14 03:39:07 +01:00
app . use ( bodyParser . json ( {
limit : '200mb' ,
} ) ) ;
2023-11-25 20:56:57 +01:00
console . log ( 'Enabling CORS proxy' ) ;
2023-11-27 06:17:07 +01:00
app . use ( '/proxy/:url(*)' , async ( req , res ) => {
2023-11-25 20:56:57 +01:00
const url = req . params . url ; // get the url from the request path
// Disallow circular requests
const serverUrl = req . protocol + '://' + req . get ( 'host' ) ;
if ( url . startsWith ( serverUrl ) ) {
return res . status ( 400 ) . send ( 'Circular requests are not allowed' ) ;
}
try {
const headers = JSON . parse ( JSON . stringify ( req . headers ) ) ;
2024-07-22 16:19:20 +02:00
const headersToRemove = [
'x-csrf-token' , 'host' , 'referer' , 'origin' , 'cookie' ,
'x-forwarded-for' , 'x-forwarded-protocol' , 'x-forwarded-proto' ,
'x-forwarded-host' , 'x-real-ip' , 'sec-fetch-mode' ,
'sec-fetch-site' , 'sec-fetch-dest' ,
] ;
headersToRemove . forEach ( header => delete headers [ header ] ) ;
2023-11-25 20:56:57 +01:00
const bodyMethods = [ 'POST' , 'PUT' , 'PATCH' ] ;
const response = await fetch ( url , {
method : req . method ,
headers : headers ,
body : bodyMethods . includes ( req . method ) ? JSON . stringify ( req . body ) : undefined ,
} ) ;
2023-12-02 07:41:00 +01:00
// Copy over relevant response params to the proxy response
2023-12-08 00:06:17 +01:00
forwardFetchResponse ( response , res ) ;
2023-11-25 20:56:57 +01:00
} catch ( error ) {
res . status ( 500 ) . send ( 'Error occurred while trying to proxy to: ' + url + ' ' + error ) ;
}
} ) ;
2023-12-02 15:04:30 +01:00
} else {
app . use ( '/proxy/:url(*)' , async ( _ , res ) => {
const message = 'CORS proxy is disabled. Enable it in config.yaml or use the --corsProxy flag.' ;
console . log ( message ) ;
res . status ( 404 ) . send ( message ) ;
} ) ;
2023-11-25 20:56:57 +01:00
}
2023-07-20 19:32:15 +02:00
2024-07-06 13:50:36 +02:00
function getSessionCookieAge ( ) {
// Defaults to 24 hours in seconds if not set
const configValue = getConfigValue ( 'sessionTimeout' , 24 * 60 * 60 ) ;
// Convert to milliseconds
if ( configValue > 0 ) {
return configValue * 1000 ;
}
// "No expiration" is just 400 days as per RFC 6265
if ( configValue < 0 ) {
return 400 * 24 * 60 * 60 * 1000 ;
}
// 0 means session cookie is deleted when the browser session ends
// (depends on the implementation of the browser)
return undefined ;
}
2024-04-07 22:08:19 +02:00
app . use ( cookieSession ( {
2024-10-10 23:41:08 +02:00
name : getCookieSessionName ( ) ,
2024-04-07 22:08:19 +02:00
sameSite : 'strict' ,
httpOnly : true ,
2024-07-06 13:50:36 +02:00
maxAge : getSessionCookieAge ( ) ,
2024-10-10 23:41:08 +02:00
secret : getCookieSecret ( ) ,
2024-04-07 22:08:19 +02:00
} ) ) ;
2023-07-20 19:32:15 +02:00
2024-10-10 23:41:08 +02:00
app . use ( setUserDataMiddleware ) ;
2024-04-07 22:08:19 +02:00
// CSRF Protection //
2024-04-13 18:35:27 +02:00
if ( ! disableCsrf ) {
2024-10-10 23:41:08 +02:00
const COOKIES _SECRET = getCookieSecret ( ) ;
2024-04-07 22:08:19 +02:00
const { generateToken , doubleCsrfProtection } = doubleCsrf ( {
2024-10-10 23:41:08 +02:00
getSecret : getCsrfSecret ,
2024-04-07 22:08:19 +02:00
cookieName : 'X-CSRF-Token' ,
cookieOptions : {
sameSite : 'strict' ,
secure : false ,
} ,
size : 64 ,
getTokenFromRequest : ( req ) => req . headers [ 'x-csrf-token' ] ,
} ) ;
app . get ( '/csrf-token' , ( req , res ) => {
res . json ( {
'token' : generateToken ( res , req ) ,
} ) ;
} ) ;
app . use ( cookieParser ( COOKIES _SECRET ) ) ;
app . use ( doubleCsrfProtection ) ;
} else {
console . warn ( '\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n' ) ;
app . get ( '/csrf-token' , ( req , res ) => {
res . json ( {
'token' : 'disabled' ,
} ) ;
} ) ;
}
2024-04-09 20:58:16 +02:00
// Static files
// Host index page
app . get ( '/' , ( request , response ) => {
2024-10-10 23:41:08 +02:00
if ( shouldRedirectToLogin ( request ) ) {
2024-04-16 17:44:11 +02:00
const query = request . url . split ( '?' ) [ 1 ] ;
const redirectUrl = query ? ` /login? ${ query } ` : '/login' ;
return response . redirect ( redirectUrl ) ;
2024-04-09 20:58:16 +02:00
}
return response . sendFile ( 'index.html' , { root : path . join ( process . cwd ( ) , 'public' ) } ) ;
} ) ;
// Host login page
app . get ( '/login' , async ( request , response ) => {
if ( ! enableAccounts ) {
console . log ( 'User accounts are disabled. Redirecting to index page.' ) ;
return response . redirect ( '/' ) ;
}
2024-04-23 23:59:55 +02:00
try {
2024-10-10 23:41:08 +02:00
const autoLogin = await tryAutoLogin ( request , basicAuthMode ) ;
2024-04-07 22:08:19 +02:00
2024-04-23 23:59:55 +02:00
if ( autoLogin ) {
return response . redirect ( '/' ) ;
}
} catch ( error ) {
console . error ( 'Error during auto-login:' , error ) ;
2024-04-09 20:58:16 +02:00
}
return response . sendFile ( 'login.html' , { root : path . join ( process . cwd ( ) , 'public' ) } ) ;
} ) ;
// Host frontend assets
2024-10-17 13:04:34 +02:00
const webpackMiddleware = getWebpackServeMiddleware ( ) ;
app . use ( webpackMiddleware ) ;
2024-04-09 20:58:16 +02:00
app . use ( express . static ( process . cwd ( ) + '/public' , { } ) ) ;
// Public API
2024-10-10 21:37:22 +02:00
app . use ( '/api/users' , usersPublicRouter ) ;
2024-04-09 20:58:16 +02:00
// Everything below this line requires authentication
2024-10-10 23:41:08 +02:00
app . use ( requireLoginMiddleware ) ;
2024-05-07 00:27:17 +02:00
app . get ( '/api/ping' , ( _ , response ) => response . sendStatus ( 204 ) ) ;
2024-04-09 20:58:16 +02:00
// File uploads
2024-06-26 22:22:42 +02:00
app . use ( multer ( { dest : uploadsPath , limits : { fieldSize : 10 * 1024 * 1024 } } ) . single ( 'avatar' ) ) ;
2024-10-10 21:37:22 +02:00
app . use ( multerMonkeyPatch ) ;
2024-04-09 20:58:16 +02:00
// User data mount
2024-10-10 23:41:08 +02:00
app . use ( '/' , userDataRouter ) ;
2024-04-09 20:58:16 +02:00
// Private endpoints
2024-10-10 21:37:22 +02:00
app . use ( '/api/users' , usersPrivateRouter ) ;
2024-04-09 20:58:16 +02:00
// Admin endpoints
2024-10-10 21:37:22 +02:00
app . use ( '/api/users' , usersAdminRouter ) ;
2024-04-09 20:58:16 +02:00
2023-09-17 13:27:41 +02:00
app . get ( '/version' , async function ( _ , response ) {
const data = await getVersion ( ) ;
2023-07-20 19:32:15 +02:00
response . send ( data ) ;
2023-12-02 20:11:06 +01:00
} ) ;
2023-07-20 19:32:15 +02:00
2023-08-19 16:43:56 +02:00
function cleanUploads ( ) {
try {
2024-06-26 22:22:42 +02:00
if ( fs . existsSync ( uploadsPath ) ) {
const uploads = fs . readdirSync ( uploadsPath ) ;
2023-08-19 16:43:56 +02:00
if ( ! uploads . length ) {
return ;
}
console . debug ( ` Cleaning uploads folder ( ${ uploads . length } files) ` ) ;
uploads . forEach ( file => {
2024-06-26 22:22:42 +02:00
const pathToFile = path . join ( uploadsPath , file ) ;
2023-08-19 16:43:56 +02:00
fs . unlinkSync ( pathToFile ) ;
} ) ;
}
} catch ( err ) {
console . error ( err ) ;
}
}
2023-12-06 18:04:44 +01:00
/ * *
* Redirect a deprecated API endpoint URL to its replacement . Because fetch , form submissions , and $ . ajax follow
* redirects , this is transparent to client - side code .
* @ param { string } src The URL to redirect from .
* @ param { string } destination The URL to redirect to .
* /
function redirect ( src , destination ) {
app . use ( src , ( req , res ) => {
console . warn ( ` API endpoint ${ src } is deprecated; use ${ destination } instead ` ) ;
// HTTP 301 causes the request to become a GET. 308 preserves the request method.
res . redirect ( 308 , destination ) ;
} ) ;
}
// Redirect deprecated character API endpoints
redirect ( '/createcharacter' , '/api/characters/create' ) ;
redirect ( '/renamecharacter' , '/api/characters/rename' ) ;
redirect ( '/editcharacter' , '/api/characters/edit' ) ;
redirect ( '/editcharacterattribute' , '/api/characters/edit-attribute' ) ;
redirect ( '/v2/editcharacterattribute' , '/api/characters/merge-attributes' ) ;
redirect ( '/deletecharacter' , '/api/characters/delete' ) ;
redirect ( '/getcharacters' , '/api/characters/all' ) ;
redirect ( '/getonecharacter' , '/api/characters/get' ) ;
redirect ( '/getallchatsofcharacter' , '/api/characters/chats' ) ;
redirect ( '/importcharacter' , '/api/characters/import' ) ;
redirect ( '/dupecharacter' , '/api/characters/duplicate' ) ;
redirect ( '/exportcharacter' , '/api/characters/export' ) ;
2023-12-06 18:11:57 +01:00
// Redirect deprecated chat API endpoints
redirect ( '/savechat' , '/api/chats/save' ) ;
redirect ( '/getchat' , '/api/chats/get' ) ;
redirect ( '/renamechat' , '/api/chats/rename' ) ;
redirect ( '/delchat' , '/api/chats/delete' ) ;
redirect ( '/exportchat' , '/api/chats/export' ) ;
redirect ( '/importgroupchat' , '/api/chats/group/import' ) ;
redirect ( '/importchat' , '/api/chats/import' ) ;
redirect ( '/getgroupchat' , '/api/chats/group/get' ) ;
redirect ( '/deletegroupchat' , '/api/chats/group/delete' ) ;
redirect ( '/savegroupchat' , '/api/chats/group/save' ) ;
2023-12-06 18:40:58 +01:00
// Redirect deprecated group API endpoints
redirect ( '/getgroups' , '/api/groups/all' ) ;
redirect ( '/creategroup' , '/api/groups/create' ) ;
redirect ( '/editgroup' , '/api/groups/edit' ) ;
redirect ( '/deletegroup' , '/api/groups/delete' ) ;
2023-12-06 23:09:48 +01:00
// Redirect deprecated worldinfo API endpoints
redirect ( '/getworldinfo' , '/api/worldinfo/get' ) ;
redirect ( '/deleteworldinfo' , '/api/worldinfo/delete' ) ;
redirect ( '/importworldinfo' , '/api/worldinfo/import' ) ;
redirect ( '/editworldinfo' , '/api/worldinfo/edit' ) ;
2023-12-07 18:31:34 +01:00
// Redirect deprecated stats API endpoints
redirect ( '/getstats' , '/api/stats/get' ) ;
redirect ( '/recreatestats' , '/api/stats/recreate' ) ;
redirect ( '/updatestats' , '/api/stats/update' ) ;
2023-12-07 21:17:19 +01:00
// Redirect deprecated backgrounds API endpoints
redirect ( '/getbackgrounds' , '/api/backgrounds/all' ) ;
redirect ( '/delbackground' , '/api/backgrounds/delete' ) ;
redirect ( '/renamebackground' , '/api/backgrounds/rename' ) ;
redirect ( '/downloadbackground' , '/api/backgrounds/upload' ) ; // yes, the downloadbackground endpoint actually uploads one
2024-03-19 23:14:32 +01:00
// Redirect deprecated theme API endpoints
redirect ( '/savetheme' , '/api/themes/save' ) ;
2024-03-19 23:39:48 +01:00
// Redirect deprecated avatar API endpoints
redirect ( '/getuseravatars' , '/api/avatars/get' ) ;
redirect ( '/deleteuseravatar' , '/api/avatars/delete' ) ;
redirect ( '/uploaduseravatar' , '/api/avatars/upload' ) ;
2024-03-19 23:46:46 +01:00
// Redirect deprecated quick reply endpoints
redirect ( '/deletequickreply' , '/api/quick-replies/delete' ) ;
redirect ( '/savequickreply' , '/api/quick-replies/save' ) ;
2024-03-19 23:59:06 +01:00
// Redirect deprecated image endpoints
redirect ( '/uploadimage' , '/api/images/upload' ) ;
redirect ( '/listimgfiles/:folder' , '/api/images/list/:folder' ) ;
2024-04-09 15:20:38 +02:00
redirect ( '/api/content/import' , '/api/content/importURL' ) ;
2024-03-19 23:59:06 +01:00
2024-03-20 00:07:28 +01:00
// Redirect deprecated moving UI endpoints
redirect ( '/savemovingui' , '/api/moving-ui/save' ) ;
2024-06-19 21:37:51 +02:00
// Redirect Serp endpoints
redirect ( '/api/serpapi/search' , '/api/search/serpapi' ) ;
redirect ( '/api/serpapi/visit' , '/api/search/visit' ) ;
redirect ( '/api/serpapi/transcript' , '/api/search/transcript' ) ;
2024-10-10 21:37:22 +02:00
app . use ( '/api/moving-ui' , movingUIRouter ) ;
app . use ( '/api/images' , imagesRouter ) ;
app . use ( '/api/quick-replies' , quickRepliesRouter ) ;
app . use ( '/api/avatars' , avatarsRouter ) ;
app . use ( '/api/themes' , themesRouter ) ;
app . use ( '/api/openai' , openAiRouter ) ;
app . use ( '/api/google' , googleRouter ) ;
app . use ( '/api/anthropic' , anthropicRouter ) ;
app . use ( '/api/tokenizers' , tokenizersRouter ) ;
app . use ( '/api/presets' , presetsRouter ) ;
app . use ( '/api/secrets' , secretsRouter ) ;
app . use ( '/thumbnail' , thumbnailRouter ) ;
app . use ( '/api/novelai' , novelAiRouter ) ;
app . use ( '/api/extensions' , extensionsRouter ) ;
app . use ( '/api/assets' , assetsRouter ) ;
app . use ( '/api/files' , filesRouter ) ;
app . use ( '/api/characters' , charactersRouter ) ;
app . use ( '/api/chats' , chatsRouter ) ;
app . use ( '/api/groups' , groupsRouter ) ;
app . use ( '/api/worldinfo' , worldInfoRouter ) ;
app . use ( '/api/stats' , statsRouter ) ;
app . use ( '/api/backgrounds' , backgroundsRouter ) ;
app . use ( '/api/sprites' , spritesRouter ) ;
app . use ( '/api/content' , contentManagerRouter ) ;
app . use ( '/api/settings' , settingsRouter ) ;
app . use ( '/api/sd' , stableDiffusionRouter ) ;
app . use ( '/api/horde' , hordeRouter ) ;
app . use ( '/api/vector' , vectorsRouter ) ;
app . use ( '/api/translate' , translateRouter ) ;
app . use ( '/api/extra/classify' , classifyRouter ) ;
app . use ( '/api/extra/caption' , captionRouter ) ;
app . use ( '/api/search' , searchRouter ) ;
app . use ( '/api/backends/text-completions' , textCompletionsRouter ) ;
app . use ( '/api/openrouter' , openRouterRouter ) ;
app . use ( '/api/backends/kobold' , koboldRouter ) ;
app . use ( '/api/backends/chat-completions' , chatCompletionsRouter ) ;
app . use ( '/api/backends/scale-alt' , scaleAltRouter ) ;
app . use ( '/api/speech' , speechRouter ) ;
app . use ( '/api/azure' , azureRouter ) ;
2024-05-22 00:37:51 +02:00
2024-08-15 19:12:12 +02:00
const tavernUrlV6 = new URL (
2023-07-20 19:32:15 +02:00
( cliArguments . ssl ? 'https://' : 'http://' ) +
2024-08-15 19:12:12 +02:00
( listen ? '[::]' : '[::1]' ) +
2023-12-02 21:06:57 +01:00
( ':' + server _port ) ,
2023-07-20 19:32:15 +02:00
) ;
2024-08-15 19:12:12 +02:00
const tavernUrl = new URL (
2023-07-20 19:32:15 +02:00
( cliArguments . ssl ? 'https://' : 'http://' ) +
2024-08-15 19:12:12 +02:00
( listen ? '0.0.0.0' : '127.0.0.1' ) +
2023-12-02 21:06:57 +01:00
( ':' + server _port ) ,
2023-07-20 19:32:15 +02:00
) ;
2024-10-17 13:04:34 +02:00
function prepareFrontendBundle ( ) {
return new Promise ( ( resolve ) => webpackMiddleware . waitUntilValid ( resolve ) ) ;
}
2024-04-27 20:41:32 +02:00
/ * *
* Tasks that need to be run before the server starts listening .
* /
const preSetupTasks = async function ( ) {
2023-09-17 13:27:41 +02:00
const version = await getVersion ( ) ;
2023-07-20 19:32:15 +02:00
2024-04-02 22:17:21 +02:00
// Print formatted header
console . log ( ) ;
console . log ( ` SillyTavern ${ version . pkgVersion } ` ) ;
console . log ( version . gitBranch ? ` Running ' ${ version . gitBranch } ' ( ${ version . gitRevision } ) - ${ version . commitDate } ` : '' ) ;
2024-04-02 22:51:43 +02:00
if ( version . gitBranch && ! version . isLatest && [ 'staging' , 'release' ] . includes ( version . gitBranch ) ) {
2024-04-02 22:17:21 +02:00
console . log ( 'INFO: Currently not on the latest commit.' ) ;
2024-04-03 00:00:20 +02:00
console . log ( ' Run \'git pull\' to update. If you have any merge conflicts, run \'git reset --hard\' and \'git pull\' to reset your branch.' ) ;
2024-04-02 22:17:21 +02:00
}
console . log ( ) ;
2023-07-20 19:32:15 +02:00
2024-10-10 23:41:08 +02:00
const directories = await getUserDirectoriesList ( ) ;
await checkForNewContent ( directories ) ;
2024-04-06 16:43:59 +02:00
await ensureThumbnailCache ( ) ;
2023-08-19 16:43:56 +02:00
cleanUploads ( ) ;
2023-07-20 19:32:15 +02:00
2024-10-10 21:37:22 +02:00
await settingsInit ( ) ;
await statsInit ( ) ;
2023-12-07 19:01:51 +01:00
2024-10-10 23:41:08 +02:00
const cleanupPlugins = await initializePlugins ( ) ;
2024-04-13 20:51:36 +02:00
const consoleTitle = process . title ;
2023-12-17 18:26:34 +01:00
2024-04-27 20:41:32 +02:00
let isExiting = false ;
2023-12-23 18:05:21 +01:00
const exitProcess = async ( ) => {
2024-04-27 20:41:32 +02:00
if ( isExiting ) return ;
isExiting = true ;
2024-10-10 21:37:22 +02:00
await statsOnExit ( ) ;
2023-12-23 18:03:13 +01:00
if ( typeof cleanupPlugins === 'function' ) {
2023-12-23 18:05:21 +01:00
await cleanupPlugins ( ) ;
2023-12-23 18:03:13 +01:00
}
2024-04-13 20:51:36 +02:00
setWindowTitle ( consoleTitle ) ;
2023-12-07 19:01:51 +01:00
process . exit ( ) ;
} ;
2023-07-20 19:32:15 +02:00
// Set up event listeners for a graceful shutdown
2023-12-07 19:01:51 +01:00
process . on ( 'SIGINT' , exitProcess ) ;
process . on ( 'SIGTERM' , exitProcess ) ;
2023-07-20 19:32:15 +02:00
process . on ( 'uncaughtException' , ( err ) => {
console . error ( 'Uncaught exception:' , err ) ;
2023-12-07 19:01:51 +01:00
exitProcess ( ) ;
2023-07-20 19:32:15 +02:00
} ) ;
2024-09-11 21:36:50 +02:00
// Add request proxy.
2024-09-12 19:25:58 +02:00
initRequestProxy ( { enabled : proxyEnabled , url : proxyUrl , bypass : proxyBypass } ) ;
2024-10-17 13:04:34 +02:00
// Wait for frontend libs to compile
await prepareFrontendBundle ( ) ;
2024-04-27 20:41:32 +02:00
} ;
2023-07-20 19:32:15 +02:00
2024-08-15 19:29:17 +02:00
/ * *
* Gets the hostname to use for autorun in the browser .
* @ returns { string } The hostname to use for autorun
* /
2024-08-15 19:12:12 +02:00
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 ;
}
2024-04-27 20:41:32 +02:00
/ * *
* Tasks that need to be run after the server starts listening .
2024-08-15 19:29:17 +02:00
* @ param { boolean } v6Failed If the server failed to start on IPv6
* @ param { boolean } v4Failed If the server failed to start on IPv4
2024-04-27 20:41:32 +02:00
* /
2024-08-15 19:12:12 +02:00
const postSetupTasks = async function ( v6Failed , v4Failed ) {
const autorunUrl = new URL (
( cliArguments . ssl ? 'https://' : 'http://' ) +
( getAutorunHostname ( ) ) +
( ':' ) +
( ( autorunPortOverride >= 0 ) ? autorunPortOverride : server _port ) ,
) ;
2023-07-20 19:32:15 +02:00
console . log ( 'Launching...' ) ;
if ( autorun ) open ( autorunUrl . toString ( ) ) ;
2023-08-19 14:58:17 +02:00
2024-04-13 19:39:28 +02:00
setWindowTitle ( 'SillyTavern WebServer' ) ;
2024-04-12 18:51:34 +02:00
2024-08-15 19:12:12 +02:00
let logListen = 'SillyTavern is listening on' ;
if ( enableIPv6 && ! v6Failed ) {
2024-08-15 19:29:17 +02:00
logListen += color . green ( ' IPv6: ' + tavernUrlV6 . host ) ;
2024-08-15 19:12:12 +02:00
}
if ( enableIPv4 && ! v4Failed ) {
2024-08-15 19:29:17 +02:00
logListen += color . green ( ' IPv4: ' + tavernUrl . host ) ;
2024-08-15 19:12:12 +02:00
}
2024-08-15 19:29:17 +02:00
const goToLog = 'Go to: ' + color . blue ( autorunUrl ) + ' to open SillyTavern' ;
const plainGoToLog = removeColorFormatting ( goToLog ) ;
2024-08-15 19:12:12 +02:00
console . log ( logListen ) ;
console . log ( '\n' + getSeparator ( plainGoToLog . length ) + '\n' ) ;
console . log ( goToLog ) ;
console . log ( '\n' + getSeparator ( plainGoToLog . length ) + '\n' ) ;
2023-07-20 19:32:15 +02:00
if ( listen ) {
2024-08-15 19:12:12 +02:00
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' ) ;
2023-07-20 19:32:15 +02:00
}
2024-03-30 19:57:23 +01:00
2024-03-30 21:46:18 +01:00
if ( basicAuthMode ) {
2024-10-09 09:43:52 +02:00
if ( perUserBasicAuth && ! enableAccounts ) {
console . error ( color . red ( 'Per-user basic authentication is enabled, but user accounts are disabled. This configuration may be insecure.' ) ) ;
} else if ( ! perUserBasicAuth ) {
2024-10-06 09:35:03 +02:00
const basicAuthUser = getConfigValue ( 'basicAuthUser' , { } ) ;
if ( ! basicAuthUser ? . username || ! basicAuthUser ? . password ) {
console . warn ( color . yellow ( 'Basic Authentication is enabled, but username or password is not set or empty!' ) ) ;
}
2024-03-30 19:57:23 +01:00
}
}
2023-12-02 20:11:06 +01:00
} ;
2023-07-20 19:32:15 +02:00
2023-12-23 18:03:13 +01:00
/ * *
* Loads server plugins from a directory .
* @ returns { Promise < Function > } Function to be run on server exit
* /
2024-10-10 23:41:08 +02:00
async function initializePlugins ( ) {
2023-12-16 21:21:40 +01:00
try {
const pluginDirectory = path . join ( serverDirectory , 'plugins' ) ;
2024-10-10 23:41:08 +02:00
const cleanupPlugins = await loadPlugins ( app , pluginDirectory ) ;
2023-12-23 18:03:13 +01:00
return cleanupPlugins ;
2023-12-16 21:21:40 +01:00
} catch {
console . log ( 'Plugin loading failed.' ) ;
2024-03-30 21:46:18 +01:00
return ( ) => { } ;
2023-12-16 21:21:40 +01:00
}
}
2024-04-13 20:51:36 +02:00
/ * *
* Set the title of the terminal window
* @ param { string } title Desired title for the window
* /
function setWindowTitle ( title ) {
if ( process . platform === 'win32' ) {
process . title = title ;
}
else {
process . stdout . write ( ` \x 1b]2; ${ title } \x 1b \x 5c ` ) ;
}
}
2024-07-03 20:24:03 +02:00
/ * *
* Prints an error message and exits the process if necessary
* @ param { string } message The error message to print
* @ returns { void }
* /
function logSecurityAlert ( message ) {
if ( basicAuthMode || enableWhitelist ) return ; // safe!
console . error ( color . red ( message ) ) ;
if ( getConfigValue ( 'securityOverride' , false ) ) {
console . warn ( color . red ( 'Security has been overridden. If it\'s not a trusted network, change the settings.' ) ) ;
return ;
}
process . exit ( 1 ) ;
}
2024-08-15 19:29:17 +02:00
/ * *
* 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 } v4Failed If the server failed to start on IPv4
* /
2024-08-15 19:12:12 +02:00
function handleServerListenFail ( v6Failed , v4Failed ) {
if ( v6Failed && ! enableIPv4 ) {
2024-08-16 13:21:58 +02:00
console . error ( color . red ( 'fatal error: Failed to start server on IPv6 and IPv4 disabled' ) ) ;
2024-08-15 19:12:12 +02:00
process . exit ( 1 ) ;
}
if ( v4Failed && ! enableIPv6 ) {
2024-08-16 13:21:58 +02:00
console . error ( color . red ( 'fatal error: Failed to start server on IPv4 and IPv6 disabled' ) ) ;
2024-08-15 19:12:12 +02:00
process . exit ( 1 ) ;
}
if ( v6Failed && v4Failed ) {
2024-08-16 13:21:58 +02:00
console . error ( color . red ( 'fatal error: Failed to start server on both IPv6 and IPv4' ) ) ;
2024-08-15 19:12:12 +02:00
process . exit ( 1 ) ;
}
}
2024-08-15 19:29:17 +02:00
/ * *
* Creates an HTTPS server .
* @ param { URL } url The URL to listen on
* @ returns { Promise < void > } A promise that resolves when the server is listening
* @ throws { Error } If the server fails to start
* /
2024-08-15 19:12:12 +02:00
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 ) ;
2024-10-11 19:34:13 +02:00
server . listen ( Number ( url . port || 443 ) , url . hostname ) ;
2024-08-15 19:12:12 +02:00
} ) ;
}
2024-08-15 19:29:17 +02:00
/ * *
* Creates an HTTP server .
* @ param { URL } url The URL to listen on
* @ returns { Promise < void > } A promise that resolves when the server is listening
* @ throws { Error } If the server fails to start
* /
2024-08-15 19:12:12 +02:00
function createHttpServer ( url ) {
return new Promise ( ( resolve , reject ) => {
const server = http . createServer ( app ) ;
server . on ( 'error' , reject ) ;
server . on ( 'listening' , resolve ) ;
2024-10-11 19:34:13 +02:00
server . listen ( Number ( url . port || 80 ) , url . hostname ) ;
2024-08-15 19:12:12 +02:00
} ) ;
}
async function startHTTPorHTTPS ( ) {
let v6Failed = false ;
let v4Failed = false ;
2024-08-15 19:29:17 +02:00
const createFunc = cliArguments . ssl ? createHttpsServer : createHttpServer ;
2024-08-15 19:12:12 +02:00
if ( enableIPv6 ) {
try {
await createFunc ( tavernUrlV6 ) ;
2024-08-15 19:29:17 +02:00
} catch ( error ) {
2024-08-16 13:25:30 +02:00
console . error ( 'non-fatal error: failed to start server on IPv6' ) ;
console . error ( error ) ;
2024-08-15 19:12:12 +02:00
v6Failed = true ;
}
}
if ( enableIPv4 ) {
try {
await createFunc ( tavernUrl ) ;
2024-08-15 19:29:17 +02:00
} catch ( error ) {
2024-08-16 13:25:30 +02:00
console . error ( 'non-fatal error: failed to start server on IPv4' ) ;
console . error ( error ) ;
2024-08-15 19:12:12 +02:00
v4Failed = true ;
}
}
2024-08-15 19:29:17 +02:00
2024-08-15 19:12:12 +02:00
return [ v6Failed , v4Failed ] ;
}
async function startServer ( ) {
2024-08-15 19:29:17 +02:00
const [ v6Failed , v4Failed ] = await startHTTPorHTTPS ( ) ;
2024-08-15 19:12:12 +02:00
handleServerListenFail ( v6Failed , v4Failed ) ;
postSetupTasks ( v6Failed , v4Failed ) ;
}
2024-07-03 20:24:03 +02:00
async function verifySecuritySettings ( ) {
// Skip all security checks as listen is set to false
if ( ! listen ) {
return ;
}
if ( ! enableAccounts ) {
logSecurityAlert ( 'Your SillyTavern is currently insecurely open to the public. Enable whitelisting, basic authentication or user accounts.' ) ;
}
2024-10-10 23:41:08 +02:00
const users = await getAllEnabledUsers ( ) ;
2024-07-03 20:24:03 +02:00
const unprotectedUsers = users . filter ( x => ! x . password ) ;
const unprotectedAdminUsers = unprotectedUsers . filter ( x => x . admin ) ;
if ( unprotectedUsers . length > 0 ) {
console . warn ( color . blue ( 'A friendly reminder that the following users are not password protected:' ) ) ;
unprotectedUsers . map ( x => ` ${ color . yellow ( x . handle ) } ${ color . red ( x . admin ? '(admin)' : '' ) } ` ) . forEach ( x => console . warn ( x ) ) ;
console . log ( ) ;
console . warn ( ` Consider setting a password in the admin panel or by using the ${ color . blue ( 'recover.js' ) } script. ` ) ;
console . log ( ) ;
if ( unprotectedAdminUsers . length > 0 ) {
logSecurityAlert ( 'If you are not using basic authentication or whitelisting, you should set a password for all admin users.' ) ;
}
}
}
2024-04-26 13:09:40 +02:00
// User storage module needs to be initialized before starting the server
2024-10-10 23:41:08 +02:00
initUserStorage ( dataRoot )
. then ( ensurePublicDirectoriesExist )
. then ( migrateUserData )
. then ( migrateSystemPrompts )
2024-07-03 20:24:03 +02:00
. then ( verifySecuritySettings )
2024-04-27 20:41:32 +02:00
. then ( preSetupTasks )
2024-08-15 19:12:12 +02:00
. finally ( startServer ) ;