configure.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  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 = 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 dataDir = path.resolve(ROOTPATH, req.body.pathData)
  147. const gitDir = path.resolve(ROOTPATH, req.body.pathRepo)
  148. let results = []
  149. fs.ensureDir(dataDir).then(() => {
  150. results.push('Data directory path is valid.')
  151. return fs.ensureDir(gitDir).then(() => {
  152. results.push('Git directory path is valid.')
  153. return true
  154. })
  155. }).then(() => {
  156. return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => {
  157. results.push('Git local repository initialized.')
  158. return true
  159. })
  160. }).then(() => {
  161. return res.json({ ok: true, results })
  162. }).catch(err => {
  163. res.json({ ok: false, error: err.message })
  164. })
  165. })
  166. /**
  167. * Check the DB connection
  168. */
  169. app.post('/finalize', (req, res) => {
  170. let mongo = require('mongodb').MongoClient
  171. mongo.connect(req.body.db, {
  172. autoReconnect: false,
  173. reconnectTries: 2,
  174. reconnectInterval: 1000,
  175. connectTimeoutMS: 5000,
  176. socketTimeoutMS: 5000
  177. }, (err, db) => {
  178. if (err === null) {
  179. // Try to create a test collection
  180. db.createCollection('test', (err, results) => {
  181. if (err === null) {
  182. // Try to drop test collection
  183. db.dropCollection('test', (err, results) => {
  184. if (err === null) {
  185. res.json({ ok: true })
  186. } else {
  187. res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message })
  188. }
  189. db.close()
  190. })
  191. } else {
  192. res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message })
  193. db.close()
  194. }
  195. })
  196. } else {
  197. res.json({ ok: false, error: err.message })
  198. }
  199. })
  200. })
  201. // ----------------------------------------
  202. // Error handling
  203. // ----------------------------------------
  204. app.use(function (req, res, next) {
  205. var err = new Error('Not Found')
  206. err.status = 404
  207. next(err)
  208. })
  209. app.use(function (err, req, res, next) {
  210. res.status(err.status || 500)
  211. res.send({
  212. message: err.message,
  213. error: IS_DEBUG ? err : {}
  214. })
  215. spinner.fail(err.message)
  216. process.exit(1)
  217. })
  218. // ----------------------------------------
  219. // Start HTTP server
  220. // ----------------------------------------
  221. spinner.text = 'Starting HTTP server...'
  222. app.set('port', port)
  223. var server = http.createServer(app)
  224. server.listen(port)
  225. server.on('error', (error) => {
  226. if (error.syscall !== 'listen') {
  227. throw error
  228. }
  229. switch (error.code) {
  230. case 'EACCES':
  231. spinner.fail('Listening on port ' + port + ' requires elevated privileges!')
  232. process.exit(1)
  233. break
  234. case 'EADDRINUSE':
  235. spinner.fail('Port ' + port + ' is already in use!')
  236. process.exit(1)
  237. break
  238. default:
  239. throw error
  240. }
  241. })
  242. server.on('listening', () => {
  243. spinner.text = 'Browse to http://localhost:' + port + ' to configure Wiki.js!'
  244. })
  245. }