mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Fix absolute paths for data root. Allow setting data root via console args.
This commit is contained in:
@ -102,6 +102,10 @@ const cliArguments = yargs(hideBin(process.argv))
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: false,
|
default: false,
|
||||||
describe: 'Enables whitelist mode',
|
describe: 'Enables whitelist mode',
|
||||||
|
}).option('dataRoot', {
|
||||||
|
type: 'string',
|
||||||
|
default: null,
|
||||||
|
describe: 'Root directory for data storage',
|
||||||
}).parseSync();
|
}).parseSync();
|
||||||
|
|
||||||
// change all relative paths
|
// change all relative paths
|
||||||
@ -121,6 +125,7 @@ const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTOR
|
|||||||
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
|
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
|
||||||
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);
|
||||||
|
const dataRoot = cliArguments.dataRoot ?? getConfigValue('dataRoot', './data');
|
||||||
const basicAuthMode = getConfigValue('basicAuthMode', false);
|
const basicAuthMode = getConfigValue('basicAuthMode', false);
|
||||||
const enableAccounts = getConfigValue('enableUserAccounts', false);
|
const enableAccounts = getConfigValue('enableUserAccounts', false);
|
||||||
|
|
||||||
@ -526,7 +531,7 @@ const setupTasks = async function () {
|
|||||||
|
|
||||||
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
|
// TODO: do endpoint init functions depend on certain directories existing or not existing? They should be callable
|
||||||
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
|
// in any order for encapsulation reasons, but right now it's unknown if that would break anything.
|
||||||
await userModule.initUserStorage();
|
await userModule.initUserStorage(dataRoot);
|
||||||
await settingsEndpoint.init();
|
await settingsEndpoint.init();
|
||||||
const directories = await userModule.ensurePublicDirectoriesExist();
|
const directories = await userModule.ensurePublicDirectoriesExist();
|
||||||
await userModule.migrateUserData();
|
await userModule.migrateUserData();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const mime = require('mime-types');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const fetch = require('node-fetch').default;
|
const fetch = require('node-fetch').default;
|
||||||
@ -216,9 +217,11 @@ router.post('/download', jsonParser, async (request, response) => {
|
|||||||
await finished(res.body.pipe(fileStream));
|
await finished(res.body.pipe(fileStream));
|
||||||
|
|
||||||
if (category === 'character') {
|
if (category === 'character') {
|
||||||
response.sendFile(temp_path, { root: process.cwd() }, () => {
|
const fileContent = fs.readFileSync(temp_path);
|
||||||
fs.rmSync(temp_path);
|
const contentType = mime.lookup(temp_path) || 'application/octet-stream';
|
||||||
});
|
response.setHeader('Content-Type', contentType);
|
||||||
|
response.send(fileContent);
|
||||||
|
fs.rmSync(temp_path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
const readline = require('readline');
|
const readline = require('readline');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const mime = require('mime-types');
|
||||||
|
|
||||||
const jimp = require('jimp');
|
const jimp = require('jimp');
|
||||||
|
|
||||||
@ -1078,33 +1080,43 @@ router.post('/duplicate', jsonParser, async function (request, response) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('/export', jsonParser, async function (request, response) {
|
router.post('/export', jsonParser, async function (request, response) {
|
||||||
if (!request.body.format || !request.body.avatar_url) {
|
try {
|
||||||
return response.sendStatus(400);
|
if (!request.body.format || !request.body.avatar_url) {
|
||||||
}
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url));
|
let filename = path.join(request.user.directories.characters, sanitize(request.body.avatar_url));
|
||||||
|
|
||||||
if (!fs.existsSync(filename)) {
|
if (!fs.existsSync(filename)) {
|
||||||
return response.sendStatus(404);
|
return response.sendStatus(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (request.body.format) {
|
switch (request.body.format) {
|
||||||
case 'png':
|
case 'png': {
|
||||||
return response.sendFile(filename, { root: process.cwd() });
|
const fileContent = await fsPromises.readFile(filename);
|
||||||
case 'json': {
|
const contentType = mime.lookup(filename) || 'image/png';
|
||||||
try {
|
response.setHeader('Content-Type', contentType);
|
||||||
let json = await readCharacterData(filename);
|
response.setHeader('Content-Disposition', `attachment; filename=${path.basename(filename)}`);
|
||||||
if (json === undefined) return response.sendStatus(400);
|
return response.send(fileContent);
|
||||||
let jsonObject = getCharaCardV2(JSON.parse(json), request.user.directories);
|
|
||||||
return response.type('json').send(JSON.stringify(jsonObject, null, 4));
|
|
||||||
}
|
}
|
||||||
catch {
|
case 'json': {
|
||||||
return response.sendStatus(400);
|
try {
|
||||||
|
let json = await readCharacterData(filename);
|
||||||
|
if (json === undefined) return response.sendStatus(400);
|
||||||
|
let jsonObject = getCharaCardV2(JSON.parse(json), request.user.directories);
|
||||||
|
return response.type('json').send(JSON.stringify(jsonObject, null, 4));
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Character export failed', err);
|
||||||
|
response.sendStatus(500);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { router };
|
module.exports = { router };
|
||||||
|
@ -122,7 +122,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const basePath = path.parse(contentItem.filename).base;
|
const basePath = path.parse(contentItem.filename).base;
|
||||||
const targetPath = path.join(process.cwd(), contentTarget, basePath);
|
const targetPath = path.join(contentTarget, basePath);
|
||||||
|
|
||||||
if (fs.existsSync(targetPath)) {
|
if (fs.existsSync(targetPath)) {
|
||||||
console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`);
|
console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const fsPromises = require('fs').promises;
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const mime = require('mime-types');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const jimp = require('jimp');
|
const jimp = require('jimp');
|
||||||
@ -165,38 +167,63 @@ const router = express.Router();
|
|||||||
|
|
||||||
// Important: This route must be mounted as '/thumbnail'. It is used in the client code and saved to chat files.
|
// Important: This route must be mounted as '/thumbnail'. It is used in the client code and saved to chat files.
|
||||||
router.get('/', jsonParser, async function (request, response) {
|
router.get('/', jsonParser, async function (request, response) {
|
||||||
if (typeof request.query.file !== 'string' || typeof request.query.type !== 'string') return response.sendStatus(400);
|
try{
|
||||||
|
if (typeof request.query.file !== 'string' || typeof request.query.type !== 'string') {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
const type = request.query.type;
|
const type = request.query.type;
|
||||||
const file = sanitize(request.query.file);
|
const file = sanitize(request.query.file);
|
||||||
|
|
||||||
if (!type || !file) {
|
if (!type || !file) {
|
||||||
return response.sendStatus(400);
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(type == 'bg' || type == 'avatar')) {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sanitize(file) !== file) {
|
||||||
|
console.error('Malicious filename prevented');
|
||||||
|
return response.sendStatus(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbnailsDisabled = getConfigValue('disableThumbnails', false);
|
||||||
|
if (thumbnailsDisabled) {
|
||||||
|
const folder = getOriginalFolder(request.user.directories, type);
|
||||||
|
|
||||||
|
if (folder === undefined) {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathToOriginalFile = path.join(folder, file);
|
||||||
|
if (!fs.existsSync(pathToOriginalFile)) {
|
||||||
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
const contentType = mime.lookup(pathToOriginalFile) || 'image/png';
|
||||||
|
const originalFile = await fsPromises.readFile(pathToOriginalFile);
|
||||||
|
response.setHeader('Content-Type', contentType);
|
||||||
|
return response.send(originalFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathToCachedFile = await generateThumbnail(request.user.directories, type, file);
|
||||||
|
|
||||||
|
if (!pathToCachedFile) {
|
||||||
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(pathToCachedFile)) {
|
||||||
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = mime.lookup(pathToCachedFile) || 'image/jpeg';
|
||||||
|
const cachedFile = await fsPromises.readFile(pathToCachedFile);
|
||||||
|
response.setHeader('Content-Type', contentType);
|
||||||
|
return response.send(cachedFile);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed getting thumbnail', error);
|
||||||
|
return response.sendStatus(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(type == 'bg' || type == 'avatar')) {
|
|
||||||
return response.sendStatus(400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sanitize(file) !== file) {
|
|
||||||
console.error('Malicious filename prevented');
|
|
||||||
return response.sendStatus(403);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getConfigValue('disableThumbnails', false) == true) {
|
|
||||||
let folder = getOriginalFolder(request.user.directories, type);
|
|
||||||
if (folder === undefined) return response.sendStatus(400);
|
|
||||||
const pathToOriginalFile = path.join(folder, file);
|
|
||||||
return response.sendFile(pathToOriginalFile, { root: process.cwd() });
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathToCachedFile = await generateThumbnail(request.user.directories, type, file);
|
|
||||||
|
|
||||||
if (!pathToCachedFile) {
|
|
||||||
return response.sendStatus(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.sendFile(pathToCachedFile, { root: process.cwd() });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
13
src/users.js
13
src/users.js
@ -15,10 +15,15 @@ const { getConfigValue, color, delay, setConfigValue, generateTimestamp } = requ
|
|||||||
const { readSecret, writeSecret } = require('./endpoints/secrets');
|
const { readSecret, writeSecret } = require('./endpoints/secrets');
|
||||||
|
|
||||||
const KEY_PREFIX = 'user:';
|
const KEY_PREFIX = 'user:';
|
||||||
const DATA_ROOT = getConfigValue('dataRoot', './data');
|
|
||||||
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
|
||||||
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root directory for user data.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
let DATA_ROOT = './data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for user directories.
|
* Cache for user directories.
|
||||||
* @type {Map<string, UserDirectoryList>}
|
* @type {Map<string, UserDirectoryList>}
|
||||||
@ -312,9 +317,13 @@ function toKey(handle) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the user storage. Currently a no-op.
|
* Initializes the user storage. Currently a no-op.
|
||||||
|
* @param {string} dataRoot The root directory for user data
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function initUserStorage() {
|
async function initUserStorage(dataRoot) {
|
||||||
|
DATA_ROOT = dataRoot;
|
||||||
|
console.log('Using data root:', color.green(DATA_ROOT));
|
||||||
|
console.log();
|
||||||
await storage.init({
|
await storage.init({
|
||||||
dir: path.join(DATA_ROOT, '_storage'),
|
dir: path.join(DATA_ROOT, '_storage'),
|
||||||
ttl: true,
|
ttl: true,
|
||||||
|
Reference in New Issue
Block a user