浏览代码

feat: config wizard UI improv. + upgrade from Mongo

NGPixel 7 年之前
父节点
当前提交
82ea0b50fb

+ 2 - 1
.eslintrc.yml

@@ -3,8 +3,9 @@ extends:
   - plugin:vue/recommended
   - plugin:vue/recommended
 env:
 env:
   node: true
   node: true
-  es6: true
   jest: true
   jest: true
+parserOptions:
+  ecmaVersion: 2017
 globals:
 globals:
   document: false
   document: false
   navigator: false
   navigator: false

+ 9 - 0
assets/svg/config-bg.svg

@@ -0,0 +1,9 @@
+<svg 
+  xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'>
+  <g fill-rule='evenodd'>
+    <g fill='#1976d2' fill-opacity='0.52'>
+      <path opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/>
+      <path d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/>
+    </g>
+  </g>
+</svg>

+ 3 - 2
client/js/components/config-manager.component.js

@@ -43,7 +43,6 @@ export default {
         gitUrl: '',
         gitUrl: '',
         gitUseRemote: (siteConfig.git !== false),
         gitUseRemote: (siteConfig.git !== false),
         lang: siteConfig.lang || 'en',
         lang: siteConfig.lang || 'en',
-        mongo: 'mongodb://',
         path: siteConfig.path || '/',
         path: siteConfig.path || '/',
         pathRepo: './repo',
         pathRepo: './repo',
         port: siteConfig.port || 80,
         port: siteConfig.port || 80,
@@ -51,7 +50,9 @@ export default {
         selfregister: (siteConfig.selfregister === true),
         selfregister: (siteConfig.selfregister === true),
         telemetry: true,
         telemetry: true,
         title: siteConfig.title || 'Wiki',
         title: siteConfig.title || 'Wiki',
-        upgrade: false
+        upgrade: false,
+        upgMongo: 'mongodb://',
+        upgUserGroups: false
       },
       },
       considerations: {
       considerations: {
         https: false,
         https: false,

+ 19 - 14
client/scss/components/config-manager.scss

@@ -1,22 +1,10 @@
 .config-manager {
 .config-manager {
-  background-image: linear-gradient(to bottom right, mc('blue', '500'), mc('blue', '700'));
-  background-repeat: no-repeat;
+  background-color: #1565c0;
+  background-image: url('../svg/config-bg.svg');
   width: 100%;
   width: 100%;
   min-height: 100%;
   min-height: 100%;
   padding-top: 1rem;
   padding-top: 1rem;
 
 
-  &::before {
-    content: '';
-    position: absolute;
-    background-image: url('../svg/login-bg.svg');
-    background-position: center bottom;
-    background-size: cover;
-    top: 0;
-    left: 0;
-    width: 100vw;
-    height: 100vh;
-  }
-
   .welcome {
   .welcome {
     text-align: center;
     text-align: center;
     padding: 1rem 0 2rem 0;
     padding: 1rem 0 2rem 0;
@@ -81,4 +69,21 @@
     }
     }
 
 
   }
   }
+
+  footer {
+    background-color: mc('blue','800');
+    border-top: 1px solid mc('blue', '700');
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 0 25px;
+    height: 70px;
+    font-size: 13px;
+    font-weight: 500;
+    color: mc('blue','200');
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+  }
 }
 }

+ 1 - 1
client/scss/components/form.scss

@@ -167,7 +167,7 @@
 
 
 	input[type=checkbox] + label {
 	input[type=checkbox] + label {
 		&:before, &:after {
 		&:before, &:after {
-	    border-radius: 0;
+	    border-radius: 3px;
 		}
 		}
 	}
 	}
 
 

+ 9 - 3
config.sample.yml

@@ -39,8 +39,14 @@ redis:
   db: 0
   db: 0
   password: null
   password: null
 
 
-# Enable for right to left languages (e.g. arabic):
-langRtl: false
+# ---------------------------------------------------------------------
+# Configuration Mode
+# ---------------------------------------------------------------------
+# Possible values:
+#  - interactive (default)
+#  - file
+
+configMode: interactive
 
 
 # ---------------------------------------------------------------------
 # ---------------------------------------------------------------------
 # Background Workers
 # Background Workers
@@ -55,5 +61,5 @@ workers: 0
 # Read the docs BEFORE changing these settings!
 # Read the docs BEFORE changing these settings!
 
 
 ha:
 ha:
-  nodeuid: primary
+  node: primary
   readonly: false
   readonly: false

+ 2 - 1
server/app/data.yml

@@ -20,9 +20,10 @@ defaults:
       port: 6379
       port: 6379
       db: 0
       db: 0
       password: null
       password: null
+    configMode: interactive
     workers: 0
     workers: 0
     ha:
     ha:
-      nodeuid: primary
+      node: primary
       readonly: false
       readonly: false
     site:
     site:
       path: ''
       path: ''

+ 42 - 119
server/configure.js

@@ -8,6 +8,8 @@ module.exports = () => {
     title: 'Wiki.js'
     title: 'Wiki.js'
   }
   }
 
 
+  wiki.system = require('./modules/system')
+
   // ----------------------------------------
   // ----------------------------------------
   // Load modules
   // Load modules
   // ----------------------------------------
   // ----------------------------------------
@@ -18,11 +20,12 @@ module.exports = () => {
   const favicon = require('serve-favicon')
   const favicon = require('serve-favicon')
   const http = require('http')
   const http = require('http')
   const Promise = require('bluebird')
   const Promise = require('bluebird')
-  const fs = Promise.promisifyAll(require('fs-extra'))
+  const fs = require('fs-extra')
   const yaml = require('js-yaml')
   const yaml = require('js-yaml')
   const _ = require('lodash')
   const _ = require('lodash')
   const cfgHelper = require('./helpers/config')
   const cfgHelper = require('./helpers/config')
   const filesize = require('filesize.js')
   const filesize = require('filesize.js')
+  const crypto = Promise.promisifyAll(require('crypto'))
 
 
   // ----------------------------------------
   // ----------------------------------------
   // Define Express App
   // Define Express App
@@ -58,12 +61,11 @@ module.exports = () => {
   // Controllers
   // Controllers
   // ----------------------------------------
   // ----------------------------------------
 
 
-  app.get('*', (req, res) => {
-    fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => {
-      res.render('configure/index', {
-        packageObj,
-        telemetryClientID: wiki.telemetry.cid
-      })
+  app.get('*', async (req, res) => {
+    let packageObj = await fs.readJson(path.join(wiki.ROOTPATH, 'package.json'))
+    res.render('configure/index', {
+      packageObj,
+      telemetryClientID: wiki.telemetry.cid
     })
     })
   })
   })
 
 
@@ -120,7 +122,7 @@ module.exports = () => {
           throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
           throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
         }).return('config.yml is writable by the setup process.')
         }).return('config.yml is writable by the setup process.')
       }
       }
-    ], test => { return test() }).then(results => {
+    ], test => test()).then(results => {
       res.json({ ok: true, results })
       res.json({ ok: true, results })
     }).catch(err => {
     }).catch(err => {
       res.json({ ok: false, error: err.message })
       res.json({ ok: false, error: err.message })
@@ -151,10 +153,10 @@ module.exports = () => {
 
 
     Promise.mapSeries([
     Promise.mapSeries([
       () => {
       () => {
-        return fs.ensureDirAsync(dataDir).return('Data directory path is valid.')
+        return fs.ensureDir(dataDir).then(() => 'Data directory path is valid.')
       },
       },
       () => {
       () => {
-        return fs.ensureDirAsync(gitDir).return('Git directory path is valid.')
+        return fs.ensureDir(gitDir).then(() => 'Git directory path is valid.')
       },
       },
       () => {
       () => {
         return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => {
         return exec.stdout('git', ['init'], { cwd: gitDir }).then(result => {
@@ -181,7 +183,10 @@ module.exports = () => {
       },
       },
       () => {
       () => {
         if (req.body.gitUseRemote === false) { return false }
         if (req.body.gitUseRemote === false) { return false }
-        if (req.body.gitAuthType === 'ssh') {
+        if (_.includes(['sshenv', 'sshdb'], req.body.gitAuthType)) {
+          req.body.gitAuthSSHKey = path.join(dataDir, 'ssh/key.pem')
+        }
+        if (_.startsWith(req.body.gitAuthType, 'ssh')) {
           return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => {
           return exec.stdout('git', ['config', '--local', 'core.sshCommand', 'ssh -i "' + req.body.gitAuthSSHKey + '" -o StrictHostKeyChecking=no'], { cwd: gitDir }).then(result => {
             return 'Git SSH Private Key path has been set successfully.'
             return 'Git SSH Private Key path has been set successfully.'
           })
           })
@@ -220,120 +225,38 @@ module.exports = () => {
   /**
   /**
    * Finalize
    * Finalize
    */
    */
-  app.post('/finalize', (req, res) => {
+  app.post('/finalize', async (req, res) => {
     wiki.telemetry.sendEvent('setup', 'finalize')
     wiki.telemetry.sendEvent('setup', 'finalize')
 
 
-    const bcrypt = require('bcryptjs-then')
-    const crypto = Promise.promisifyAll(require('crypto'))
-    let mongo = require('mongodb').MongoClient
-    let parsedMongoConStr = cfgHelper.parseConfigValue(req.body.db)
-
-    Promise.join(
-      new Promise((resolve, reject) => {
-        mongo.connect(parsedMongoConStr, {
-          autoReconnect: false,
-          reconnectTries: 2,
-          reconnectInterval: 1000,
-          connectTimeoutMS: 5000,
-          socketTimeoutMS: 5000
-        }, (err, db) => {
-          if (err === null) {
-            db.createCollection('users', { strict: false }, (err, results) => {
-              if (err === null) {
-                bcrypt.hash(req.body.adminPassword).then(adminPwdHash => {
-                  db.collection('users').findOneAndUpdate({
-                    provider: 'local',
-                    email: req.body.adminEmail
-                  }, {
-                    provider: 'local',
-                    email: req.body.adminEmail,
-                    name: 'Administrator',
-                    password: adminPwdHash,
-                    rights: [{
-                      role: 'admin',
-                      path: '/',
-                      exact: false,
-                      deny: false
-                    }],
-                    updatedAt: new Date(),
-                    createdAt: new Date()
-                  }, {
-                    upsert: true,
-                    returnOriginal: false
-                  }, (err, results) => {
-                    if (err === null) {
-                      resolve(true)
-                    } else {
-                      reject(err)
-                    }
-                    db.close()
-                  })
-                })
-              } else {
-                reject(err)
-                db.close()
-              }
-            })
-          } else {
-            reject(err)
-          }
-        })
-      }),
-      fs.readFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {
-        let conf = yaml.safeLoad(confRaw)
-        conf.title = req.body.title
-        conf.host = req.body.host
-        conf.port = req.body.port
-        conf.paths = {
-          repo: req.body.pathRepo,
-          data: req.body.pathData
-        }
-        conf.uploads = {
-          maxImageFileSize: (conf.uploads && _.isNumber(conf.uploads.maxImageFileSize)) ? conf.uploads.maxImageFileSize : 3,
-          maxOtherFileSize: (conf.uploads && _.isNumber(conf.uploads.maxOtherFileSize)) ? conf.uploads.maxOtherFileSize : 100
-        }
-        conf.lang = req.body.lang
-        conf.public = (req.body.public === true)
-        if (conf.auth && conf.auth.local) {
-          conf.auth.local = { enabled: true }
-        } else {
-          conf.auth = { local: { enabled: true } }
-        }
-        conf.db = req.body.db
-        if (req.body.gitUseRemote === false) {
-          conf.git = false
-        } else {
-          conf.git = {
-            url: req.body.gitUrl,
-            branch: req.body.gitBranch,
-            auth: {
-              type: req.body.gitAuthType,
-              username: req.body.gitAuthUser,
-              password: req.body.gitAuthPass,
-              privateKey: req.body.gitAuthSSHKey,
-              sslVerify: (req.body.gitAuthSSL === true)
-            },
-            showUserEmail: (req.body.gitShowUserEmail === true),
-            serverEmail: req.body.gitServerEmail
-          }
-        }
-        return crypto.randomBytesAsync(32).then(buf => {
-          conf.sessionSecret = buf.toString('hex')
-          confRaw = yaml.safeDump(conf)
-          return fs.writeFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), confRaw)
+    try {
+      // Upgrade from Wiki.js 1.x?
+      if (req.body.upgrade) {
+        await wiki.system.upgradeFromMongo({
+          mongoCnStr: cfgHelper.parseConfigValue(req.body.upgMongo)
         })
         })
-      })
-    ).then(() => {
-      if (process.env.IS_HEROKU) {
-        return fs.outputJsonAsync(path.join(wiki.SERVERPATH, 'app/heroku.json'), { configured: true })
-      } else {
-        return true
       }
       }
-    }).then(() => {
+
+      // Load configuration file
+      let confRaw = await fs.readFile(path.join(wiki.ROOTPATH, 'config.yml'), 'utf8')
+      let conf = yaml.safeLoad(confRaw)
+
+      // Update config
+      conf.host = req.body.host
+      conf.port = req.body.port
+      conf.paths.repo = req.body.pathRepo
+
+      // Generate session secret
+      let sessionSecret = (await crypto.randomBytesAsync(32)).toString('hex')
+      console.info(sessionSecret)
+
+      // Save updated config to file
+      confRaw = yaml.safeDump(conf)
+      await fs.writeFile(path.join(wiki.ROOTPATH, 'config.yml'), confRaw)
+
       res.json({ ok: true })
       res.json({ ok: true })
-    }).catch(err => {
+    } catch (err) {
       res.json({ ok: false, error: err.message })
       res.json({ ok: false, error: err.message })
-    })
+    }
   })
   })
 
 
   /**
   /**

+ 38 - 121
server/modules/system.js

@@ -1,136 +1,53 @@
-'use strict'
-
-/* global winston, ROOTPATH, appconfig */
+/* global wiki */
 
 
 const Promise = require('bluebird')
 const Promise = require('bluebird')
-const crypto = require('crypto')
-const fs = Promise.promisifyAll(require('fs-extra'))
-const https = require('follow-redirects').https
-const klaw = require('klaw')
-const path = require('path')
-const pm2 = Promise.promisifyAll(require('pm2'))
-const tar = require('tar')
-const through2 = require('through2')
-const zlib = require('zlib')
-const _ = require('lodash')
+// const pm2 = Promise.promisifyAll(require('pm2'))
+// const _ = require('lodash')
+const cfgHelper = require('../helpers/config')
 
 
 module.exports = {
 module.exports = {
-
-  _remoteFile: 'https://github.com/Requarks/wiki/releases/download/{0}/wiki-js.tar.gz',
-  _installDir: '',
-
   /**
   /**
-   * Install a version of Wiki.js
+   * Upgrade from Wiki.js 1.x - MongoDB database
    *
    *
-   * @param {any} targetTag The version to install
-   * @returns {Promise} Promise of the operation
+   * @param {Object} opts Options object
    */
    */
-  install (targetTag) {
-    let self = this
+  async upgradeFromMongo (opts) {
+    wiki.telemetry.sendEvent('setup', 'upgradeFromMongo')
 
 
-    self._installDir = path.resolve(ROOTPATH, appconfig.paths.data, 'install')
+    let mongo = require('mongodb').MongoClient
+    let parsedMongoConStr = cfgHelper.parseConfigValue(opts.mongoCnStr)
 
 
-    return fs.ensureDirAsync(self._installDir).then(() => {
-      return fs.emptyDirAsync(self._installDir)
-    }).then(() => {
-      let remoteURL = _.replace(self._remoteFile, '{0}', targetTag)
-
-      return new Promise((resolve, reject) => {
-        /**
-         * Fetch tarball and extract to temporary folder
-         */
-        https.get(remoteURL, resp => {
-          if (resp.statusCode !== 200) {
-            return reject(new Error('Remote file not found'))
+    return new Promise((resolve, reject) => {
+      // Connect to MongoDB
+
+      return mongo.connect(parsedMongoConStr, {
+        autoReconnect: false,
+        reconnectTries: 2,
+        reconnectInterval: 1000,
+        connectTimeoutMS: 5000,
+        socketTimeoutMS: 5000
+      }, async (err, db) => {
+        try {
+          if (err !== null) { throw err }
+
+          let users = db.collection('users')
+
+          // Check if users table is populated
+          let userCount = await users.count()
+          if (userCount < 1) {
+            throw new Error('Users table is empty or invalid!')
           }
           }
-          winston.info('[SERVER.System] Install tarball found. Downloading...')
 
 
-          resp.pipe(zlib.createGunzip())
-            .pipe(tar.Extract({ path: self._installDir }))
-            .on('error', err => reject(err))
-            .on('end', () => {
-              winston.info('[SERVER.System] Tarball extracted. Comparing files...')
-              /**
-             * Replace old files
-             */
-              klaw(self._installDir)
-                .on('error', err => reject(err))
-                .on('end', () => {
-                  winston.info('[SERVER.System] All files were updated successfully.')
-                  resolve(true)
-                })
-                .pipe(self.replaceFile())
-            })
-        })
-      })
-    }).then(() => {
-      winston.info('[SERVER.System] Cleaning install leftovers...')
-      return fs.removeAsync(self._installDir).then(() => {
-        winston.info('[SERVER.System] Restarting Wiki.js...')
-        return pm2.restartAsync('wiki').catch(err => { // eslint-disable-line handle-callback-err
-          winston.error('Unable to restart Wiki.js via pm2... Do a manual restart!')
-          process.exit()
-        })
-      })
-    }).catch(err => {
-      winston.warn(err)
-    })
-  },
+          // Fetch all users
+          let userData = await users.find({}).toArray()
+          console.info(userData)
 
 
-  /**
-   * Replace file if different
-   */
-  replaceFile () {
-    let self = this
-    return through2.obj((item, enc, next) => {
-      if (!item.stats.isDirectory()) {
-        self.digestFile(item.path).then(sourceHash => {
-          let destFilePath = _.replace(item.path, self._installDir, ROOTPATH)
-          return self.digestFile(destFilePath).then(targetHash => {
-            if (sourceHash === targetHash) {
-              winston.log('verbose', '[SERVER.System] Skipping ' + destFilePath)
-              return fs.removeAsync(item.path).then(() => {
-                return next() || true
-              })
-            } else {
-              winston.log('verbose', '[SERVER.System] Updating ' + destFilePath + '...')
-              return fs.moveAsync(item.path, destFilePath, { overwrite: true }).then(() => {
-                return next() || true
-              })
-            }
-          })
-        }).catch(err => {
-          throw err
-        })
-      } else {
-        next()
-      }
-    })
-  },
-
-  /**
-   * Generate the hash of a file
-   *
-   * @param {String} filePath The absolute path of the file
-   * @return {Promise<String>} Promise of the hash result
-   */
-  digestFile: (filePath) => {
-    return new Promise((resolve, reject) => {
-      let hash = crypto.createHash('sha1')
-      hash.setEncoding('hex')
-      fs.createReadStream(filePath)
-        .on('error', err => { reject(err) })
-        .on('end', () => {
-          hash.end()
-          resolve(hash.read())
-        })
-        .pipe(hash)
-    }).catch(err => {
-      if (err.code === 'ENOENT') {
-        return '0'
-      } else {
-        throw err
-      }
+          resolve(true)
+        } catch (err) {
+          reject(err)
+          db.close()
+        }
+      })
     })
     })
   }
   }
 }
 }

+ 4 - 4
server/views/configure/index.pug

@@ -187,7 +187,7 @@ block body
                           label.label Authentication
                           label.label Authentication
                           select(v-model='conf.gitAuthType')
                           select(v-model='conf.gitAuthType')
                             option(value='ssh') SSH using Private Key file (recommended)
                             option(value='ssh') SSH using Private Key file (recommended)
-                            option(value='sshenv') SSH using Private Key in env. variable
+                            option(value='sshenv') SSH using Private Key in environment variable
                             option(value='sshdb') SSH using Private Key in database
                             option(value='sshdb') SSH using Private Key in database
                             option(value='basic') Basic Credentials
                             option(value='basic') Basic Credentials
                           span.desc The authentication method used to connect to your remote Git repository.
                           span.desc The authentication method used to connect to your remote Git repository.
@@ -317,11 +317,11 @@ block body
                     section
                     section
                       p.control.is-fullwidth
                       p.control.is-fullwidth
                         label.label Connection String to Wiki.js 1.x MongoDB database
                         label.label Connection String to Wiki.js 1.x MongoDB database
-                        input(type='text', placeholder='mongodb://', v-model='conf.mongo', data-vv-scope='upgrade', name='ipt-mongo', v-validate='{ required: true, min: 2 }')
+                        input(type='text', placeholder='mongodb://', v-model='conf.upgMongo', data-vv-scope='upgrade', name='ipt-mongo', v-validate='{ required: true, min: 2 }')
                         span.desc A MongoDB database connection string where a Wiki.js 1.x installation is located. #[strong No alterations will be made to this database. ]
                         span.desc A MongoDB database connection string where a Wiki.js 1.x installation is located. #[strong No alterations will be made to this database. ]
                     section
                     section
                       p.control.is-fullwidth
                       p.control.is-fullwidth
-                        input#ipt-public(type='checkbox', v-model='conf.public', data-vv-scope='upgrade', name='ipt-public')
+                        input#ipt-public(type='checkbox', v-model='conf.upgUserGroups', data-vv-scope='upgrade', name='ipt-public')
                         label.label(for='ipt-public') Create groups based on individual permissions
                         label.label(for='ipt-public') Create groups based on individual permissions
                         span.desc User groups will be created based on existing users permissions. If multiple users have the exact same permission rules, they will be put in the same user group.
                         span.desc User groups will be created based on existing users permissions. If multiple users have the exact same permission rules, they will be put in the same user group.
                   .panel-footer
                   .panel-footer
@@ -370,6 +370,6 @@ block body
                   .panel-footer
                   .panel-footer
                     button.button.is-small.is-green(disabled='disabled') Start
                     button.button.is-small.is-green(disabled='disabled') Start
           
           
-          .footer
+          footer
             small Wiki.js Installation Wizard
             small Wiki.js Installation Wizard
             small(v-if='conf.telemetry') Telemetry Client ID: !{telemetryClientID}
             small(v-if='conf.telemetry') Telemetry Client ID: !{telemetryClientID}