diff --git a/modules/federation.js b/modules/federation.js index 1441f9c..a53843b 100644 --- a/modules/federation.js +++ b/modules/federation.js @@ -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 }); } diff --git a/mstream.js b/mstream.js index f54c272..3ae2645 100755 --- a/mstream.js +++ b/mstream.js @@ -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); }); }; diff --git a/save/conf/default.json b/save/conf/default.json deleted file mode 100644 index 7a73a41..0000000 --- a/save/conf/default.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/src/api/admin.js b/src/api/admin.js index 3e7f1aa..86e2602 100644 --- a/src/api/admin.js +++ b/src/api/admin.js @@ -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) { diff --git a/src/api/auth.js b/src/api/auth.js index b02f50a..8cc57b6 100644 --- a/src/api/auth.js +++ b/src/api/auth.js @@ -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(); diff --git a/src/api/file-explorer.js b/src/api/file-explorer.js index dcb98f4..de48b62 100644 --- a/src/api/file-explorer.js +++ b/src/api/file-explorer.js @@ -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, "/")); } } diff --git a/src/db/task-queue.js b/src/db/task-queue.js index be9d55a..64ca13f 100644 --- a/src/db/task-queue.js +++ b/src/db/task-queue.js @@ -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); } \ No newline at end of file diff --git a/src/global.js b/src/global.js deleted file mode 100644 index f4b6249..0000000 --- a/src/global.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.setup = (config) => { - exports.program = config; -} - -exports.setConfigFile = (configFile) => { - exports.configFile = configFile; -} - diff --git a/modules/defaults.js b/src/state/config.js similarity index 84% rename from modules/defaults.js rename to src/state/config.js index 796ab2f..a8b132d 100644 --- a/modules/defaults.js +++ b/src/state/config.js @@ -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; -} +} \ No newline at end of file diff --git a/src/util/admin.js b/src/util/admin.js index b441d0e..7c9c146 100644 --- a/src/util/admin.js +++ b/src/util/admin.js @@ -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; } diff --git a/src/util/vpath.js b/src/util/vpath.js index c2b1868..fa3ea78 100644 --- a/src/util/vpath.js +++ b/src/util/vpath.js @@ -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,