mirror of
https://github.com/chuot/rdio-scanner.git
synced 2026-08-13 08:33:00 -06:00
226 lines
9.1 KiB
JavaScript
226 lines
9.1 KiB
JavaScript
/*
|
|
* *****************************************************************************
|
|
* Copyright (C) 2019-2021 Chrystian Huot <chrystian.huot@saubeo.solutions>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
* ****************************************************************************
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import { spawnSync } from 'child_process';
|
|
import compression from 'compression';
|
|
import cors from 'cors';
|
|
import EventEmitter from 'events';
|
|
import dotenv from 'dotenv';
|
|
import express from 'express';
|
|
import fs from 'fs';
|
|
import helmet from 'helmet';
|
|
import http from 'http';
|
|
import https from 'https';
|
|
import path from 'path';
|
|
import Sequelize from 'sequelize';
|
|
import url from 'url';
|
|
|
|
import { RdioScanner } from './lib/rdio-scanner/main.js';
|
|
|
|
export class App extends EventEmitter {
|
|
constructor() {
|
|
super();
|
|
|
|
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
|
|
const configFile = path.resolve(process.env.APP_DATA || dirname, 'config.json');
|
|
|
|
const staticFile = 'index.html';
|
|
|
|
const staticDir = fs.existsSync(path.resolve(dirname, `../client/${staticFile}`))
|
|
? path.resolve(dirname, '../client')
|
|
: path.resolve(dirname, '../client/dist/rdio-scanner');
|
|
|
|
const openssl = !spawnSync('openssl', ['version']).error;
|
|
|
|
dotenv.config();
|
|
|
|
if (fs.existsSync(configFile)) {
|
|
this.config = JSON.parse(fs.readFileSync(configFile));
|
|
}
|
|
|
|
if (this.config === null || typeof this.config !== 'object') {
|
|
this.config = {};
|
|
}
|
|
|
|
const nodejs = this.config.nodejs || {};
|
|
|
|
this.config.nodejs = {
|
|
env: typeof nodejs.env === 'string' && nodejs.env.length ? nodejs.env : 'production',
|
|
host: typeof nodejs.host === 'string' && nodejs.host.length ? nodejs.host : '0.0.0.0',
|
|
port: typeof nodejs.port === 'number' ? nodejs.port : 3000,
|
|
ssl: typeof nodejs.ssl === 'boolean' ? nodejs.ssl : false,
|
|
sslCa: typeof nodejs.sslCa === 'string' && nodejs.sslCa.length ? nodejs.sslCa : 'ca.crt',
|
|
sslCert: typeof nodejs.sslCert === 'string' && nodejs.sslCert.length ? nodejs.sslCert : 'server.crt',
|
|
sslKey: typeof nodejs.sslKey === 'string' && nodejs.sslKey.length ? nodejs.sslKey : 'server.key',
|
|
};
|
|
|
|
const sequelize = this.config.sequelize || {};
|
|
|
|
this.config.sequelize = typeof sequelize.dialect === 'string' && sequelize.dialect.length && sequelize.dialect !== 'sqlite' ? {
|
|
dialect: sequelize.dialect,
|
|
database: typeof sequelize.database === 'string' && sequelize.database.length ? sequelize.database : 'db',
|
|
host: typeof sequelize.host === 'string' && sequelize.host.length ? sequelize.host : '',
|
|
port: typeof sequelize.port === 'number' ? sequelize.port : 3306,
|
|
username: typeof sequelize.username === 'string' && sequelize.username.length ? sequelize.username : 'user',
|
|
password: typeof sequelize.password === 'string' && sequelize.password.length ? sequelize.password : 'pass',
|
|
|
|
} : {
|
|
dialect: 'sqlite',
|
|
storage: typeof sequelize.storage === 'string' && sequelize.storage.length ? sequelize.storage : 'database.sqlite',
|
|
};
|
|
|
|
const sslCaCert = path.resolve(process.env.APP_DATA || dirname, this.config.nodejs.sslCa);
|
|
const sslServerCert = path.resolve(process.env.APP_DATA || dirname, this.config.nodejs.sslCert);
|
|
const sslServerKey = path.resolve(process.env.APP_DATA || dirname, this.config.nodejs.sslKey);
|
|
|
|
if (openssl && !(fs.existsSync(sslCaCert) && fs.existsSync(sslServerCert) && fs.existsSync(sslServerKey))) {
|
|
const sslCaKey = sslCaCert.replace(/\..*$/, '.key');
|
|
const sslCaSerial = sslCaCert.replace(/\..*$/, '.srl');
|
|
const sslServerCsr = sslServerCert.replace(/\..*$/, '.csr');
|
|
|
|
spawnSync('openssl', [
|
|
'req', '-new', '-newkey', 'rsa:4096', '-batch', '-nodes', '-x509', '-days', '7305',
|
|
'-subj', '/CN=Rdio Scanner CA', '-keyout', sslCaKey, '-out', sslCaCert
|
|
]);
|
|
|
|
spawnSync('openssl', [
|
|
'req', '-new', '-newkey', 'rsa:4096', '-batch', '-nodes', '-subj', '/CN=Rdio Scanner',
|
|
'-keyout', sslServerKey, '-out', sslServerCsr,
|
|
]);
|
|
|
|
spawnSync('openssl', [
|
|
'x509', '-in', sslServerCsr, '-days', '7305', '-req', '-CA', sslCaCert,
|
|
'-CAkey', sslCaKey, '-CAcreateserial', '-CAserial', sslCaSerial, '-out', sslServerCert,
|
|
]);
|
|
|
|
fs.rmSync(sslCaSerial);
|
|
fs.rmSync(sslServerCsr);
|
|
}
|
|
|
|
if (this.config.sequelize === null || typeof this.config.sequelize !== 'object') {
|
|
this.config.sequelize = {};
|
|
}
|
|
|
|
if (typeof this.config.sequelize.dialect !== 'string' || !this.config.sequelize.dialect.length) {
|
|
this.config.sequelize.dialect = 'sqlite';
|
|
}
|
|
|
|
if (this.config.sequelize.dialect === 'sqlite' &&
|
|
(typeof this.config.sequelize.storage !== 'string' || !this.config.sequelize.storage.length)) {
|
|
this.config.sequelize.storage = 'database.sqlite';
|
|
}
|
|
|
|
this.router = express();
|
|
this.router.disable('x-powered-by');
|
|
this.router.use(compression());
|
|
this.router.use(cors());
|
|
this.router.use(express.json({ limit: '10mb' }));
|
|
this.router.use(express.urlencoded({ extended: false, limit: '10mb' }));
|
|
this.router.use(helmet({ contentSecurityPolicy: false }));
|
|
this.router.use(express.static(staticDir));
|
|
this.router.use((req, res, next) => {
|
|
if (
|
|
req.path.startsWith('/api') ||
|
|
req.accepts('html', 'json', 'xml') !== 'html' ||
|
|
path.extname(req.path) !== ''
|
|
) {
|
|
return next();
|
|
}
|
|
|
|
if (fs.existsSync(path.join(staticDir, staticFile))) {
|
|
return res.sendFile(staticFile, { root: staticDir });
|
|
|
|
} else {
|
|
return res.send('A new build is being prepared. Please check back in a few minutes.');
|
|
}
|
|
});
|
|
this.router.set(this.config.nodejs.port);
|
|
|
|
this.sequelize = new Sequelize(Object.assign({}, this.config.sequelize, {
|
|
dialectOptions: { autoJsonMap: false },
|
|
logging: false,
|
|
storage: this.config.sequelize.storage
|
|
? path.resolve(process.env.APP_DATA || process.env.DB_STORAGE || dirname, this.config.sequelize.storage)
|
|
: '',
|
|
}));
|
|
|
|
if (
|
|
this.config.nodejs.env !== 'development' && this.config.nodejs.ssl === true &&
|
|
fs.existsSync(sslServerCert) && fs.existsSync(sslServerKey)
|
|
) {
|
|
const options = {
|
|
cert: fs.readFileSync(sslServerCert),
|
|
key: fs.readFileSync(sslServerKey),
|
|
};
|
|
|
|
if (fs.existsSync(this.config.nodejs.sslCA)) {
|
|
options.ca = fs.readFileSync(sslCaCert);
|
|
}
|
|
|
|
this.httpServer = https.createServer(options, this.router);
|
|
|
|
} else {
|
|
this.httpServer = http.createServer(this.router);
|
|
}
|
|
|
|
this.once('ready', () => {
|
|
let config = Object.keys(this.config)
|
|
.sort((a, b) => a.localeCompare(b))
|
|
.reduce((conf, key) => {
|
|
conf[key] = this.config[key];
|
|
return conf;
|
|
}, {});
|
|
|
|
try {
|
|
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
|
|
} catch (error) {
|
|
console.error(error.message);
|
|
}
|
|
});
|
|
|
|
RdioScanner.migrate(this.sequelize).then(() => {
|
|
this.rdioScanner = new RdioScanner(this);
|
|
|
|
this.rdioScanner.once('ready', () => {
|
|
// Starting from v5.1, config is now in the database.
|
|
// Delete obsolete config from the config.json file.
|
|
if ('rdioScanner' in this.config) {
|
|
delete this.config.rdioScanner;
|
|
}
|
|
|
|
this.httpServer.listen(this.config.nodejs.port, this.config.nodejs.host, () => {
|
|
const scheme = this.httpServer instanceof https.Server ? 'https' : 'http';
|
|
|
|
this.url = `${scheme}://${this.config.nodejs.host}:${this.config.nodejs.port}`;
|
|
|
|
console.log(`Server is running at ${this.url}`);
|
|
});
|
|
|
|
this.emit('ready');
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export const app = new App();
|