setup.js 11.4 KB
Newer Older
1
const path = require('path')
2
const uuid = require('uuid/v4')
3

4
/* global WIKI */
5

6
module.exports = () => {
7
  WIKI.config.site = {
8
    path: '',
9
    title: 'Wiki.js'
10 11
  }

12
  WIKI.system = require('./core/system')
13

14 15 16 17 18 19 20 21 22
  // ----------------------------------------
  // Load modules
  // ----------------------------------------

  const bodyParser = require('body-parser')
  const compression = require('compression')
  const express = require('express')
  const favicon = require('serve-favicon')
  const http = require('http')
23
  const Promise = require('bluebird')
24
  const fs = require('fs-extra')
25
  const _ = require('lodash')
NGPixel's avatar
NGPixel committed
26
  const cfgHelper = require('./helpers/config')
27
  const crypto = Promise.promisifyAll(require('crypto'))
28
  const pem2jwk = require('pem-jwk').pem2jwk
29
  const semver = require('semver')
30 31 32 33 34

  // ----------------------------------------
  // Define Express App
  // ----------------------------------------

35
  let app = express()
36 37 38 39 40 41
  app.use(compression())

  // ----------------------------------------
  // Public Assets
  // ----------------------------------------

42 43
  app.use(favicon(path.join(WIKI.ROOTPATH, 'assets', 'favicon.ico')))
  app.use(express.static(path.join(WIKI.ROOTPATH, 'assets')))
44 45 46 47 48

  // ----------------------------------------
  // View Engine Setup
  // ----------------------------------------

49
  app.set('views', path.join(WIKI.SERVERPATH, 'views'))
50 51 52 53 54
  app.set('view engine', 'pug')

  app.use(bodyParser.json())
  app.use(bodyParser.urlencoded({ extended: false }))

55 56
  app.locals.config = WIKI.config
  app.locals.data = WIKI.data
57 58
  app.locals._ = require('lodash')

NGPixel's avatar
NGPixel committed
59 60 61 62 63
  // ----------------------------------------
  // HMR (Dev Mode Only)
  // ----------------------------------------

  if (global.DEV) {
64 65
    app.use(global.WP_DEV.devMiddleware)
    app.use(global.WP_DEV.hotMiddleware)
NGPixel's avatar
NGPixel committed
66 67
  }

68 69 70 71
  // ----------------------------------------
  // Controllers
  // ----------------------------------------

72
  app.get('*', async (req, res) => {
73
    let packageObj = await fs.readJson(path.join(WIKI.ROOTPATH, 'package.json'))
74
    res.render('setup', {
75
      packageObj,
76
      telemetryClientID: WIKI.telemetry.cid
77
    })
78 79
  })

NGPixel's avatar
NGPixel committed
80
  /**
81
   * Finalize
NGPixel's avatar
NGPixel committed
82
   */
83
  app.post('/finalize', async (req, res) => {
84
    WIKI.telemetry.sendEvent('setup', 'finalize')
NGPixel's avatar
NGPixel committed
85

86
    try {
87 88 89 90 91
      // Basic checks
      if (!semver.satisfies(process.version, '>=10.14')) {
        throw new Error('Node.js 10.14.x or later required!')
      }

92
      // Upgrade from WIKI.js 1.x?
93
      if (req.body.upgrade) {
94
        await WIKI.system.upgradeFromMongo({
95
          mongoCnStr: cfgHelper.parseConfigValue(req.body.upgMongo)
96
        })
97
      }
98

99
      // Create directory structure
100
      WIKI.logger.info('Creating data directories...')
101 102
      const dataPath = path.join(process.cwd(), 'data')
      await fs.ensureDir(dataPath)
103
      await fs.emptyDir(path.join(dataPath, 'cache'))
104
      await fs.ensureDir(path.join(dataPath, 'uploads'))
105

106
      // Set config
107 108 109 110 111
      _.set(WIKI.config, 'auth', {
        audience: 'urn:wiki.js',
        tokenExpiration: '30m',
        tokenRenewal: '14d'
      })
112 113 114 115 116 117
      _.set(WIKI.config, 'company', '')
      _.set(WIKI.config, 'features', {
        featurePageRatings: true,
        featurePageComments: true,
        featurePersonalWikis: true
      })
118
      _.set(WIKI.config, 'graphEndpoint', 'https://graph.requarks.io')
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
      _.set(WIKI.config, 'host', 'http://')
      _.set(WIKI.config, 'lang', {
        code: 'en',
        autoUpdate: true,
        namespacing: false,
        namespaces: []
      })
      _.set(WIKI.config, 'logo', {
        hasLogo: false,
        logoIsSquare: false
      })
      _.set(WIKI.config, 'mail', {
        senderName: '',
        senderEmail: '',
        host: '',
        port: 465,
        secure: true,
        user: '',
        pass: '',
        useDKIM: false,
        dkimDomainName: '',
        dkimKeySelector: '',
        dkimPrivateKey: ''
      })
      _.set(WIKI.config, 'seo', {
        description: '',
        robots: ['index', 'follow'],
146 147
        analyticsService: '',
        analyticsId: ''
148
      })
149
      _.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
150
      _.set(WIKI.config, 'telemetry', {
151
        isEnabled: req.body.telemetry === true,
152 153 154 155 156 157
        clientId: WIKI.telemetry.cid
      })
      _.set(WIKI.config, 'theming', {
        theme: 'default',
        darkMode: false
      })
158
      _.set(WIKI.config, 'title', 'Wiki.js')
159

160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
      // Generate certificates
      WIKI.logger.info('Generating certificates...')
      const certs = crypto.generateKeyPairSync('rsa', {
        modulusLength: 2048,
        publicKeyEncoding: {
          type: 'pkcs1',
          format: 'pem'
        },
        privateKeyEncoding: {
          type: 'pkcs1',
          format: 'pem',
          cipher: 'aes-256-cbc',
          passphrase: WIKI.config.sessionSecret
        }
      })

176 177 178 179 180
      _.set(WIKI.config, 'certs', {
        jwk: pem2jwk(certs.publicKey),
        public: certs.publicKey,
        private: certs.privateKey
      })
181

NGPixel's avatar
NGPixel committed
182
      // Save config to DB
183
      WIKI.logger.info('Persisting config to DB...')
184
      await WIKI.configSvc.saveToDb([
185
        'auth',
186 187 188
        'certs',
        'company',
        'features',
189
        'graphEndpoint',
190
        'host',
191
        'lang',
192 193 194
        'logo',
        'mail',
        'seo',
195 196
        'sessionSecret',
        'telemetry',
197
        'theming',
198
        'title'
199
      ])
NGPixel's avatar
NGPixel committed
200

201 202
      // Create default locale
      WIKI.logger.info('Installing default locale...')
203
      await WIKI.models.locales.query().insert({
204 205 206 207 208 209 210
        code: 'en',
        strings: require('./locales/default.json'),
        isRTL: false,
        name: 'English',
        nativeName: 'English'
      })

211
      // Create default groups
212 213 214 215 216

      WIKI.logger.info('Creating default groups...')
      const adminGroup = await WIKI.models.groups.query().insert({
        name: 'Administrators',
        permissions: JSON.stringify(['manage:system']),
217
        pageRules: JSON.stringify([]),
218 219 220 221
        isSystem: true
      })
      const guestGroup = await WIKI.models.groups.query().insert({
        name: 'Guests',
222
        permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
223
        pageRules: JSON.stringify([
224
          { id: 'guest', roles: ['read:pages', 'read:assets', 'read:comments'], match: 'START', deny: false, path: '', locales: [] }
225
        ]),
226 227 228
        isSystem: true
      })

229
      // Load authentication strategies + enable local
230 231
      await WIKI.models.authentication.refreshStrategiesFromDisk()
      await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local')
232 233

      // Load editors + enable default
234 235
      await WIKI.models.editors.refreshEditorsFromDisk()
      await WIKI.models.editors.query().patch({ isEnabled: true }).where('key', 'markdown')
236

237 238 239
      // Load loggers
      await WIKI.models.loggers.refreshLoggersFromDisk()

240 241 242
      // Load renderers
      await WIKI.models.renderers.refreshRenderersFromDisk()

243 244 245 246
      // Load search engines + enable default
      await WIKI.models.searchEngines.refreshSearchEnginesFromDisk()
      await WIKI.models.searchEngines.query().patch({ isEnabled: true }).where('key', 'db')

247
      // Load storage targets
248
      await WIKI.models.storage.refreshTargetsFromDisk()
249

NGPixel's avatar
NGPixel committed
250
      // Create root administrator
251
      WIKI.logger.info('Creating root administrator...')
252
      await WIKI.models.users.query().delete().where({
NGPixel's avatar
NGPixel committed
253
        providerKey: 'local',
254 255
        email: req.body.adminEmail
      })
256
      const adminUser = await WIKI.models.users.query().insert({
NGPixel's avatar
NGPixel committed
257 258
        email: req.body.adminEmail,
        provider: 'local',
259
        password: req.body.adminPassword,
NGPixel's avatar
NGPixel committed
260
        name: 'Administrator',
261
        locale: 'en',
262
        defaultEditor: 'markdown',
263 264 265
        tfaIsActive: false,
        isActive: true,
        isVerified: true
NGPixel's avatar
NGPixel committed
266
      })
267
      await adminUser.$relatedQuery('groups').relate(adminGroup.id)
NGPixel's avatar
NGPixel committed
268

269
      // Create Guest account
270
      WIKI.logger.info('Creating guest account...')
271
      await WIKI.models.users.query().delete().where({
NGPixel's avatar
NGPixel committed
272
        providerKey: 'local',
273 274
        email: 'guest@example.com'
      })
275 276 277 278 279 280 281
      const guestUser = await WIKI.models.users.query().insert({
        provider: 'local',
        email: 'guest@example.com',
        name: 'Guest',
        password: '',
        locale: 'en',
        defaultEditor: 'markdown',
282
        tfaIsActive: false,
283 284 285
        isSystem: true,
        isActive: true,
        isVerified: true
286 287
      })
      await guestUser.$relatedQuery('groups').relate(guestGroup.id)
288

289 290 291 292 293 294
      // Create site nav

      WIKI.logger.info('Creating default site navigation')
      await WIKI.models.navigation.query().delete().where({ key: 'site' })
      await WIKI.models.navigation.query().insert({
        key: 'site',
Nicolas Giard's avatar
Nicolas Giard committed
295
        config: [
296
          {
297
            id: uuid(),
298 299 300 301 302 303
            icon: 'home',
            kind: 'link',
            label: 'Home',
            target: '/',
            targetType: 'home'
          }
Nicolas Giard's avatar
Nicolas Giard committed
304
        ]
305 306
      })

307
      WIKI.logger.info('Setup is complete!')
308 309
      res.json({
        ok: true,
310
        redirectPath: '/',
311
        redirectPort: WIKI.config.port
312 313
      }).end()

314 315
      WIKI.config.setup = false

316
      WIKI.logger.info('Stopping Setup...')
317
      WIKI.server.destroy(() => {
318
        WIKI.logger.info('Setup stopped. Starting Wiki.js...')
319
        _.delay(() => {
320
          WIKI.kernel.bootMaster()
321 322
        }, 1000)
      })
323
    } catch (err) {
324
      res.json({ ok: false, error: err.message })
325
    }
NGPixel's avatar
NGPixel committed
326 327
  })

328 329 330 331 332 333 334 335 336 337 338 339 340 341
  // ----------------------------------------
  // Error handling
  // ----------------------------------------

  app.use(function (req, res, next) {
    var err = new Error('Not Found')
    err.status = 404
    next(err)
  })

  app.use(function (err, req, res, next) {
    res.status(err.status || 500)
    res.send({
      message: err.message,
342
      error: WIKI.IS_DEBUG ? err : {}
343
    })
344 345
    WIKI.logger.error(err.message)
    WIKI.telemetry.sendError(err)
346 347 348 349 350 351
  })

  // ----------------------------------------
  // Start HTTP server
  // ----------------------------------------

352
  WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`)
353

354 355
  app.set('port', WIKI.config.port)
  WIKI.server = http.createServer(app)
356
  WIKI.server.listen(WIKI.config.port, WIKI.config.bindIP)
357 358 359

  var openConnections = []

360
  WIKI.server.on('connection', (conn) => {
361 362 363 364 365 366 367
    let key = conn.remoteAddress + ':' + conn.remotePort
    openConnections[key] = conn
    conn.on('close', () => {
      delete openConnections[key]
    })
  })

368 369
  WIKI.server.destroy = (cb) => {
    WIKI.server.close(cb)
370 371 372 373 374
    for (let key in openConnections) {
      openConnections[key].destroy()
    }
  }

375
  WIKI.server.on('error', (error) => {
376 377 378 379 380 381
    if (error.syscall !== 'listen') {
      throw error
    }

    switch (error.code) {
      case 'EACCES':
382
        WIKI.logger.error('Listening on port ' + WIKI.config.port + ' requires elevated privileges!')
NGPixel's avatar
NGPixel committed
383
        return process.exit(1)
384
      case 'EADDRINUSE':
385
        WIKI.logger.error('Port ' + WIKI.config.port + ' is already in use!')
NGPixel's avatar
NGPixel committed
386
        return process.exit(1)
387 388 389 390 391
      default:
        throw error
    }
  })

392
  WIKI.server.on('listening', () => {
393
    WIKI.logger.info('HTTP Server: [ RUNNING ]')
394 395 396 397 398
    WIKI.logger.info('🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻')
    WIKI.logger.info('')
    WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/ to complete setup!`)
    WIKI.logger.info('')
    WIKI.logger.info('🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺')
399 400
  })
}