UNPKG

reldens

Version:
270 lines (253 loc) 11.9 kB
/** * * Reldens - Installer * */ const fs = require('fs'); const path = require('path'); const express = require('express'); const bodyParser = require('body-parser'); const session = require('express-session'); const crypto = require('crypto'); const { TemplateEngine } = require('./template-engine'); const { DriversMap } = require('./storage/drivers-map'); const { Logger, sc } = require('@reldens/utils'); class Installer { constructor(props) { this.themeManager = sc.get(props, 'themeManager'); this.startCallback = sc.get(props, 'startCallback'); this.secretKey = this.generateSecretKey(); } generateSecretKey() { return crypto.randomBytes(32).toString('hex'); } isInstalled() { if('' === sc.get(this.themeManager, 'installationLockPath', '')){ return false; } return fs.existsSync(this.themeManager.installationLockPath); } async prepareSetup(app) { if(fs.existsSync(this.themeManager.installerPathIndex)){ await fs.rmSync(this.themeManager.installerPathIndex, {recursive: true}); } Logger.info('Building installer...'); await this.themeManager.buildInstaller(); app.use(bodyParser.urlencoded({extended: true})); app.use(express.static( this.themeManager.installerPath, { index: false, filter: (req, file) => { return '.html' !== path.extname(file); } } )); // @IMPORTANT: do not use session secret like this in the app, it is not secure. // @NOTE: Include "secure: true" for that case (that only works through SSL). // app.use(session({secret: this.secretKey, resave: true, saveUninitialized: true, cookie: {secure: true}})); app.use(session({secret: this.secretKey, resave: true, saveUninitialized: true})); app.use(async (req, res, next) => { return await this.executeForEveryRequest(next, req, res); }); app.post('/install', async (req, res) => { return await this.executeInstallProcess(req, res); }); } async executeInstallProcess(req, res) { if(this.isInstalled()){ return res.redirect('/?redirect=already-installed'); } let templateVariables = req.body; templateVariables['app-limit-key-generator'] = '' !== templateVariables['app-trusted-proxy'] ? 1 : 0; this.setCheckboxesMissingValues(templateVariables); this.setSelectedOptions(templateVariables); req.session.templateVariables = templateVariables; let selectedDriver = DriversMap[templateVariables['db-storage-driver']]; if(!selectedDriver){ Logger.critical('Invalid storage driver: ' + templateVariables['db-storage-driver']); return res.redirect('/?error=invalid-driver'); } let dbConfig = { client: templateVariables['db-client'], config: { host: templateVariables['db-host'], port: Number(templateVariables['db-port']), database: templateVariables['db-name'], user: templateVariables['db-username'], password: templateVariables['db-password'], multipleStatements: true }, debug: '1' === process?.env?.RELDENS_DEBUG_QUERIES }; try { let dbDriver = new selectedDriver(dbConfig); if(!await dbDriver.connect()){ Logger.critical('Connection failed, please check the storage configuration.'); return res.redirect('/?error=connection-failed'); } if(!sc.isObjectFunction(dbDriver, 'rawQuery')){ Logger.critical('Method "rawQuery" not found in the specified storage driver.'); return res.redirect('/?error=raw-query-not-found'); } let migrationsPath = path.join(this.themeManager.reldensModulePath, 'migrations', 'production'); await this.executeQueryFile(migrationsPath, dbDriver, 'reldens-install-v4.0.0.sql'); Logger.info('Installed tables.'); if('1' === templateVariables['db-basic-config']){ await this.executeQueryFile(migrationsPath, dbDriver, 'reldens-basic-config-v4.0.0.sql'); Logger.info('Installed basic-config.'); } if('1' === templateVariables['db-sample-data']){ await this.executeQueryFile(migrationsPath, dbDriver, 'reldens-sample-data-v4.0.0.sql'); Logger.info('Installed sample-data.'); } } catch (error) { Logger.critical('There was an error during the installation process.', error); return res.redirect('/?error=db-installation-process-failed'); } if('' === templateVariables['app-admin-path']){ templateVariables['app-admin-path'] = '/reldens-admin'; } if('' === templateVariables['app-admin-secret']){ return res.redirect('/?error=db-installation-process-failed-missing-admin-secret'); } let envFilePath = path.join(this.themeManager.projectRoot, '.env'); let knexFilePath = path.join(this.themeManager.projectRoot, 'knexfile.js'); let lockFilePath = path.join(this.themeManager.projectRoot, 'install.lock'); try { let envDistTemplate = fs.readFileSync( this.themeManager.reldensModulePathInstallTemplateEnvDist, {encoding: this.encoding()} ); let envFileContent = await TemplateEngine.render(envDistTemplate, templateVariables); fs.writeFileSync(envFilePath, envFileContent); let knexDistTemplate = fs.readFileSync( this.themeManager.reldensModulePathInstallTemplateKnexDist, {encoding: this.encoding()} ); let knexFileContent = await TemplateEngine.render(knexDistTemplate, templateVariables); fs.writeFileSync(knexFilePath, knexFileContent); fs.writeFileSync(lockFilePath, ''); Logger.info('Installation success!'); if(this.startCallback){ Logger.info('Running Server Start callback...'); await this.startCallback(); } return res.redirect(templateVariables['app-host']+':'+templateVariables['app-port']+'/?success=1'); } catch (error) { if(fs.existsSync(envFilePath)){ fs.rmSync(envFilePath); } if(fs.existsSync(knexFilePath)){ fs.rmSync(knexFilePath); } if(fs.existsSync(lockFilePath)){ fs.rmSync(lockFilePath); } Logger.critical('There was an error during the theme creation process.', error); return res.redirect('/?error=installation-process-failed'); } } async executeForEveryRequest(next, req, res) { if(this.isInstalled()){ return next(); } if('' === req._parsedUrl.pathname || '/' === req._parsedUrl.pathname){ let installerIndexTemplate = fs.readFileSync( this.themeManager.installerPathIndex, {encoding: this.encoding()} ); let templateVariables = req?.session?.templateVariables || this.fetchDefaults(); return res.send(await TemplateEngine.render(installerIndexTemplate, templateVariables)); } if(!req.url.endsWith('.html')){ return express.static(this.themeManager.installerPath)(req, res, next); } next(); } fetchDefaults() { let host = (process.env.RELDENS_HOST || '').toString(); let port = (process.env.RELDENS_PORT || '').toString(); let publicUrl = (process.env.RELDENS_PUBLIC_URL || '').toString(); let trustedProxy = (process.env.RELDENS_EXPRESS_TRUSTED_PROXY || '').toString(); let adminPath = (process.env.RELDENS_ADMIN_ROUTE_PATH || '').toString(); let secureAdmin = Number(process.env.RELDENS_ADMIN_SECURE_LOGIN || 1); let hotPlug = Number(process.env.RELDENS_HOT_PLUG || 1); let dbClient = (process.env.RELDENS_DB_CLIENT || '').toString(); let dbHost = (process.env.RELDENS_DB_HOST || '').toString(); let dbPort = (process.env.RELDENS_DB_PORT || '').toString(); let dbName = (process.env.RELDENS_DB_NAME || '').toString(); return { 'app-host': '' !== host ? host : 'http://localhost', 'app-port': '' !== port ? port : '8080', 'app-public-url': '' !== publicUrl ? publicUrl : 'http://localhost:8080', 'app-trusted-proxy': trustedProxy, 'app-admin-path': '' !== adminPath ? adminPath : '/reldens-admin', 'app-secure-admin-checked': 1 === secureAdmin ? ' checked="checked"' : '', 'app-admin-hot-plug-checked': 1 === hotPlug ? ' checked="checked"' : '', 'db-client': '' !== dbClient ? dbClient : 'mysql2', 'db-host': '' !== dbHost ? dbHost : 'localhost', 'db-port': '' !== dbPort ? dbPort : '3306', 'db-name': '' !== dbName ? dbName : 'reldens', 'db-basic-config-checked': ' checked="checked"', 'db-sample-data-checked': ' checked="checked"' }; } async executeQueryFile(migrationsPath, dbDriver, fileName) { await dbDriver.rawQuery( fs.readFileSync(path.join(migrationsPath, fileName), {encoding: this.encoding()}).toString() ); } encoding() { return process.env.RELDENS_DEFAULT_ENCODING || 'utf8'; } setCheckboxesMissingValues(templateVariables) { this.setVariable(templateVariables, 'app-secure-admin'); this.setVariable(templateVariables, 'app-admin-hot-plug'); this.setVariable(templateVariables, 'app-use-https'); this.setVariable(templateVariables, 'app-use-monitor'); this.setVariable(templateVariables, 'app-secure-monitor'); this.setVariable(templateVariables, 'db-basic-config'); this.setVariable(templateVariables, 'db-sample-data'); this.setVariable(templateVariables, 'mailer-enable'); this.setVariable(templateVariables, 'mailer-secure'); this.setVariable(templateVariables, 'firebase-enable'); } setVariable(templateVariables, checkboxId) { if(!sc.hasOwn(templateVariables, checkboxId)){ templateVariables[checkboxId] = '0'; return; } templateVariables[checkboxId+'-checked'] = ' checked="checked"'; } setSelectedOptions(templateVariables) { let selectedDriver = templateVariables['db-storage-driver']; templateVariables['db-storage-driver-objection-js'] = 'objection-js' === selectedDriver ? ' selected="selected"' : ''; templateVariables['db-storage-driver-mikro-orm'] = 'mikro-orm' === selectedDriver ? ' selected="selected"' : ''; let selectedMailer = templateVariables['mailer-service']; templateVariables['mailer-service-sendgrid'] = 'sendgrid' === selectedMailer ? ' selected="selected"' : ''; templateVariables['mailer-service-nodemailer'] = 'nodemailer' === selectedMailer ? ' selected="selected"' : ''; } } module.exports.Installer = Installer;