You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 lines
14 KiB

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