mStream/src/util/admin.js
2022-03-26 18:00:13 -04:00

390 lines
12 KiB
JavaScript

const fs = require("fs").promises;
const path = require("path");
const child = require('child_process');
const express = require('express');
const auth = require('./auth');
const config = require('../state/config');
const mStreamServer = require('../server');
const dbQueue = require('../db/task-queue');
const logger = require('../logger');
const db = require('../db/manager');
const syncthing = require('../state/syncthing');
exports.loadFile = async (file) => {
return JSON.parse(await fs.readFile(file, 'utf-8'));
}
exports.saveFile = async (saveData, file) => {
return await fs.writeFile(file, JSON.stringify(saveData, null, 2), 'utf8')
}
exports.addDirectory = async (directory, vpath, autoAccess, isAudioBooks, mstream) => {
// confirm directory is real
const stat = await fs.stat(directory);
if (!stat.isDirectory()) { throw `${directory} is not a directory` };
if (config.program.folders[vpath]) { throw `'${vpath}' is already loaded into memory`; }
// This extra step is so we can handle the process like a SQL transaction
// The new var is a copy so the original program isn't touched
// Once the file save is complete, the new user will be added
const memClone = JSON.parse(JSON.stringify(config.program.folders));
memClone[vpath] = { root: directory };
if (isAudioBooks) { memClone[vpath].type = 'audio-books'; }
// add directory to config file
const loadConfig = await this.loadFile(config.configFile);
loadConfig.folders = memClone;
if (autoAccess === true) {
const memCloneUsers = JSON.parse(JSON.stringify(config.program.users));
Object.values(memCloneUsers).forEach(user => {
user.vpaths.push(vpath);
});
loadConfig.users = memCloneUsers;
}
await this.saveFile(loadConfig, config.configFile);
// add directory to program
config.program.folders[vpath] = memClone;
if (autoAccess === true) {
Object.values(config.program.users).forEach(user => {
user.vpaths.push(vpath);
});
}
// add directory to server routing
mstream.use(`/media/${vpath}/`, express.static(directory));
}
exports.removeDirectory = async (vpath) => {
if (!config.program.folders[vpath]) { throw `'${vpath}' not found`; }
const memCloneFolders = JSON.parse(JSON.stringify(config.program.folders));
delete memCloneFolders[vpath];
const memCloneUsers = JSON.parse(JSON.stringify(config.program.users));
Object.values(memCloneUsers).forEach(user => {
if (user.vpaths.includes(vpath)) {
user.vpaths.splice(user.vpaths.indexOf(vpath), 1);
}
});
const loadConfig = await this.loadFile(config.configFile);
loadConfig.folders = memCloneFolders;
loadConfig.users = memCloneUsers;
await this.saveFile(loadConfig, config.configFile);
db.getFileCollection().findAndRemove({ 'vpath': { '$eq': vpath } });
db.saveFilesDB();
// reboot server
mStreamServer.reboot();
}
exports.addUser = async (username, password, admin, vpaths) => {
if (config.program.users[username]) { throw `'${username}' is already loaded into memory`; }
// hash password
const hash = await auth.hashPassword(password);
const newUser = {
vpaths: vpaths,
password: hash.hashPassword,
salt: hash.salt,
admin: admin
};
// This extra step is so we can handle the process like a SQL transaction
// The new var is a copy so the original program isn't touched
// Once the file save is complete, the new user will be added
const memClone = JSON.parse(JSON.stringify(config.program.users));
memClone[username] = newUser;
const loadConfig = await this.loadFile(config.configFile);
loadConfig.users = memClone;
await this.saveFile(loadConfig, config.configFile);
config.program.users[username] = newUser;
// TODO: add user from scrobbler
}
exports.deleteUser = async (username) => {
if (!config.program.users[username]) { throw `'${username}' does not exist`; }
const memClone = JSON.parse(JSON.stringify(config.program.users));
delete memClone[username];
const loadConfig = await this.loadFile(config.configFile);
loadConfig.users = memClone;
await this.saveFile(loadConfig, config.configFile);
delete config.program.users[username];
db.getUserMetadataCollection().findAndRemove({ 'user': { '$eq': username } });
db.saveUserDB();
db.getPlaylistCollection().findAndRemove({ 'user': { '$eq': username } });
db.saveUserDB();
db.getShareCollection().findAndRemove({ 'user': { '$eq': username } });
db.saveUserDB();
// TODO: Remove user from scrobbler
}
exports.editUserPassword = async (username, password) => {
if (!config.program.users[username]) { throw `'${username}' does not exist`; }
const hash = await auth.hashPassword(password);
const memClone = JSON.parse(JSON.stringify(config.program.users));
memClone[username].password = hash.hashPassword;
memClone[username].salt = hash.salt;
const loadConfig = await this.loadFile(config.configFile);
loadConfig.users = memClone;
await this.saveFile(loadConfig, config.configFile);
config.program.users[username].password = hash.hashPassword;
config.program.users[username].salt = hash.salt;
}
exports.editUserVPaths = async (username, vpaths) => {
if (!config.program.users[username]) { throw `'${username}' does not exist`; }
const memClone = JSON.parse(JSON.stringify(config.program.users));
memClone[username].vpaths = vpaths;
const loadConfig = await this.loadFile(config.configFile);
loadConfig.users = memClone;
await this.saveFile(loadConfig, config.configFile);
config.program.users[username].vpaths = vpaths;
}
exports.editUserAccess = async (username, admin) => {
if (!config.program.users[username]) { throw `'${username}' does not exist`; }
const memClone = JSON.parse(JSON.stringify(config.program.users));
memClone[username].admin = admin;
const loadConfig = await this.loadFile(config.configFile);
loadConfig.users = memClone;
await this.saveFile(loadConfig, config.configFile);
config.program.users[username].admin = admin;
}
exports.editPort = async (port) => {
if (config.program.port === port) { return; }
const loadConfig = await this.loadFile(config.configFile);
loadConfig.port = port;
await this.saveFile(loadConfig, config.configFile);
// reboot server
mStreamServer.reboot();
}
exports.editMaxRequestSize = async (maxRequestSize) => {
if (config.program.maxRequestSize === maxRequestSize) { return; }
const loadConfig = await this.loadFile(config.configFile);
loadConfig.maxRequestSize = maxRequestSize;
await this.saveFile(loadConfig, config.configFile);
// reboot server
mStreamServer.reboot();
}
exports.editUpload = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
loadConfig.noUpload = val;
await this.saveFile(loadConfig, config.configFile);
config.program.noUpload = val;
}
exports.editAddress = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
loadConfig.address = val;
await this.saveFile(loadConfig, config.configFile);
mStreamServer.reboot();
}
exports.editSecret = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
loadConfig.secret = val;
await this.saveFile(loadConfig, config.configFile);
config.program.secret = val;
}
exports.editScanInterval = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.scanInterval = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.scanInterval = val;
// update timer
dbQueue.resetScanInterval();
}
exports.editSaveInterval = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.saveInterval = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.saveInterval = val;
}
exports.editSkipImg = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.skipImg = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.skipImg = val;
}
exports.editPause = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.pause = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.pause = val;
}
exports.editBootScanDelay = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.bootScanDelay = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.bootScanDelay = val;
}
exports.editMaxConcurrentTasks = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.maxConcurrentTasks = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.maxConcurrentTasks = val;
}
exports.editCompressImages = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.scanOptions) { loadConfig.scanOptions = {}; }
loadConfig.scanOptions.compressImage = val;
await this.saveFile(loadConfig, config.configFile);
config.program.scanOptions.compressImage = val;
}
exports.editWriteLogs = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
loadConfig.writeLogs = val;
await this.saveFile(loadConfig, config.configFile);
config.program.writeLogs = val;
if (val === false) {
logger.reset();
} else {
logger.addFileLogger(config.program.storage.logsDirectory);
}
}
exports.enableTranscode = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.transcode) { loadConfig.transcode = {}; }
loadConfig.transcode.enabled = val;
await this.saveFile(loadConfig, config.configFile);
config.program.transcode.enabled = val;
}
exports.editDefaultCodec = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.transcode) { loadConfig.transcode = {}; }
loadConfig.transcode.defaultCodec = val;
await this.saveFile(loadConfig, config.configFile);
config.program.transcode.defaultCodec = val;
}
exports.editDefaultBitrate = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.transcode) { loadConfig.transcode = {}; }
loadConfig.transcode.defaultBitrate = val;
await this.saveFile(loadConfig, config.configFile);
config.program.transcode.defaultBitrate = val;
}
exports.editDefaultAlgorithm = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
if (!loadConfig.transcode) { loadConfig.transcode = {}; }
loadConfig.transcode.algorithm = val;
await this.saveFile(loadConfig, config.configFile);
config.program.transcode.algorithm = val;
}
exports.lockAdminApi = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
loadConfig.lockAdmin = val;
await this.saveFile(loadConfig, config.configFile);
config.program.lockAdmin = val;
}
exports.enableFederation = async (val) => {
const loadConfig = await this.loadFile(config.configFile);
loadConfig.federation.enabled = val;
await this.saveFile(loadConfig, config.configFile);
config.program.federation.enabled = val;
syncthing.setup();
}
exports.removeSSL = async () => {
const loadConfig = await this.loadFile(config.configFile);
delete loadConfig.ssl;
await this.saveFile(loadConfig, config.configFile);
delete config.program.ssl;
mStreamServer.reboot();
}
function testSSL(jsonLoad) {
return new Promise((resolve, reject) => {
child.fork(path.join(__dirname, './ssl-test.js'), [JSON.stringify(jsonLoad)], { silent: true }).on('close', (code) => {
if (code !== 0) {
return reject('SSL Failure');
}
resolve();
});
});
}
exports.setSSL = async (cert, key) => {
const sslObj = { key, cert };
await testSSL(sslObj);
const loadConfig = await this.loadFile(config.configFile);
loadConfig.ssl = sslObj;
await this.saveFile(loadConfig, config.configFile);
config.program.ssl = sslObj;
mStreamServer.reboot();
}