update server config handling

This commit is contained in:
IrosTheBeggar 2021-01-04 23:04:55 -05:00
parent 254bffbee9
commit ae23d89c85
11 changed files with 91 additions and 105 deletions

View File

@ -5,7 +5,7 @@ const mkdirp = require('make-dir');
const fs = require('fs');
const express = require('express');
const sync = require('./sync');
const globals = require('./../src/global');
const config = require('../src/state/config');
exports.setup = function (mstream, program) {
mstream.post('/federation/invite/exchange', (req, res) => {
@ -86,7 +86,7 @@ exports.setup = function (mstream, program) {
throw new Error('Folders already federated');
}
loadJson = JSON.parse(fs.readFileSync(globals.configFile, 'utf8'));
loadJson = JSON.parse(fs.readFileSync(config.configFile, 'utf8'));
}catch (err) {
return res.status(403).json({ error: err.message });
}
@ -135,7 +135,7 @@ exports.setup = function (mstream, program) {
sync.addDevice(decodedToken.federationId, {});
// Save config file
fs.writeFileSync(globals.configFile, JSON.stringify(loadJson, null, 2), 'utf8');
fs.writeFileSync(config.configFile, JSON.stringify(loadJson, null, 2), 'utf8');
}catch (err) {
return res.status(403).json({ error: err.message });
}

View File

@ -9,31 +9,30 @@ const bodyParser = require('body-parser');
const jukebox = require('./modules/jukebox.js');
const sync = require('./modules/sync.js');
const sharedModule = require('./modules/shared.js');
const defaults = require('./modules/defaults.js');
const ddns = require('./modules/ddns');
const federation = require('./modules/federation');
const config = require('./src/state/config');
exports.serveIt = async configFile => {
try {
var program = await defaults.setup(configFile);
require('./src/global').setup(program);
await config.setup(configFile);
} catch (err) {
winston.error('Failed to validate config file', { stack: err });
process.exit(1);
}
// Logging
if (program.writeLogs) {
logger.addFileLogger(program.storage.logsDirectory);
if (config.program.writeLogs) {
logger.addFileLogger(config.program.storage.logsDirectory);
}
// Set server
var server;
if (program.ssl && program.ssl.cert && program.ssl.key) {
if (config.program.ssl && config.program.ssl.cert && config.program.ssl.key) {
try {
server = require('https').createServer({
key: fs.readFileSync(program.ssl.key),
cert: fs.readFileSync(program.ssl.cert)
key: fs.readFileSync(config.program.ssl.key),
cert: fs.readFileSync(config.program.ssl.cert)
});
} catch (error) {
winston.error('FAILED TO CREATE HTTPS SERVER');
@ -53,33 +52,33 @@ exports.serveIt = async configFile => {
next();
});
if (program.newWebApp) {
if (config.program.newWebApp) {
mstream.use(express.static( 'webapp-beta' ));
} else {
// Give access to public folder
mstream.use('/public', express.static( program.webAppDirectory ));
mstream.use('/public', express.static( config.program.webAppDirectory ));
// Serve the webapp
mstream.get('/', (req, res) => {
res.sendFile('mstream.html', { root: program.webAppDirectory });
res.sendFile('mstream.html', { root: config.program.webAppDirectory });
});
// Serve Shared Page
mstream.all('/shared/playlist/*', (req, res) => {
res.sendFile('shared.html', { root: program.webAppDirectory });
res.sendFile('shared.html', { root: config.program.webAppDirectory });
});
// Serve Jukebox Page
mstream.all('/remote', (req, res) => {
res.sendFile('remote.html', { root: program.webAppDirectory });
res.sendFile('remote.html', { root: config.program.webAppDirectory });
});
// Admin Panel
mstream.all('/admin', (req, res) => {
res.sendFile('admin.html', { root: program.webAppDirectory });
res.sendFile('admin.html', { root: config.program.webAppDirectory });
});
}
// JukeBox
jukebox.setup2(mstream, server, program);
jukebox.setup2(mstream, server, config.program);
// Shared
sharedModule.setupBeforeSecurity(mstream, program);
sharedModule.setupBeforeSecurity(mstream, config.program);
require('./src/api/auth.js').setup(mstream);
@ -87,33 +86,33 @@ exports.serveIt = async configFile => {
require('./src/api/db.js').setup(mstream);
// Album art endpoint
mstream.use('/album-art', express.static(program.storage.albumArtDirectory));
mstream.use('/album-art', express.static(config.program.storage.albumArtDirectory));
// Download Files API
require('./modules/download.js').setup(mstream);
// File Explorer API
require('./src/api/file-explorer.js').setup(mstream);
require('./modules/file-explorer.js').setup(mstream, program);
require('./modules/file-explorer.js').setup(mstream, config.program);
// DB API
require('./modules/db-read/database-public-loki.js').setup(mstream, program);
require('./modules/db-read/database-public-loki.js').setup(mstream, config.program);
if (program.federation && program.federation.folder) {
federation.setup(mstream, program);
sync.setup(program);
if (config.program.federation && config.program.federation.folder) {
federation.setup(mstream, config.program);
sync.setup(config.program);
}
// Transcoder
if (program.transcode && program.transcode.enabled === true) {
require("./modules/ffmpeg.js").setup(mstream, program);
if (config.program.transcode && config.program.transcode.enabled === true) {
require("./modules/ffmpeg.js").setup(mstream, config.program);
}
// Scrobbler
require('./modules/scrobbler.js').setup(mstream, program);
require('./modules/scrobbler.js').setup(mstream, config.program);
// Finish setting up the jukebox and shared
jukebox.setup(mstream, server, program);
sharedModule.setupAfterSecurity(mstream, program);
jukebox.setup(mstream, server, config.program);
sharedModule.setupAfterSecurity(mstream, config.program);
// TODO: Add middleware to determine if user has access to the exact file
// Setup all folders with express static
Object.keys(program.folders).forEach( key => {
mstream.use('/media/' + key + '/', express.static(program.folders[key].root));
Object.keys(config.program.folders).forEach( key => {
mstream.use('/media/' + key + '/', express.static(config.program.folders[key].root));
});
// Versioned APIs
@ -122,11 +121,11 @@ exports.serveIt = async configFile => {
// Start the server!
server.on('request', mstream);
server.listen(program.port, program.address, () => {
const protocol = program.ssl && program.ssl.cert && program.ssl.key ? 'https' : 'http';
winston.info(`Access mStream locally: ${protocol}://${program.address}:${program.port}`);
server.listen(config.program.port, config.program.address, () => {
const protocol = config.program.ssl && config.program.ssl.cert && config.program.ssl.key ? 'https' : 'http';
winston.info(`Access mStream locally: ${protocol}://${config.program.address}:${config.program.port}`);
require('./src/db/task-queue').runAfterBoot();
ddns.setup(program);
ddns.setup(config.program);
});
};

View File

@ -1,2 +0,0 @@
{
}

View File

@ -2,7 +2,7 @@ const path = require('path');
const Joi = require('joi');
const fileExplorer = require('../util/file-explorer');
const admin = require('../util/admin');
const globals = require('../global');
const config = require('../state/config');
const dbQueue = require('../db/task-queue');
const winston = require('winston/lib/winston/config');
@ -45,7 +45,7 @@ exports.setup = (mstream) => {
mstream.get("/api/v1/admin/directories", async (req, res) => {
try {
res.json(globals.program.folders);
res.json(config.program.folders);
} catch (err) {
console.log(err)
return res.status(500).json({ error: 'Failed to get vpaths' });
@ -55,7 +55,7 @@ exports.setup = (mstream) => {
mstream.get("/api/v1/admin/users", async (req, res) => {
try {
// Scrub passwords
const memClone = JSON.parse(JSON.stringify(globals.program.users));
const memClone = JSON.parse(JSON.stringify(config.program.users));
Object.keys(memClone).forEach(key => {
if(key === 'password' || key === 'salt') {
delete memClone[key];
@ -82,7 +82,7 @@ exports.setup = (mstream) => {
}
try {
await admin.addDirectory(req.body.directory, req.body.vpath, globals.program, mstream);
await admin.addDirectory(req.body.directory, req.body.vpath, config.program, mstream);
res.json({});
} catch (err) {
console.log(err)
@ -118,7 +118,7 @@ exports.setup = (mstream) => {
req.body.admin,
req.body.guest,
req.body.vpaths,
globals.program
config.program
);
res.json({});
} catch (err) {

View File

@ -2,7 +2,7 @@ const jwt = require('jsonwebtoken');
const Joi = require('joi');
const winston = require('winston');
const auth = require('../util/auth');
const globals = require('../global');
const config = require('../state/config');
exports.setup = (mstream) => {
mstream.post('/api/v1/auth/login', async (req, res) => {
@ -13,13 +13,13 @@ exports.setup = (mstream) => {
});
await schema.validateAsync(req.body);
if (!globals.program.users[req.body.username]) { throw 'user not found'; }
if (!config.program.users[req.body.username]) { throw 'user not found'; }
await auth.authenticateUser(globals.program.users[req.body.username].password, globals.program.users[req.body.username].salt, req.body.password)
await auth.authenticateUser(config.program.users[req.body.username].password, config.program.users[req.body.username].salt, req.body.password)
res.json({
vpaths: globals.program.users[req.body.username].vpaths,
token: jwt.sign({ username: req.body.username }, globals.program.secret)
vpaths: config.program.users[req.body.username].vpaths,
token: jwt.sign({ username: req.body.username }, config.program.secret)
});
}catch (err) {
winston.warn(`Failed login attempt from ${req.ip}. Username: ${req.body.username}`);
@ -30,9 +30,9 @@ exports.setup = (mstream) => {
mstream.use((req, res, next) => {
try {
// Handle No Users
if (globals.program.users && Object.keys(globals.program.users).length === 0) {
if (config.program.users && Object.keys(config.program.users).length === 0) {
req.user = {
vpaths: Object.keys(globals.program.folders),
vpaths: Object.keys(config.program.folders),
username: 'mstream-user',
admin: true
};
@ -43,7 +43,7 @@ exports.setup = (mstream) => {
const token = req.body.token || req.query.token || req.headers['x-access-token'];
if (!token) { throw 'Token Not Found'; }
const decoded = jwt.verify(token, globals.program.secret);
const decoded = jwt.verify(token, config.program.secret);
// handle federation invite tokens
if (decoded.invite && decoded.invite === true) {
@ -63,17 +63,17 @@ exports.setup = (mstream) => {
}
if (!decoded.username || !globals.program.users[decoded.username]) {
if (!decoded.username || !config.program.users[decoded.username]) {
throw 'Invalid Auth Token';
}
// TODO: Re-enable this later
// const restrictedFunctions = { '/db/recursive-scan': true };
// if (decoded.federation || decoded.jukebox || globals.program.users[decoded.username].guest) {
// if (decoded.federation || decoded.jukebox || config.program.users[decoded.username].guest) {
// if (restrictedFunctions[req.path]) { throw 'Invalid Token'; }
// }
req.user = globals.program.users[decoded.username];
req.user = config.program.users[decoded.username];
req.user.username = decoded.username;
next();

View File

@ -4,7 +4,7 @@ const Joi = require('joi');
const winston = require('winston');
const fileExplorer = require('../util/file-explorer');
const vpath = require('../util/vpath');
const globals = require('../global');
const config = require('../state/config');
exports.setup = (mstream) => {
mstream.post("/api/v1/file-explorer", async (req, res) => {
@ -41,7 +41,7 @@ exports.setup = (mstream) => {
}
// get directory contents
const folderContents = await fileExplorer.getDirectoryContents(pathInfo.fullPath, globals.program.supportedAudioFiles, reqData.sort);
const folderContents = await fileExplorer.getDirectoryContents(pathInfo.fullPath, config.program.supportedAudioFiles, reqData.sort);
// Format directory string for return value
let returnDirectory = path.join(pathInfo.vpath, pathInfo.relativePath);
@ -70,7 +70,7 @@ exports.setup = (mstream) => {
await recursiveFileScan(path.join(directory, file), fileList, path.join(relativePath, file), vPath);
} else {
const extension = fileExplorer.getFileType(file).toLowerCase();
if (globals.program.supportedAudioFiles[extension] === true) {
if (config.program.supportedAudioFiles[extension] === true) {
fileList.push(path.join(vPath, path.join(relativePath, file)).replace(/\\/g, "/"));
}
}

View File

@ -2,7 +2,7 @@ const child = require('child_process');
const path = require('path');
const winston = require('winston');
const nanoid = require('nanoid');
const globals = require('../global');
const config = require('../state/config');
const mstreamReadPublicDB = require('../../modules/db-read/database-public-loki');
const taskQueue = [];
@ -10,7 +10,7 @@ const runningTasks = new Set();
const vpathLimiter = new Set();
function addScanTask(vpath) {
if (runningTasks.size < globals.program.scanOptions.maxConcurrentTasks) {
if (runningTasks.size < config.program.scanOptions.maxConcurrentTasks) {
runScan(vpath);
} else {
taskQueue.push({ task: 'scan', vpath: vpath, id: nanoid.nanoid(8) });
@ -22,13 +22,13 @@ function removeTask(taskId) {
}
function scanAll() {
Object.keys(globals.program.folders).forEach((vpath) => {
Object.keys(config.program.folders).forEach((vpath) => {
addScanTask(vpath);
});
}
function nextTask() {
if (taskQueue.length > 0 && runningTasks.size < globals.program.scanOptions.maxConcurrentTasks && !vpathLimiter.has(taskQueue[taskQueue.length - 1].vpath)) {
if (taskQueue.length > 0 && runningTasks.size < config.program.scanOptions.maxConcurrentTasks && !vpathLimiter.has(taskQueue[taskQueue.length - 1].vpath)) {
runScan(taskQueue.pop().vpath);
}
}
@ -37,14 +37,14 @@ function runScan(vpath) {
let parseFlag = false;
const jsonLoad = {
directory: globals.program.folders[vpath].root,
directory: config.program.folders[vpath].root,
vpath: vpath,
dbPath: path.join(globals.program.storage.dbDirectory, mstreamReadPublicDB.getFileDbName()),
albumArtDirectory: globals.program.storage.albumArtDirectory,
skipImg: globals.program.scanOptions.skipImg,
saveInterval: globals.program.scanOptions.saveInterval,
pause: globals.program.scanOptions.pause,
supportedFiles: globals.program.supportedAudioFiles
dbPath: path.join(config.program.storage.dbDirectory, mstreamReadPublicDB.getFileDbName()),
albumArtDirectory: config.program.storage.albumArtDirectory,
skipImg: config.program.scanOptions.skipImg,
saveInterval: config.program.scanOptions.saveInterval,
pause: config.program.scanOptions.pause,
supportedFiles: config.program.supportedAudioFiles
};
const forkedScan = child.fork(path.join(__dirname, './scanner.js'), [JSON.stringify(jsonLoad)], { silent: true });
@ -96,8 +96,8 @@ exports.isScanning = () => {
exports.runAfterBoot = () => {
setTimeout(() => {
scanAll();
if (globals.program.scanOptions.scanInterval > 0) {
setInterval(() => runScan(), globals.program.scanOptions.scanInterval * 60 * 60 * 1000);
if (config.program.scanOptions.scanInterval > 0) {
setInterval(() => runScan(), config.program.scanOptions.scanInterval * 60 * 60 * 1000);
}
}, globals.program.scanOptions.scanDelay * 1000);
}, config.program.scanOptions.scanDelay * 1000);
}

View File

@ -1,8 +0,0 @@
exports.setup = (config) => {
exports.program = config;
}
exports.setConfigFile = (configFile) => {
exports.configFile = configFile;
}

View File

@ -1,13 +1,12 @@
const fs = require("fs").promises;
const path = require('path');
const Joi = require('joi');
const globals = require('../src/global');
const storageJoi = Joi.object({
albumArtDirectory: Joi.string().default(path.join(__dirname, '../image-cache')),
dbDirectory: Joi.string().default(path.join(__dirname, '../save/db')),
logsDirectory: Joi.string().default(path.join(__dirname, '../save/logs')),
syncConfigDirectory: Joi.string().default(path.join(__dirname, '../save/sync')),
albumArtDirectory: Joi.string().default(path.join(__dirname, '../../image-cache')),
dbDirectory: Joi.string().default(path.join(__dirname, '../../save/db')),
logsDirectory: Joi.string().default(path.join(__dirname, '../../save/logs')),
syncConfigDirectory: Joi.string().default(path.join(__dirname, '../../save/sync')),
});
const scanOptions = Joi.object({
@ -35,9 +34,9 @@ const schema = Joi.object({
noUpload: Joi.boolean().default(false),
writeLogs: Joi.boolean().default(false),
storage: storageJoi.default(storageJoi.validate({}).value),
webAppDirectory: Joi.string().default(path.join(__dirname, '../public')),
webAppDirectory: Joi.string().default(path.join(__dirname, '../../public')),
ddns: Joi.object({
iniFile: Joi.string().default(path.join(__dirname, `../frp/frps.ini`)),
iniFile: Joi.string().default(path.join(__dirname, `../../bin/rpn/frps.ini`)),
email: Joi.string().allow('').optional(),
password: Joi.string().allow('').optional(),
tested: Joi.boolean().optional(),
@ -46,7 +45,7 @@ const schema = Joi.object({
}),
transcode: Joi.object({
enabled: Joi.boolean().default(false),
ffmpegDirectory: Joi.string().default(path.join(__dirname, '../bin/ffmpeg')),
ffmpegDirectory: Joi.string().default(path.join(__dirname, '../../bin/ffmpeg')),
defaultCodec: Joi.string().valid('mp3', 'opus', 'aac').default('opus'),
defaultBitrate: Joi.string().valid('64k', '128k', '192k', '96k').default('96k')
}).optional(),
@ -78,11 +77,9 @@ const schema = Joi.object({
}).optional()
});
exports.setup = async configFile => {
globals.setConfigFile(configFile);
const config = JSON.parse(await fs.readFile(configFile, 'utf8'))
const program = await schema.validateAsync(config, { allowUnknown: true });
const program = JSON.parse(await fs.readFile(configFile, 'utf8'));
exports.configFile = configFile;
// Verify paths are real
for (let folder in program.folders) {
@ -98,11 +95,11 @@ exports.setup = async configFile => {
program.secret = buffer.toString('base64');
});
}
return program;
exports.program = await schema.validateAsync(program, { allowUnknown: true });
}
exports.getDefaults = async () => {
exports.getDefaults = () => {
const { value, error } = schema.validate({});
return value;
}
}

View File

@ -1,7 +1,7 @@
const fs = require("fs").promises;
const express = require('express');
const auth = require('./auth');
const globals = require('../global');
const config = require('../state/config');
exports.loadFile = async (file) => {
return JSON.parse(await fs.readFile(file, 'utf-8'));
@ -25,9 +25,9 @@ exports.addDirectory = async (directory, vpath, program, mstream) => {
memClone[vpath] = { root: directory };
// add directory to config file
const config = await this.loadFile(globals.configFile);
config.folders = memClone;
await this.saveFile(config, globals.configFile);
const loadConfig = await this.loadFile(config.configFile);
loadConfig.folders = memClone;
await this.saveFile(loadConfig, config.configFile);
// add directory to program
program.folders[vpath] = { root: directory };
@ -56,9 +56,9 @@ exports.addUser = async (username, password, admin, guest, vpaths, program) => {
const memClone = JSON.parse(JSON.stringify(program.users));
memClone[username] = newUser;
const config = await this.loadFile(globals.configFile);
config.users = memClone;
await this.saveFile(config, globals.configFile);
const loadConfig = await this.loadFile(config.configFile);
loadConfig.users = memClone;
await this.saveFile(loadConfig, config.configFile);
program.users[username] = newUser;
}

View File

@ -1,8 +1,8 @@
const path = require('path');
const globals = require('../global');
const config = require('../state/config');
exports.getVPathInfo = (url, user) => {
if (!globals.program) { throw 'Not Configured'; }
if (!config.program) { throw 'Not Configured'; }
// remove leading slashes
if (url.charAt(0) === '/') {
@ -16,7 +16,7 @@ exports.getVPathInfo = (url, user) => {
return false;
}
const baseDir = globals.program.folders[vpath].root;
const baseDir = config.program.folders[vpath].root;
return {
vpath: vpath,
basePath: baseDir,