/* ---------------------------------------------------------------------------- Question Server GitLab: 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 . ------------------------------------------------------------------------- */ console.log('Node version: ' + process.version) console.log('Current working directory: ' + process.cwd()) const port = process.env.PORT || 8080 const httpsport = 5001 // import os from 'os' // os.setPriority(10) // console.log(`Process priority set to ${os.getPriority()}`) import express from 'express' import http from 'http' import https from 'https' import cors from 'cors' import cookieParser from 'cookie-parser' import { v4 as uuidv4 } from 'uuid' import type { Database } from 'better-sqlite3' import logger from './utils/logger' import utils from './utils/utils' import dbtools from './utils/dbtools' import reqlogger from './middlewares/reqlogger.middleware' import idStats from './utils/ids' import { paths, validateAndSetupFiles } from './utils/files' import constants from './constants' const logFile = paths.logDir + logger.logFileName const vlogFile = paths.vlogDir + logger.logFileName function moveLogIfNotFromToday(path: string, to: string) { if (utils.FileExists(path)) { const today = new Date() const stat = utils.statFile(path) if ( today.getFullYear() !== stat.mtime.getFullYear() || today.getMonth() !== stat.mtime.getMonth() || today.getDate() !== stat.mtime.getDate() ) { utils.renameFile(path, to + utils.GetDateString(stat.mtime)) } } } moveLogIfNotFromToday(logFile, paths.logDir) moveLogIfNotFromToday(vlogFile, paths.vlogDir) interface Modules { [name: string]: Module } interface Module { path: string publicdirs: Array name: string route: string nextdir?: string isNextJs?: boolean app: express.Application dailyAction: Function cleanup: Function } export interface SetupData { url: string publicdirs: Array userDB?: Database nextdir?: string httpServer: http.Server httpsServer: https.Server } const filesValid = validateAndSetupFiles() if (!filesValid) { const msg = 'Not all files are valid which are needed to run the server! Please resolve the above issues, and start again.' logger.Log(msg, 'red') throw new Error(msg) } idStats.Load() logger.Load() const userDB = dbtools.GetDB(paths.usersDBPath) let modules: Modules = utils.ReadJSON(paths.modulesFile) const debugLevel = parseInt(process.env.NS_LOGLEVEL) || 0 logger.Log('Loglevel is: ' + debugLevel) logger.Log(`Log path: ${logFile}`) logger.Log(`vLog path: ${vlogFile}`) try { if (utils.FileExists(paths.extraModulesFile)) { const extraModules = JSON.parse(utils.ReadFile(paths.extraModulesFile)) modules = { ...extraModules, ...modules, } } } catch (err) { logger.Log('Failed to read extra modules file') console.error(err) } process.on('SIGINT', () => exit('SIGINT')) process.on('SIGTERM', () => exit('SIGTERM')) function exit(reason: string) { console.log() logger.Log(`Exiting, reason: ${reason}`) Object.keys(modules).forEach((key) => { const module = modules[key] if (module.cleanup) { try { module.cleanup() } catch (err) { logger.Log( `Error in ${key} cleanup! Details in STDERR`, logger.GetColor('redbg') ) console.error(err) } } }) logger.Log('Closing Auth DB') userDB.close() process.exit() } let certsLoaded = false let certs: { key: string; cert: string; ca: string } // https://certbot.eff.org/ if ( !process.env.NS_NO_HTTPS_FORCE && utils.FileExists(paths.privkeyFile) && utils.FileExists(paths.fullchainFile) && utils.FileExists(paths.chainFile) ) { try { certs = { key: utils.ReadFile(paths.privkeyFile), cert: utils.ReadFile(paths.fullchainFile), ca: utils.ReadFile(paths.chainFile), } certsLoaded = true } catch (err) { logger.Log('Error loading cert files!', logger.GetColor('redbg')) console.error(err) } } const app = express() const httpServer = http.createServer(app) let httpsServer: https.Server if (!process.env.NS_NO_HTTPS_FORCE && certsLoaded) { httpsServer = https.createServer(certs, app) logger.Log('Listening on port: ' + httpsport + ' (https)') } else { logger.Log('Https not avaible', 'yellowbg') } if (!process.env.NS_NO_HTTPS_FORCE) { app.use(function (req, res, next) { if (req.secure) { next() } else { logger.DebugLog( `HTTPS ${req.method} redirect to: ${ 'https://' + req.headers.host + req.url }`, 'https', 1 ) if (req.method === 'POST') { res.redirect(307, 'https://' + req.headers.host + req.url) } else { res.redirect('https://' + req.headers.host + req.url) } } }) } // https://github.com/expressjs/cors#configuration-options app.use( cors({ credentials: true, origin: true, }) ) const cookieSecret = uuidv4() app.use(cookieParser(cookieSecret)) app.set('view engine', 'ejs') app.set('views', ['./src/modules/api/views', './src/sharedViews']) if (!utils.FileExists(paths.statExcludeFile)) { utils.WriteFile('[]', paths.statExcludeFile) } const excludeFromStats = utils.ReadJSON(paths.statExcludeFile) app.use( reqlogger({ loggableKeywords: ['news.json'], loggableModules: [], exceptions: ['_next/static'], excludeFromStats: excludeFromStats, }) ) Object.keys(modules).forEach(function (key) { const module = modules[key] try { const mod = require(module.path).default // eslint-disable-line // const mod = require(module.path) module.publicdirs.forEach((pdir) => { utils.CreatePath(pdir) }) if (mod.setup) { mod.setup({ url: constants.domain, // used by api.ts -> userManagement.ts -> cookies userDB: userDB, publicdirs: module.publicdirs, nextdir: module.nextdir, httpServer: httpServer, httpsServer: httpsServer, }) } const modApp = mod.getApp() module.app = modApp.app module.dailyAction = modApp.dailyAction module.cleanup = modApp.cleanup logger.Log( `Module "${mod.name}" loaded at "${module.route}"`, logger.GetColor('yellow') ) app.use(module.route, module.app) } catch (err) { logger.Log(`Error setting up submodule: ${module.name}`, 'redbg') console.error(err) } }) app.get('*', (req, res) => { if (req.headers['content-type'] === 'application/json') { res.status(404).end() } else { res.status(404).render('404') } }) app.post('*', (req, res) => { if (req.headers['content-type'] === 'application/json') { res.status(404).end() } else { res.status(404).render('404') } }) setLogTimer() function setLogTimer() { const now = new Date() const night = new Date( now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 1 ) logger.DebugLog(`Next daily action: ${night}`, 'daily', 1) const msToMidnight = night.getTime() - now.getTime() + 10000 logger.DebugLog(`msToMidnight: ${msToMidnight}`, 'daily', 1) logger.DebugLog(`Seconds To Midnight: ${msToMidnight / 1000}`, 'daily', 1) if (msToMidnight < 0) { logger.Log( `Error setting up Log Timer, msToMidnight is negative! (${msToMidnight})`, logger.GetColor('redbg') ) return } setTimeout(function () { LogTimerAction() rotateLog() setLogTimer() }, msToMidnight) } function rotateLog() { const date = new Date() date.setDate(date.getDate() - 1) const fname = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2) if (utils.FileExists(logFile)) { utils.CopyFile(logFile, paths.logDir + fname) } if (utils.FileExists(vlogFile)) { utils.CopyFile(vlogFile, paths.vlogDir + fname) } utils.WriteFile(fname, logFile) utils.WriteFile(fname, vlogFile) } function LogTimerAction() { logger.DebugLog(`Running Log Timer Action`, 'daily', 1) Object.keys(modules).forEach((key) => { const module = modules[key] logger.DebugLog(`Ckecking ${key}`, 'daily', 1) if (module.dailyAction) { try { logger.Log(`Running daily action of ${key}`) module.dailyAction() } catch (err) { logger.Log( `Error in ${key} daily action! Details in STDERR`, logger.GetColor('redbg') ) console.error(err) } } }) const line = '===================================================================================================================================================' logger.Log(line) } logger.Log('Node version: ' + process.version) logger.Log('Current working directory: ' + process.cwd()) logger.Log('Listening on port: ' + logger.C('blue') + port + logger.C()) if (process.getuid && process.getuid() === 0) { logger.Log('Running as root', 'redbg') } httpServer.listen(port) if (httpsServer) { httpsServer.listen(httpsport) }