| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 | 'use strict'module.exports = (port, spinner) => {  const path = require('path')  const ROOTPATH = process.cwd()  const SERVERPATH = path.join(ROOTPATH, 'server')  const IS_DEBUG = process.env.NODE_ENV === 'development'  // ----------------------------------------  // Load modules  // ----------------------------------------  const bodyParser = require('body-parser')  const compression = require('compression')  const express = require('express')  const favicon = require('serve-favicon')  const http = require('http')  const Promise = require('bluebird')  const fs = Promise.promisifyAll(require('fs-extra'))  const yaml = require('js-yaml')  const _ = require('lodash')  const cfgHelper = require('./helpers/config')  // ----------------------------------------  // Define Express App  // ----------------------------------------  var app = express()  app.use(compression())  var server  // ----------------------------------------  // Public Assets  // ----------------------------------------  app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))  app.use(express.static(path.join(ROOTPATH, 'assets')))  // ----------------------------------------  // View Engine Setup  // ----------------------------------------  app.set('views', path.join(SERVERPATH, 'views'))  app.set('view engine', 'pug')  app.use(bodyParser.json())  app.use(bodyParser.urlencoded({ extended: false }))  app.locals._ = require('lodash')  // ----------------------------------------  // Controllers  // ----------------------------------------  app.get('*', (req, res) => {    let langs = []    let conf = {}    try {      langs = yaml.safeLoad(fs.readFileSync(path.join(SERVERPATH, 'app/data.yml'), 'utf8')).langs      conf = yaml.safeLoad(fs.readFileSync(path.join(ROOTPATH, 'config.yml'), 'utf8'))    } catch (err) {      console.error(err)    }    res.render('configure/index', {      langs,      conf,      runmode: {        staticPort: (process.env.WIKI_JS_HEROKU || process.env.WIKI_JS_DOCKER),        staticMongo: (!_.isNil(process.env.WIKI_JS_HEROKU))      }    })  })  /**   * Perform basic system checks   */  app.post('/syscheck', (req, res) => {    Promise.mapSeries([      () => {        const semver = require('semver')        if (!semver.satisfies(semver.clean(process.version), '>=6.9.0')) {          throw new Error('Node.js version is too old. Minimum is v6.6.0.')        }        return 'Node.js ' + process.version + ' detected. Minimum is v6.9.0.'      },      () => {        return Promise.try(() => {          require('crypto')        }).catch(err => { // eslint-disable-line handle-callback-err          throw new Error('Crypto Node.js module is not available.')        }).return('Node.js Crypto module is available.')      },      () => {        const exec = require('child_process').exec        const semver = require('semver')        return new Promise((resolve, reject) => {          exec('git --version', (err, stdout, stderr) => {            if (err || stdout.length < 3) {              reject(new Error('Git is not installed or not reachable from PATH.'))            }            let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi))            if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) {              reject(new Error('Git version is too old. Minimum is v2.7.4.'))            }            resolve('Git v' + gitver + ' detected. Minimum is v2.7.4.')          })        })      },      () => {        const os = require('os')        if (os.totalmem() < 1000 * 1000 * 768) {          throw new Error('Not enough memory. Minimum is 768 MB.')        }        return _.round(os.totalmem() / (1024 * 1024)) + ' MB of system memory available. Minimum is 768 MB.'      },      () => {        let fs = require('fs')        return Promise.try(() => {          fs.accessSync(path.join(ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)        }).catch(err => { // eslint-disable-line handle-callback-err          throw new Error('config.yml file is not writable by Node.js process or was not created properly.')        }).return('config.yml is writable by the setup process.')      }    ], test => { return test() }).then(results => {      res.json({ ok: true, results })    }).catch(err => {      res.json({ ok: false, error: err.message })    })  })  /**   * Check the DB connection   */  app.post('/dbcheck', (req, res) => {    let mongo = require('mongodb').MongoClient    let mongoURI = cfgHelper.parseConfigValue(req.body.db)    mongo.connect(mongoURI, {      autoReconnect: false,      reconnectTries: 2,      reconnectInterval: 1000,      connectTimeoutMS: 5000,      socketTimeoutMS: 5000    }, (err, db) => {      if (err === null) {        // Try to create a test collection        db.createCollection('test', (err, results) => {          if (err === null) {            // Try to drop test collection            db.dropCollection('test', (err, results) => {              if (err === null) {                res.json({ ok: true })              } else {                res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })              }              db.close()            })          } else {            res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })            db.close()          }        })      } else {        res.json({ ok: false, error: err.message })      }    })  })  /**   * Check the Git connection   */  app.post('/gitcheck', (req, res) => {    const exec = require('execa')    const url = require('url')    const dataDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData))    const gitDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo))    let gitRemoteUrl = ''    if (req.body.gitUseRemote === true) {      let urlObj = url.parse(cfgHelper.parseConfigValue(req.body.gitUrl))      if (req.body.gitAuthType === 'basic') {        urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass      }      gitRemoteUrl = url.format(urlObj)    }    Promise.mapSeries([      () => {        return fs.ensureDirAsync(dataDir).return('Data directory path is valid.')      },      () => {        return fs.ensureDirAsync(gitDir).return('Git directory path is valid.')      },      () => {        return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => {          return 'Local git repository has been initialized.'        })      },      () => {        if (req.body.gitUseRemote === false) { return false }        return exec.stdout('git', ['config', '--local', 'user.name', 'Wiki'], { cwd: gitDir }).then(result => {          return 'Git Signature Name has been set successfully.'        })      },      () => {        if (req.body.gitUseRemote === false) { return false }        return exec.stdout('git', ['config', '--local', 'user.email', req.body.gitServerEmail], { cwd: gitDir }).then(result => {          return 'Git Signature Name has been set successfully.'        })      },      () => {        if (req.body.gitUseRemote === false) { return false }        return exec.stdout('git', ['config', '--local', '--bool', 'http.sslVerify', req.body.gitAuthSSL], { cwd: gitDir }).then(result => {          return 'Git SSL Verify flag has been set successfully.'        })      },      () => {        if (req.body.gitUseRemote === false) { return false }        if (req.body.gitAuthType === 'ssh') {          return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => {            return 'Git SSH Private Key path has been set successfully.'          })        } else {          return false        }      },      () => {        if (req.body.gitUseRemote === false) { return false }        return exec.stdout('git', ['remote', 'rm', 'origin'], { cwd: gitDir }).catch(err => {          if (_.includes(err.message, 'No such remote') || _.includes(err.message, 'Could not remove')) {            return true          } else {            throw err          }        }).then(() => {          return exec.stdout('git', ['remote', 'add', 'origin', gitRemoteUrl], { cwd: gitDir }).then(result => {            return 'Git Remote was added successfully.'          })        })      },      () => {        if (req.body.gitUseRemote === false) { return false }        return exec.stdout('git', ['pull', 'origin', req.body.gitBranch], { cwd: gitDir }).then(result => {          return 'Git Pull operation successful.'        })      }    ], step => { return step() }).then(results => {      return res.json({ ok: true, results: _.without(results, false) })    }).catch(err => {      let errMsg = (err.stderr) ? err.stderr.replace(/(error:|warning:|fatal:)/gi, '').replace(/ \s+/g, ' ') : err.message      res.json({ ok: false, error: errMsg })    })  })  /**   * Finalize   */  app.post('/finalize', (req, res) => {    const bcrypt = require('bcryptjs-then')    const crypto = Promise.promisifyAll(require('crypto'))    let mongo = require('mongodb').MongoClient    let parsedMongoConStr = cfgHelper.parseConfigValue(req.body.db)    Promise.join(      new Promise((resolve, reject) => {        mongo.connect(parsedMongoConStr, {          autoReconnect: false,          reconnectTries: 2,          reconnectInterval: 1000,          connectTimeoutMS: 5000,          socketTimeoutMS: 5000        }, (err, db) => {          if (err === null) {            db.createCollection('users', { strict: false }, (err, results) => {              if (err === null) {                bcrypt.hash(req.body.adminPassword).then(adminPwdHash => {                  db.collection('users').findOneAndUpdate({                    provider: 'local',                    email: req.body.adminEmail                  }, {                    provider: 'local',                    email: req.body.adminEmail,                    name: 'Administrator',                    password: adminPwdHash,                    rights: [{                      role: 'admin',                      path: '/',                      exact: false,                      deny: false                    }],                    updatedAt: new Date(),                    createdAt: new Date()                  }, {                    upsert: true,                    returnOriginal: false                  }, (err, results) => {                    if (err === null) {                      resolve(true)                    } else {                      reject(err)                    }                    db.close()                  })                })              } else {                reject(err)                db.close()              }            })          } else {            reject(err)          }        })      }),      fs.readFileAsync(path.join(ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {        let conf = yaml.safeLoad(confRaw)        conf.title = req.body.title        conf.host = req.body.host        conf.port = req.body.port        conf.paths = {          repo: req.body.pathRepo,          data: req.body.pathData        }        conf.uploads = {          maxImageFileSize: (conf.uploads && _.isNumber(conf.uploads.maxImageFileSize)) ? conf.uploads.maxImageFileSize : 3,          maxOtherFileSize: (conf.uploads && _.isNumber(conf.uploads.maxOtherFileSize)) ? conf.uploads.maxOtherFileSize : 100        }        conf.lang = req.body.lang        conf.public = (req.body.public === true)        if (conf.auth && conf.auth.local) {          conf.auth.local = { enabled: true }        } else {          conf.auth = { local: { enabled: true } }        }        conf.db = req.body.db        if (req.body.gitUseRemote === false) {          conf.git = false        } else {          conf.git = {            url: req.body.gitUrl,            branch: req.body.gitBranch,            auth: {              type: req.body.gitAuthType,              username: req.body.gitAuthUser,              password: req.body.gitAuthPass,              privateKey: req.body.gitAuthSSHKey,              sslVerify: (req.body.gitAuthSSL === true)            },            showUserEmail: (req.body.gitShowUserEmail === true),            serverEmail: req.body.gitServerEmail          }        }        return crypto.randomBytesAsync(32).then(buf => {          conf.sessionSecret = buf.toString('hex')          confRaw = yaml.safeDump(conf)          return fs.writeFileAsync(path.join(ROOTPATH, 'config.yml'), confRaw)        })      })    ).then(() => {      if (process.env.IS_HEROKU) {        return fs.outputJsonAsync(path.join(SERVERPATH, 'app/heroku.json'), { configured: true })      } else {        return true      }    }).then(() => {      res.json({ ok: true })    }).catch(err => {      res.json({ ok: false, error: err.message })    })  })  /**   * Restart in normal mode   */  app.post('/restart', (req, res) => {    res.status(204).end()    server.destroy(() => {      spinner.text = 'Setup wizard terminated. Restarting in normal mode...'      _.delay(() => {        const exec = require('execa')        exec.stdout('node', ['wiki', 'start']).then(result => {          spinner.succeed('Wiki.js is now running in normal mode!')          process.exit(0)        })      }, 1000)    })  })  // ----------------------------------------  // Error handling  // ----------------------------------------  app.use(function (req, res, next) {    var err = new Error('Not Found')    err.status = 404    next(err)  })  app.use(function (err, req, res, next) {    res.status(err.status || 500)    res.send({      message: err.message,      error: IS_DEBUG ? err : {}    })    spinner.fail(err.message)    process.exit(1)  })  // ----------------------------------------  // Start HTTP server  // ----------------------------------------  spinner.text = 'Starting HTTP server...'  app.set('port', port)  server = http.createServer(app)  server.listen(port)  var openConnections = []  server.on('connection', (conn) => {    let key = conn.remoteAddress + ':' + conn.remotePort    openConnections[key] = conn    conn.on('close', () => {      delete openConnections[key]    })  })  server.destroy = (cb) => {    server.close(cb)    for (let key in openConnections) {      openConnections[key].destroy()    }  }  server.on('error', (error) => {    if (error.syscall !== 'listen') {      throw error    }    switch (error.code) {      case 'EACCES':        spinner.fail('Listening on port ' + port + ' requires elevated privileges!')        return process.exit(1)      case 'EADDRINUSE':        spinner.fail('Port ' + port + ' is already in use!')        return process.exit(1)      default:        throw error    }  })  server.on('listening', () => {    spinner.text = 'Browse to http://localhost:' + port + ' to configure Wiki.js!'  })}
 |