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.

451 lines
14 KiB

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