configure.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. 'use strict'
  2. module.exports = (port, spinner) => {
  3. const ROOTPATH = __dirname
  4. const IS_DEBUG = process.env.NODE_ENV === 'development'
  5. // ----------------------------------------
  6. // Load modules
  7. // ----------------------------------------
  8. const bodyParser = require('body-parser')
  9. const compression = require('compression')
  10. const express = require('express')
  11. const favicon = require('serve-favicon')
  12. const http = require('http')
  13. const path = require('path')
  14. const Promise = require('bluebird')
  15. const fs = Promise.promisifyAll(require('fs-extra'))
  16. const yaml = require('js-yaml')
  17. const _ = require('lodash')
  18. // ----------------------------------------
  19. // Define Express App
  20. // ----------------------------------------
  21. var app = express()
  22. app.use(compression())
  23. // ----------------------------------------
  24. // Public Assets
  25. // ----------------------------------------
  26. app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
  27. app.use(express.static(path.join(ROOTPATH, 'assets')))
  28. // ----------------------------------------
  29. // View Engine Setup
  30. // ----------------------------------------
  31. app.set('views', path.join(ROOTPATH, 'views'))
  32. app.set('view engine', 'pug')
  33. app.use(bodyParser.json())
  34. app.use(bodyParser.urlencoded({ extended: false }))
  35. app.locals._ = require('lodash')
  36. // ----------------------------------------
  37. // Controllers
  38. // ----------------------------------------
  39. app.get('*', (req, res) => {
  40. let langs = []
  41. try {
  42. langs = yaml.safeLoad(fs.readFileSync('./app/data.yml', 'utf8')).langs
  43. } catch (err) {
  44. console.error(err)
  45. }
  46. res.render('configure/index', {
  47. langs
  48. })
  49. })
  50. /**
  51. * Perform basic system checks
  52. */
  53. app.post('/syscheck', (req, res) => {
  54. Promise.mapSeries([
  55. () => {
  56. const semver = require('semver')
  57. if (!semver.satisfies(semver.clean(process.version), '>=4.6.0')) {
  58. throw new Error('Node.js version is too old. Minimum is v4.6.0.')
  59. }
  60. return 'Node.js ' + process.version + ' detected. Minimum is v4.6.0.'
  61. },
  62. () => {
  63. return Promise.try(() => {
  64. require('crypto')
  65. }).catch(err => { // eslint-disable-line handle-callback-err
  66. throw new Error('Crypto Node.js module is not available.')
  67. }).return('Node.js Crypto module is available.')
  68. },
  69. () => {
  70. const exec = require('child_process').exec
  71. const semver = require('semver')
  72. return new Promise((resolve, reject) => {
  73. exec('git --version', (err, stdout, stderr) => {
  74. if (err || stdout.length < 3) {
  75. reject(new Error('Git is not installed or not reachable from PATH.'))
  76. }
  77. let gitver = _.chain(stdout.replace(/[^\d.]/g, '')).split('.').take(3).join('.').value()
  78. if (!semver.satisfies(semver.clean(gitver), '>=2.11.0')) {
  79. reject(new Error('Git version is too old. Minimum is v2.11.0.'))
  80. }
  81. resolve('Git v' + gitver + ' detected. Minimum is v2.11.0.')
  82. })
  83. })
  84. },
  85. () => {
  86. const os = require('os')
  87. if (os.totalmem() < 1024 * 1024 * 768) {
  88. throw new Error('Not enough memory. Minimum is 768 MB.')
  89. }
  90. return _.round(os.totalmem() / (1024 * 1024)) + ' MB of system memory available. Minimum is 768 MB.'
  91. },
  92. () => {
  93. let fs = require('fs')
  94. return Promise.try(() => {
  95. fs.accessSync(path.join(ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)
  96. }).catch(err => { // eslint-disable-line handle-callback-err
  97. throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
  98. }).return('config.yml is writable by the setup process.')
  99. }
  100. ], test => { return test() }).then(results => {
  101. res.json({ ok: true, results })
  102. }).catch(err => {
  103. res.json({ ok: false, error: err.message })
  104. })
  105. })
  106. /**
  107. * Check the DB connection
  108. */
  109. app.post('/dbcheck', (req, res) => {
  110. let mongo = require('mongodb').MongoClient
  111. mongo.connect(req.body.db, {
  112. autoReconnect: false,
  113. reconnectTries: 2,
  114. reconnectInterval: 1000,
  115. connectTimeoutMS: 5000,
  116. socketTimeoutMS: 5000
  117. }, (err, db) => {
  118. if (err === null) {
  119. // Try to create a test collection
  120. db.createCollection('test', (err, results) => {
  121. if (err === null) {
  122. // Try to drop test collection
  123. db.dropCollection('test', (err, results) => {
  124. if (err === null) {
  125. res.json({ ok: true })
  126. } else {
  127. res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
  128. }
  129. db.close()
  130. })
  131. } else {
  132. res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
  133. db.close()
  134. }
  135. })
  136. } else {
  137. res.json({ ok: false, error: err.message })
  138. }
  139. })
  140. })
  141. /**
  142. * Check the Git connection
  143. */
  144. app.post('/gitcheck', (req, res) => {
  145. const exec = require('execa')
  146. const url = require('url')
  147. const dataDir = path.resolve(ROOTPATH, req.body.pathData)
  148. const gitDir = path.resolve(ROOTPATH, req.body.pathRepo)
  149. let urlObj = url.parse(req.body.gitUrl)
  150. if (req.body.gitAuthType === 'basic') {
  151. urlObj.auth = req.body.gitAuthUser + ':' + req.body.gitAuthPass
  152. }
  153. const gitRemoteUrl = url.format(urlObj)
  154. Promise.mapSeries([
  155. () => {
  156. return fs.ensureDirAsync(dataDir).return('Data directory path is valid.')
  157. },
  158. () => {
  159. return fs.ensureDirAsync(gitDir).return('Git directory path is valid.')
  160. },
  161. () => {
  162. return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => {
  163. return 'Local git repository has been initialized.'
  164. })
  165. },
  166. () => {
  167. return exec.stdout('git', ['config', '--local', 'user.name', req.body.gitSignatureName], { cwd: gitDir }).then(result => {
  168. return 'Git Signature Name has been set successfully.'
  169. })
  170. },
  171. () => {
  172. return exec.stdout('git', ['config', '--local', 'user.email', req.body.gitSignatureEmail], { cwd: gitDir }).then(result => {
  173. return 'Git Signature Name has been set successfully.'
  174. })
  175. },
  176. () => {
  177. return exec.stdout('git', ['config', '--local', '--bool', 'http.sslVerify', req.body.gitAuthSSL], { cwd: gitDir }).then(result => {
  178. return 'Git SSL Verify flag has been set successfully.'
  179. })
  180. },
  181. () => {
  182. if (req.body.gitAuthType === 'ssh') {
  183. return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => {
  184. return 'Git SSH Private Key path has been set successfully.'
  185. })
  186. } else {
  187. return false
  188. }
  189. },
  190. () => {
  191. return exec.stdout('git', ['remote', 'remove', 'origin'], { cwd: gitDir }).catch(err => {
  192. if (_.includes(err.message, 'No such remote')) {
  193. return true
  194. } else {
  195. throw err
  196. }
  197. }).then(() => {
  198. return exec.stdout('git', ['remote', 'add', 'origin', gitRemoteUrl], { cwd: gitDir }).then(result => {
  199. return 'Git Remote was added successfully.'
  200. })
  201. })
  202. },
  203. () => {
  204. return exec.stdout('git', ['pull', 'origin', req.body.gitBranch], { cwd: gitDir }).then(result => {
  205. return 'Git Pull operation successful.'
  206. })
  207. }
  208. ], step => { return step() }).then(results => {
  209. return res.json({ ok: true, results: _.without(results, false) })
  210. }).catch(err => {
  211. let errMsg = (err.stderr) ? err.stderr.replace(/(error:|warning:|fatal:)/gi, '').replace(/ \s+/g, ' ') : err.message
  212. res.json({ ok: false, error: errMsg })
  213. })
  214. })
  215. /**
  216. * Check the DB connection
  217. */
  218. app.post('/finalize', (req, res) => {
  219. let mongo = require('mongodb').MongoClient
  220. mongo.connect(req.body.db, {
  221. autoReconnect: false,
  222. reconnectTries: 2,
  223. reconnectInterval: 1000,
  224. connectTimeoutMS: 5000,
  225. socketTimeoutMS: 5000
  226. }, (err, db) => {
  227. if (err === null) {
  228. // Try to create a test collection
  229. db.createCollection('test', (err, results) => {
  230. if (err === null) {
  231. // Try to drop test collection
  232. db.dropCollection('test', (err, results) => {
  233. if (err === null) {
  234. res.json({ ok: true })
  235. } else {
  236. res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
  237. }
  238. db.close()
  239. })
  240. } else {
  241. res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
  242. db.close()
  243. }
  244. })
  245. } else {
  246. res.json({ ok: false, error: err.message })
  247. }
  248. })
  249. })
  250. // ----------------------------------------
  251. // Error handling
  252. // ----------------------------------------
  253. app.use(function (req, res, next) {
  254. var err = new Error('Not Found')
  255. err.status = 404
  256. next(err)
  257. })
  258. app.use(function (err, req, res, next) {
  259. res.status(err.status || 500)
  260. res.send({
  261. message: err.message,
  262. error: IS_DEBUG ? err : {}
  263. })
  264. spinner.fail(err.message)
  265. process.exit(1)
  266. })
  267. // ----------------------------------------
  268. // Start HTTP server
  269. // ----------------------------------------
  270. spinner.text = 'Starting HTTP server...'
  271. app.set('port', port)
  272. var server = http.createServer(app)
  273. server.listen(port)
  274. server.on('error', (error) => {
  275. if (error.syscall !== 'listen') {
  276. throw error
  277. }
  278. switch (error.code) {
  279. case 'EACCES':
  280. spinner.fail('Listening on port ' + port + ' requires elevated privileges!')
  281. process.exit(1)
  282. break
  283. case 'EADDRINUSE':
  284. spinner.fail('Port ' + port + ' is already in use!')
  285. process.exit(1)
  286. break
  287. default:
  288. throw error
  289. }
  290. })
  291. server.on('listening', () => {
  292. spinner.text = 'Browse to http://localhost:' + port + ' to configure Wiki.js!'
  293. })
  294. }