diff --git a/post-install.js b/post-install.js index ae01b7d1a..94f354e20 100644 --- a/post-install.js +++ b/post-install.js @@ -3,7 +3,6 @@ */ import fs from 'node:fs'; import path from 'node:path'; -import crypto from 'node:crypto'; import process from 'node:process'; import yaml from 'yaml'; import _ from 'lodash'; @@ -283,57 +282,12 @@ function createDefaultFiles() { } } -/** - * Returns the MD5 hash of the given data. - * @param {Buffer} data Input data - * @returns {string} MD5 hash of the input data - */ -function getMd5Hash(data) { - return crypto - .createHash('md5') - .update(new Uint8Array(data)) - .digest('hex'); -} - -/** - * Copies the WASM binaries from the sillytavern-transformers package to the dist folder. - */ -function copyWasmFiles() { - if (!fs.existsSync('./dist')) { - fs.mkdirSync('./dist'); - } - - const listDir = fs.readdirSync('./node_modules/sillytavern-transformers/dist'); - - for (const file of listDir) { - if (file.endsWith('.wasm')) { - const sourcePath = `./node_modules/sillytavern-transformers/dist/${file}`; - const targetPath = `./dist/${file}`; - - // Don't copy if the file already exists and is the same checksum - if (fs.existsSync(targetPath)) { - const sourceChecksum = getMd5Hash(fs.readFileSync(sourcePath)); - const targetChecksum = getMd5Hash(fs.readFileSync(targetPath)); - - if (sourceChecksum === targetChecksum) { - continue; - } - } - - fs.copyFileSync(sourcePath, targetPath); - console.log(`${file} successfully copied to ./dist/${file}`); - } - } -} - try { // 0. Convert config.conf to config.yaml convertConfig(); // 1. Create default config files createDefaultFiles(); - // 2. Copy transformers WASM binaries from node_modules - copyWasmFiles(); - // 3. Add missing config values + // 2. Add missing config values addMissingConfigValues(); } catch (error) { console.error(error); diff --git a/server.js b/server.js index a5e03cd41..76a9c6585 100644 --- a/server.js +++ b/server.js @@ -6,7 +6,6 @@ import util from 'node:util'; import net from 'node:net'; import dns from 'node:dns'; import process from 'node:process'; -import { fileURLToPath } from 'node:url'; import cors from 'cors'; import { csrfSync } from 'csrf-sync'; @@ -60,6 +59,7 @@ import { } from './src/util.js'; import { UPLOADS_DIRECTORY } from './src/constants.js'; import { ensureThumbnailCache } from './src/endpoints/thumbnails.js'; +import { serverDirectory } from './src/server-directory.js'; // Routers import { router as usersPublicRouter } from './src/endpoints/users-public.js'; @@ -74,10 +74,7 @@ util.inspect.defaultOptions.maxArrayLength = null; util.inspect.defaultOptions.maxStringLength = null; util.inspect.defaultOptions.depth = 4; -// Set a working directory for the server -const serverDirectory = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url)); console.log(`Node version: ${process.version}. Running in ${process.env.NODE_ENV} environment. Server directory: ${serverDirectory}`); -process.chdir(serverDirectory); // 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 @@ -211,7 +208,7 @@ app.get('/', getCacheBusterMiddleware(), (request, response) => { return response.redirect(redirectUrl); } - return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') }); + return response.sendFile('index.html', { root: path.join(serverDirectory, 'public') }); }); // Callback endpoint for OAuth PKCE flows (e.g. OpenRouter) @@ -231,7 +228,7 @@ app.get('/login', loginPageMiddleware); // Host frontend assets const webpackMiddleware = getWebpackServeMiddleware(); app.use(webpackMiddleware); -app.use(express.static(process.cwd() + '/public', {})); +app.use(express.static(path.join(serverDirectory, 'public'), {})); // Public API app.use('/api/users', usersPublicRouter); @@ -375,7 +372,7 @@ async function postSetupTasks(result) { * Registers a not-found error response if a not-found error page exists. Should only be called after all other middlewares have been registered. */ function apply404Middleware() { - const notFoundWebpage = safeReadFileSync('./public/error/url-not-found.html') ?? ''; + const notFoundWebpage = safeReadFileSync(path.join(serverDirectory, 'public/error/url-not-found.html')) ?? ''; app.use((req, res) => { res.status(404).send(notFoundWebpage); }); diff --git a/src/endpoints/content-manager.js b/src/endpoints/content-manager.js index 6cebf53b7..293645bfb 100644 --- a/src/endpoints/content-manager.js +++ b/src/endpoints/content-manager.js @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import process from 'node:process'; import { Buffer } from 'node:buffer'; import express from 'express'; @@ -10,9 +9,10 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; import { getConfigValue, color } from '../util.js'; import { write } from '../character-card-parser.js'; +import { serverDirectory } from '../server-directory.js'; -const contentDirectory = path.join(process.cwd(), 'default/content'); -const scaffoldDirectory = path.join(process.cwd(), 'default/scaffold'); +const contentDirectory = path.join(serverDirectory, 'default/content'); +const scaffoldDirectory = path.join(serverDirectory, 'default/scaffold'); const contentIndexPath = path.join(contentDirectory, 'index.json'); const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json'); @@ -149,6 +149,30 @@ async function seedContentForUser(contentIndex, directories, forceCategories) { } fs.cpSync(contentPath, targetPath, { recursive: true, force: false }); + function setPermissionsSync(targetPath_) { + + function appendWritablePermission(filepath, stats) { + const currentMode = stats.mode; + const newMode = currentMode | 0o200; + if (newMode != currentMode) { + fs.chmodSync(filepath, newMode); + } + } + + const stats = fs.statSync(targetPath_); + + if (stats.isDirectory()) { + appendWritablePermission(targetPath_, stats); + const files = fs.readdirSync(targetPath_); + + files.forEach((file) => { + setPermissionsSync(path.join(targetPath_, file)); + }); + } else { + appendWritablePermission(targetPath_, stats); + } + } + setPermissionsSync(targetPath); console.info(`Content file ${contentItem.filename} copied to ${contentTarget}`); anyContentAdded = true; } diff --git a/src/fetch-patch.js b/src/fetch-patch.js index c17b7b85c..f6f5ded9e 100644 --- a/src/fetch-patch.js +++ b/src/fetch-patch.js @@ -2,6 +2,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import mime from 'mime-types'; +import { serverDirectory } from './server-directory.js'; const originalFetch = globalThis.fetch; @@ -67,10 +68,9 @@ globalThis.fetch = async (/** @type {string | URL | Request} */ request, /** @ty } const url = getRequestURL(request); const filePath = path.resolve(fileURLToPath(url)); - const cwd = path.resolve(process.cwd()) + path.sep; - const isUnderCwd = isPathUnderParent(cwd, filePath); - if (!isUnderCwd) { - throw new Error('Requested file path is outside of the current working directory.'); + const isUnderServerDirectory = isPathUnderParent(serverDirectory, filePath); + if (!isUnderServerDirectory) { + throw new Error('Requested file path is outside of the server directory.'); } const parsedPath = path.parse(filePath); if (!ALLOWED_EXTENSIONS.includes(parsedPath.ext)) { diff --git a/src/server-directory.js b/src/server-directory.js new file mode 100644 index 000000000..6c0c6e1da --- /dev/null +++ b/src/server-directory.js @@ -0,0 +1,3 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +export const serverDirectory = path.dirname(import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url))); diff --git a/src/transformers.js b/src/transformers.js index 2bd03f4a5..d0eb851a1 100644 --- a/src/transformers.js +++ b/src/transformers.js @@ -5,14 +5,16 @@ import { Buffer } from 'node:buffer'; import { pipeline, env, RawImage } from 'sillytavern-transformers'; import { getConfigValue } from './util.js'; +import { serverDirectory } from './server-directory.js'; configureTransformers(); function configureTransformers() { // Limit the number of threads to 1 to avoid issues on Android env.backends.onnx.wasm.numThreads = 1; + console.log(env.backends.onnx.wasm.wasmPaths); // Use WASM from a local folder to avoid CDN connections - env.backends.onnx.wasm.wasmPaths = path.join(process.cwd(), 'dist') + path.sep; + env.backends.onnx.wasm.wasmPaths = path.join(serverDirectory, 'node_modules', 'sillytavern-transformers', 'dist') + path.sep; } const tasks = { diff --git a/src/users.js b/src/users.js index b02c81c73..c1e631de5 100644 --- a/src/users.js +++ b/src/users.js @@ -18,6 +18,7 @@ import { USER_DIRECTORY_TEMPLATE, DEFAULT_USER, PUBLIC_DIRECTORIES, SETTINGS_FIL import { getConfigValue, color, delay, generateTimestamp } from './util.js'; import { readSecret, writeSecret } from './endpoints/secrets.js'; import { getContentOfType } from './endpoints/content-manager.js'; +import { serverDirectory } from './server-directory.js'; export const KEY_PREFIX = 'user:'; const AVATAR_PREFIX = 'avatar:'; @@ -905,7 +906,7 @@ export async function loginPageMiddleware(request, response) { console.error('Error during auto-login:', error); } - return response.sendFile('login.html', { root: path.join(process.cwd(), 'public') }); + return response.sendFile('login.html', { root: path.join(serverDirectory, 'public') }); } /** diff --git a/src/util.js b/src/util.js index ec94ceb1a..45cc3f1f7 100644 --- a/src/util.js +++ b/src/util.js @@ -17,6 +17,7 @@ import { default as simpleGit } from 'simple-git'; import chalk from 'chalk'; import { LOG_LEVELS } from './constants.js'; import bytes from 'bytes'; +import { serverDirectory } from './server-directory.js'; /** * Parsed config object. @@ -121,20 +122,19 @@ export async function getVersion() { try { const require = createRequire(import.meta.url); - const pkgJson = require(path.join(process.cwd(), './package.json')); + const pkgJson = require(path.join(serverDirectory, './package.json')); pkgVersion = pkgJson.version; if (commandExistsSync('git')) { - const git = simpleGit(); - const cwd = process.cwd(); - gitRevision = await git.cwd(cwd).revparse(['--short', 'HEAD']); - gitBranch = await git.cwd(cwd).revparse(['--abbrev-ref', 'HEAD']); - commitDate = await git.cwd(cwd).show(['-s', '--format=%ci', gitRevision]); + const git = simpleGit({ baseDir: serverDirectory }); + gitRevision = await git.revparse(['--short', 'HEAD']); + gitBranch = await git.revparse(['--abbrev-ref', 'HEAD']); + commitDate = await git.show(['-s', '--format=%ci', gitRevision]); - const trackingBranch = await git.cwd(cwd).revparse(['--abbrev-ref', '@{u}']); + const trackingBranch = await git.revparse(['--abbrev-ref', '@{u}']); // Might fail, but exception is caught. Just don't run anything relevant after in this block... - const localLatest = await git.cwd(cwd).revparse(['HEAD']); - const remoteLatest = await git.cwd(cwd).revparse([trackingBranch]); + const localLatest = await git.revparse(['HEAD']); + const remoteLatest = await git.revparse([trackingBranch]); isLatest = localLatest === remoteLatest; } } diff --git a/webpack.config.js b/webpack.config.js index 3579a3cae..1790de6dd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ import process from 'node:process'; import path from 'node:path'; import isDocker from 'is-docker'; +import { serverDirectory } from './src/server-directory.js'; /** * Get the Webpack configuration for the public/lib.js file. @@ -40,7 +41,7 @@ export default function getPublicLibConfig(forceDist = false) { return { mode: 'production', - entry: './public/lib.js', + entry: path.join(serverDirectory, 'public/lib.js'), cache: { type: 'filesystem', cacheDirectory: cacheDirectory,