setup.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. const path = require('path')
  2. const uuid = require('uuid/v4')
  3. const bodyParser = require('body-parser')
  4. const compression = require('compression')
  5. const express = require('express')
  6. const favicon = require('serve-favicon')
  7. const http = require('http')
  8. const https = require('https')
  9. const Promise = require('bluebird')
  10. const fs = require('fs-extra')
  11. const _ = require('lodash')
  12. const crypto = Promise.promisifyAll(require('crypto'))
  13. const pem2jwk = require('pem-jwk').pem2jwk
  14. const semver = require('semver')
  15. /* global WIKI */
  16. module.exports = () => {
  17. WIKI.config.site = {
  18. path: '',
  19. title: 'Wiki.js'
  20. }
  21. WIKI.system = require('./core/system')
  22. // ----------------------------------------
  23. // Define Express App
  24. // ----------------------------------------
  25. let app = express()
  26. app.use(compression())
  27. // ----------------------------------------
  28. // Public Assets
  29. // ----------------------------------------
  30. app.use(favicon(path.join(WIKI.ROOTPATH, 'assets', 'favicon.ico')))
  31. app.use(express.static(path.join(WIKI.ROOTPATH, 'assets')))
  32. // ----------------------------------------
  33. // View Engine Setup
  34. // ----------------------------------------
  35. app.set('views', path.join(WIKI.SERVERPATH, 'views'))
  36. app.set('view engine', 'pug')
  37. app.use(bodyParser.json())
  38. app.use(bodyParser.urlencoded({ extended: false }))
  39. app.locals.config = WIKI.config
  40. app.locals.data = WIKI.data
  41. app.locals._ = require('lodash')
  42. // ----------------------------------------
  43. // HMR (Dev Mode Only)
  44. // ----------------------------------------
  45. if (global.DEV) {
  46. app.use(global.WP_DEV.devMiddleware)
  47. app.use(global.WP_DEV.hotMiddleware)
  48. }
  49. // ----------------------------------------
  50. // Controllers
  51. // ----------------------------------------
  52. app.get('*', async (req, res) => {
  53. let packageObj = await fs.readJson(path.join(WIKI.ROOTPATH, 'package.json'))
  54. res.render('setup', { packageObj })
  55. })
  56. /**
  57. * Finalize
  58. */
  59. app.post('/finalize', async (req, res) => {
  60. try {
  61. // Set config
  62. _.set(WIKI.config, 'auth', {
  63. audience: 'urn:wiki.js',
  64. tokenExpiration: '30m',
  65. tokenRenewal: '14d'
  66. })
  67. _.set(WIKI.config, 'company', '')
  68. _.set(WIKI.config, 'features', {
  69. featurePageRatings: true,
  70. featurePageComments: true,
  71. featurePersonalWikis: true
  72. })
  73. _.set(WIKI.config, 'graphEndpoint', 'https://graph.requarks.io')
  74. _.set(WIKI.config, 'host', req.body.siteUrl)
  75. _.set(WIKI.config, 'lang', {
  76. code: 'en',
  77. autoUpdate: true,
  78. namespacing: false,
  79. namespaces: []
  80. })
  81. _.set(WIKI.config, 'logo', {
  82. hasLogo: false,
  83. logoIsSquare: false
  84. })
  85. _.set(WIKI.config, 'mail', {
  86. senderName: '',
  87. senderEmail: '',
  88. host: '',
  89. port: 465,
  90. secure: true,
  91. user: '',
  92. pass: '',
  93. useDKIM: false,
  94. dkimDomainName: '',
  95. dkimKeySelector: '',
  96. dkimPrivateKey: ''
  97. })
  98. _.set(WIKI.config, 'seo', {
  99. description: '',
  100. robots: ['index', 'follow'],
  101. analyticsService: '',
  102. analyticsId: ''
  103. })
  104. _.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
  105. _.set(WIKI.config, 'telemetry', {
  106. isEnabled: req.body.telemetry === true,
  107. clientId: uuid()
  108. })
  109. _.set(WIKI.config, 'theming', {
  110. theme: 'default',
  111. darkMode: false,
  112. iconset: 'mdi',
  113. injectCSS: '',
  114. injectHead: '',
  115. injectBody: ''
  116. })
  117. _.set(WIKI.config, 'title', 'Wiki.js')
  118. // Init Telemetry
  119. WIKI.kernel.initTelemetry()
  120. WIKI.telemetry.sendEvent('setup', 'install-start')
  121. // Basic checks
  122. if (!semver.satisfies(process.version, '>=10.12')) {
  123. throw new Error('Node.js 10.12.x or later required!')
  124. }
  125. // Create directory structure
  126. WIKI.logger.info('Creating data directories...')
  127. await fs.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath))
  128. await fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
  129. await fs.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'))
  130. // Generate certificates
  131. WIKI.logger.info('Generating certificates...')
  132. const certs = crypto.generateKeyPairSync('rsa', {
  133. modulusLength: 2048,
  134. publicKeyEncoding: {
  135. type: 'pkcs1',
  136. format: 'pem'
  137. },
  138. privateKeyEncoding: {
  139. type: 'pkcs1',
  140. format: 'pem',
  141. cipher: 'aes-256-cbc',
  142. passphrase: WIKI.config.sessionSecret
  143. }
  144. })
  145. _.set(WIKI.config, 'certs', {
  146. jwk: pem2jwk(certs.publicKey),
  147. public: certs.publicKey,
  148. private: certs.privateKey
  149. })
  150. // Save config to DB
  151. WIKI.logger.info('Persisting config to DB...')
  152. await WIKI.configSvc.saveToDb([
  153. 'auth',
  154. 'certs',
  155. 'company',
  156. 'features',
  157. 'graphEndpoint',
  158. 'host',
  159. 'lang',
  160. 'logo',
  161. 'mail',
  162. 'seo',
  163. 'sessionSecret',
  164. 'telemetry',
  165. 'theming',
  166. 'title'
  167. ])
  168. // Truncate tables (reset from previous failed install)
  169. if (WIKI.config.db.type !== 'mssql') {
  170. await WIKI.models.locales.query().truncate()
  171. await WIKI.models.groups.query().truncate()
  172. await WIKI.models.users.query().truncate()
  173. await WIKI.models.navigation.query().truncate()
  174. } else {
  175. await WIKI.models.locales.query().del()
  176. await WIKI.models.groups.query().del()
  177. await WIKI.models.users.query().del()
  178. await WIKI.models.navigation.query().truncate()
  179. await WIKI.models.knex.raw(`
  180. IF EXISTS (SELECT * FROM sys.identity_columns WHERE OBJECT_NAME(OBJECT_ID) = 'groups' AND last_value IS NOT NULL)
  181. DBCC CHECKIDENT ([groups], RESEED, 0)
  182. `)
  183. await WIKI.models.knex.raw(`
  184. IF EXISTS (SELECT * FROM sys.identity_columns WHERE OBJECT_NAME(OBJECT_ID) = 'users' AND last_value IS NOT NULL)
  185. DBCC CHECKIDENT ([users], RESEED, 0)
  186. `)
  187. }
  188. // Create default locale
  189. WIKI.logger.info('Installing default locale...')
  190. await WIKI.models.locales.query().insert({
  191. code: 'en',
  192. strings: {},
  193. isRTL: false,
  194. name: 'English',
  195. nativeName: 'English'
  196. })
  197. // Create default groups
  198. WIKI.logger.info('Creating default groups...')
  199. const adminGroup = await WIKI.models.groups.query().insert({
  200. ...(WIKI.config.db.type !== `mssql` && { id: 1 }),
  201. name: 'Administrators',
  202. permissions: JSON.stringify(['manage:system']),
  203. pageRules: JSON.stringify([]),
  204. isSystem: true
  205. })
  206. const guestGroup = await WIKI.models.groups.query().insert({
  207. ...(WIKI.config.db.type !== `mssql` && { id: 2 }),
  208. name: 'Guests',
  209. permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
  210. pageRules: JSON.stringify([
  211. { id: 'guest', roles: ['read:pages', 'read:assets', 'read:comments'], match: 'START', deny: false, path: '', locales: [] }
  212. ]),
  213. isSystem: true
  214. })
  215. if (adminGroup.id !== 1 || guestGroup.id !== 2) {
  216. throw new Error('Incorrect groups auto-increment configuration! Should start at 0 and increment by 1. Contact your database administrator.')
  217. }
  218. // Load authentication strategies + enable local
  219. await WIKI.models.authentication.refreshStrategiesFromDisk()
  220. await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local')
  221. // Load editors + enable default
  222. await WIKI.models.editors.refreshEditorsFromDisk()
  223. await WIKI.models.editors.query().patch({ isEnabled: true }).where('key', 'markdown')
  224. // Load loggers
  225. await WIKI.models.loggers.refreshLoggersFromDisk()
  226. // Load renderers
  227. await WIKI.models.renderers.refreshRenderersFromDisk()
  228. // Load search engines + enable default
  229. await WIKI.models.searchEngines.refreshSearchEnginesFromDisk()
  230. await WIKI.models.searchEngines.query().patch({ isEnabled: true }).where('key', 'db')
  231. WIKI.telemetry.sendEvent('setup', 'install-loadedmodules')
  232. // Load storage targets
  233. await WIKI.models.storage.refreshTargetsFromDisk()
  234. // Create root administrator
  235. WIKI.logger.info('Creating root administrator...')
  236. const adminUser = await WIKI.models.users.query().insert({
  237. ...(WIKI.config.db.type !== `mssql` && { id: 1 }),
  238. email: req.body.adminEmail,
  239. provider: 'local',
  240. password: req.body.adminPassword,
  241. name: 'Administrator',
  242. locale: 'en',
  243. defaultEditor: 'markdown',
  244. tfaIsActive: false,
  245. isActive: true,
  246. isVerified: true
  247. })
  248. await adminUser.$relatedQuery('groups').relate(adminGroup.id)
  249. // Create Guest account
  250. WIKI.logger.info('Creating guest account...')
  251. const guestUser = await WIKI.models.users.query().insert({
  252. ...(WIKI.config.db.type !== `mssql` && { id: 2 }),
  253. provider: 'local',
  254. email: 'guest@example.com',
  255. name: 'Guest',
  256. password: '',
  257. locale: 'en',
  258. defaultEditor: 'markdown',
  259. tfaIsActive: false,
  260. isSystem: true,
  261. isActive: true,
  262. isVerified: true
  263. })
  264. await guestUser.$relatedQuery('groups').relate(guestGroup.id)
  265. if (adminUser.id !== 1 || guestUser.id !== 2) {
  266. throw new Error('Incorrect users auto-increment configuration! Should start at 0 and increment by 1. Contact your database administrator.')
  267. }
  268. // Create site nav
  269. WIKI.logger.info('Creating default site navigation')
  270. await WIKI.models.navigation.query().insert({
  271. key: 'site',
  272. config: [
  273. {
  274. id: uuid(),
  275. icon: 'mdi-home',
  276. kind: 'link',
  277. label: 'Home',
  278. target: '/',
  279. targetType: 'home'
  280. }
  281. ]
  282. })
  283. WIKI.logger.info('Setup is complete!')
  284. WIKI.telemetry.sendEvent('setup', 'install-completed')
  285. res.json({
  286. ok: true,
  287. redirectPath: '/',
  288. redirectPort: WIKI.config.port
  289. }).end()
  290. WIKI.config.setup = false
  291. WIKI.logger.info('Stopping Setup...')
  292. WIKI.server.destroy(() => {
  293. WIKI.logger.info('Setup stopped. Starting Wiki.js...')
  294. _.delay(() => {
  295. WIKI.kernel.bootMaster()
  296. }, 1000)
  297. })
  298. } catch (err) {
  299. try {
  300. await WIKI.models.knex('settings').truncate()
  301. } catch (err) {}
  302. WIKI.telemetry.sendError(err)
  303. res.json({ ok: false, error: err.message })
  304. }
  305. })
  306. // ----------------------------------------
  307. // Error handling
  308. // ----------------------------------------
  309. app.use(function (req, res, next) {
  310. var err = new Error('Not Found')
  311. err.status = 404
  312. next(err)
  313. })
  314. app.use(function (err, req, res, next) {
  315. res.status(err.status || 500)
  316. res.send({
  317. message: err.message,
  318. error: WIKI.IS_DEBUG ? err : {}
  319. })
  320. WIKI.logger.error(err.message)
  321. WIKI.telemetry.sendError(err)
  322. })
  323. // ----------------------------------------
  324. // Start HTTP server
  325. // ----------------------------------------
  326. WIKI.logger.info(`Starting HTTP server on port ${WIKI.config.port}...`)
  327. app.set('port', WIKI.config.port)
  328. if (WIKI.config.ssl.enabled) {
  329. WIKI.logger.info(`HTTPS Server on port: [ ${WIKI.config.port} ]`)
  330. const tlsOpts = {}
  331. try {
  332. if (WIKI.config.ssl.format === 'pem') {
  333. tlsOpts.key = fs.readFileSync(WIKI.config.ssl.key)
  334. tlsOpts.cert = fs.readFileSync(WIKI.config.ssl.cert)
  335. } else {
  336. tlsOpts.pfx = fs.readFileSync(WIKI.config.ssl.pfx)
  337. }
  338. if (!_.isEmpty(WIKI.config.ssl.passphrase)) {
  339. tlsOpts.passphrase = WIKI.config.ssl.passphrase
  340. }
  341. if (!_.isEmpty(WIKI.config.ssl.dhparam)) {
  342. tlsOpts.dhparam = WIKI.config.ssl.dhparam
  343. }
  344. } catch (err) {
  345. WIKI.logger.error('Failed to setup HTTPS server parameters:')
  346. WIKI.logger.error(err)
  347. return process.exit(1)
  348. }
  349. WIKI.server = https.createServer(tlsOpts, app)
  350. } else {
  351. WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`)
  352. WIKI.server = http.createServer(app)
  353. }
  354. WIKI.server.listen(WIKI.config.port, WIKI.config.bindIP)
  355. var openConnections = []
  356. WIKI.server.on('connection', (conn) => {
  357. let key = conn.remoteAddress + ':' + conn.remotePort
  358. openConnections[key] = conn
  359. conn.on('close', () => {
  360. openConnections.splice(key, 1)
  361. })
  362. })
  363. WIKI.server.destroy = (cb) => {
  364. WIKI.server.close(cb)
  365. for (let key in openConnections) {
  366. openConnections[key].destroy()
  367. }
  368. }
  369. WIKI.server.on('error', (error) => {
  370. if (error.syscall !== 'listen') {
  371. throw error
  372. }
  373. switch (error.code) {
  374. case 'EACCES':
  375. WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!')
  376. return process.exit(1)
  377. case 'EADDRINUSE':
  378. WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!')
  379. return process.exit(1)
  380. default:
  381. throw error
  382. }
  383. })
  384. WIKI.server.on('listening', () => {
  385. WIKI.logger.info('HTTP Server: [ RUNNING ]')
  386. WIKI.logger.info('🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻')
  387. WIKI.logger.info('')
  388. WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/ to complete setup!`)
  389. WIKI.logger.info('')
  390. WIKI.logger.info('🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺')
  391. })
  392. }