feat: config wizard save

pull/621/head
NGPixel 7 years ago
parent 1f350172b9
commit 18dee58a06

@ -47,7 +47,7 @@ export default {
pathRepo: './repo',
port: siteConfig.port || 80,
public: (siteConfig.public === true),
selfregister: (siteConfig.selfregister === true),
selfRegister: (siteConfig.selfRegister === true),
telemetry: true,
title: siteConfig.title || 'Wiki',
upgrade: false,

@ -1,7 +1,7 @@
#######################################################################
# Wiki.js - CONFIGURATION #
#######################################################################
# Full explanation + examples in the documentation:
# Full documentation + examples:
# https://docs.requarks.io/wiki/install
# ---------------------------------------------------------------------
@ -39,11 +39,18 @@ redis:
db: 0
password: null
# ---------------------------------------------------------------------
# Log Level
# ---------------------------------------------------------------------
# Possible values: error, warn, info (default), verbose, debug, silly
logLevel: info
# ---------------------------------------------------------------------
# Configuration Mode
# ---------------------------------------------------------------------
# Possible values:
# - interactive (default)
# - interactive (recommended)
# - file
configMode: interactive

@ -33,17 +33,17 @@
},
"homepage": "https://github.com/Requarks/wiki#readme",
"engines": {
"node": ">=8.8.1"
"node": ">=8.9.3"
},
"dependencies": {
"apollo-server-express": "1.2.0",
"apollo-server-express": "1.3.0",
"auto-load": "3.0.0",
"axios": "0.17.1",
"bcryptjs-then": "1.0.1",
"bluebird": "3.5.1",
"body-parser": "1.18.2",
"bugsnag": "2.0.1",
"bull": "3.3.6",
"bull": "3.3.7",
"bunyan": "1.8.12",
"cheerio": "1.0.0-rc.2",
"child-process-promise": "2.2.1",
@ -59,24 +59,24 @@
"express-brute": "1.0.1",
"express-brute-redis": "0.0.1",
"express-session": "1.15.6",
"file-type": "7.2.0",
"file-type": "7.4.0",
"filesize.js": "1.0.2",
"follow-redirects": "1.2.5",
"fs-extra": "4.0.2",
"follow-redirects": "1.2.6",
"fs-extra": "5.0.0",
"git-wrapper2-promise": "0.2.9",
"graphql": "0.11.7",
"graphql-tools": "2.7.2",
"graphql": "0.12.1",
"graphql-tools": "2.13.0",
"highlight.js": "9.12.0",
"i18next": "10.0.7",
"i18next-express-middleware": "1.0.7",
"i18next": "10.2.1",
"i18next-express-middleware": "1.0.9",
"i18next-localstorage-cache": "1.1.1",
"i18next-node-fs-backend": "1.0.0",
"image-size": "0.6.1",
"ioredis": "3.2.1",
"image-size": "0.6.2",
"ioredis": "3.2.2",
"jimp": "0.2.28",
"js-yaml": "3.10.0",
"jsonwebtoken": "8.1.0",
"klaw": "2.1.0",
"klaw": "2.1.1",
"lodash": "4.17.4",
"markdown-it": "8.4.0",
"markdown-it-abbr": "1.0.4",
@ -90,9 +90,9 @@
"markdown-it-task-lists": "2.1.0",
"mathjax-node": "1.2.1",
"mime-types": "2.1.17",
"moment": "2.19.2",
"moment": "2.20.0",
"moment-timezone": "0.5.14",
"mongodb": "2.2.33",
"mongodb": "3.0.0-rc0",
"multer": "1.3.0",
"node-2fa": "1.1.2",
"node-graceful": "0.2.3",
@ -106,10 +106,10 @@
"passport-local": "1.0.0",
"passport-slack": "0.0.7",
"passport-windowslive": "1.0.2",
"pg": "7.4.0",
"pg": "6.4.2",
"pg-hstore": "2.3.2",
"pg-promise": "7.3.1",
"pm2": "2.7.2",
"pg-promise": "7.3.2",
"pm2": "2.9.1",
"pug": "2.0.0-rc.4",
"qr-image": "3.2.0",
"read-chunk": "2.1.0",
@ -117,65 +117,65 @@
"request": "2.83.0",
"request-promise": "4.2.2",
"semver": "5.4.1",
"sequelize": "4.22.6",
"sequelize": "4.28.5",
"serve-favicon": "2.4.5",
"simplemde": "1.11.2",
"stream-to-promise": "2.2.0",
"tar": "4.0.2",
"tar": "4.1.1",
"through2": "2.0.3",
"uuid": "3.1.0",
"validator": "9.1.1",
"validator": "9.2.0",
"validator-as-promised": "1.0.2",
"winston": "2.4.0",
"yargs": "10.0.3"
},
"devDependencies": {
"@glimpse/glimpse": "0.22.15",
"@panter/vue-i18next": "0.8.1",
"apollo-client-preset": "1.0.2",
"autoprefixer": "7.1.6",
"@panter/vue-i18next": "0.9.1",
"apollo-client-preset": "1.0.5",
"autoprefixer": "7.2.3",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-jest": "21.2.0",
"babel-preset-env": "1.6.1",
"babel-preset-es2015": "6.24.1",
"babel-preset-stage-2": "6.24.1",
"brace": "0.10.0",
"brace": "0.11.0",
"colors": "1.1.2",
"consolidate": "0.15.0",
"eslint": "4.11.0",
"eslint": "4.13.1",
"eslint-config-requarks": "1.0.7",
"eslint-config-standard": "10.2.1",
"eslint-config-standard": "11.0.0-beta.0",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-node": "5.2.1",
"eslint-plugin-promise": "3.6.0",
"eslint-plugin-standard": "3.0.1",
"eslint-plugin-vue": "3.13.1",
"fuse-box": "2.5.0-beta.1",
"graphql-tag": "^2.5.0",
"graphql-tag": "^2.6.0",
"i18next-xhr-backend": "1.5.0",
"jest": "21.2.1",
"jest-junit": "3.1.0",
"jest-junit": "3.4.0",
"js-cookie": "2.2.0",
"node-sass": "4.6.1",
"nodemon": "1.12.1",
"node-sass": "4.7.2",
"nodemon": "1.13.3",
"postcss-selector-parser": "3.1.1",
"pug-lint": "2.5.0",
"twemoji-awesome": "1.0.6",
"typescript": "2.6.1",
"uglify-es": "3.1.9",
"vee-validate": "2.0.0-rc.21",
"vue": "2.5.3",
"vue-clipboards": "1.1.0",
"vue-hot-reload-api": "2.2.3",
"typescript": "2.6.2",
"uglify-es": "3.2.2",
"vee-validate": "2.0.0-rc.27",
"vue": "2.5.11",
"vue-clipboards": "1.2.0",
"vue-hot-reload-api": "2.2.4",
"vue-lodash": "1.0.4",
"vue-material": "^0.8.1",
"vue-resource": "1.3.4",
"vue-simple-breakpoints": "1.0.3",
"vue-template-compiler": "2.5.3",
"vue-template-compiler": "2.5.11",
"vue-template-es2015-compiler": "1.6.0",
"vuex": "3.0.1",
"vuex-persistedstate": "2.3.2"
"vuex-persistedstate": "2.4.2"
},
"jest": {
"testResultsProcessor": "./node_modules/jest-junit",

@ -237,6 +237,7 @@ module.exports = () => {
}
// Update config file
wiki.logger.info('Writing config file to disk...')
let confRaw = await fs.readFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), 'utf8')
let conf = yaml.safeLoad(confRaw)
@ -256,18 +257,60 @@ module.exports = () => {
wiki.config.uploads = wiki.config.uploads || {}
// Site namespace
wiki.config.site.title = req.body.title
wiki.config.site.path = req.body.path
wiki.config.site.lang = req.body.lang
wiki.config.site.rtl = _.includes(wiki.data.rtlLangs, req.body.lang)
wiki.config.site.sessionSecret = (await crypto.randomBytesAsync(32)).toString('hex')
_.set(wiki.config.site, 'title', req.body.title)
_.set(wiki.config.site, 'path', req.body.path)
_.set(wiki.config.site, 'lang', req.body.lang)
_.set(wiki.config.site, 'rtl', _.includes(wiki.data.rtlLangs, req.body.lang))
_.set(wiki.config.site, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
// Auth namespace
wiki.config.auth.public = (req.body.public === 'true')
_.set(wiki.config.auth, 'public', req.body.public === 'true')
_.set(wiki.config.auth, 'strategies.local.allowSelfRegister', req.body.selfRegister === 'true')
// Git namespace
_.set(wiki.config.git, 'enabled', req.body.gitUseRemote === 'true')
if (wiki.config.git.enabled) {
_.set(wiki.config.git, 'url', req.body.gitUrl)
_.set(wiki.config.git, 'branch', req.body.gitBranch)
_.set(wiki.config.git, 'author.defaultEmail', req.body.gitServerEmail)
_.set(wiki.config.git, 'author.useUserEmail', req.body.gitShowUserEmail)
_.set(wiki.config.git, 'sslVerify', req.body.gitAuthSSL === 'true')
_.set(wiki.config.git, 'auth.type', req.body.gitAuthType)
switch (wiki.config.git.auth.type) {
case 'basic':
_.set(wiki.config.git, 'auth.user', req.body.gitAuthUser)
_.set(wiki.config.git, 'auth.pass', req.body.gitAuthPass)
break
case 'ssh':
_.set(wiki.config.git, 'auth.keyPath', req.body.gitAuthSSHKey)
break
case 'sshenv':
_.set(wiki.config.git, 'auth.keyEnv', req.body.gitAuthSSHKeyEnv)
break
case 'sshdb':
_.set(wiki.config.git, 'auth.keyContents', req.body.gitAuthSSHKeyDB)
break
}
}
// Logging namespace
wiki.config.logging.telemetry = (req.body.telemetry === 'true')
// Save config to DB
wiki.logger.info('Persisting config to DB...')
await wiki.configSvc.saveToDb()
// Create root administrator
wiki.logger.info('Creating root administrator...')
await wiki.db.User.upsert({
email: req.body.adminEmail,
provider: 'local',
password: await wiki.db.User.hashPassword(req.body.adminPassword),
name: 'Administrator',
role: 'admin',
tfaIsActive: false
})
res.json({ ok: true })
} catch (err) {
res.json({ ok: false, error: err.message })

@ -37,126 +37,126 @@ router.get('/t/*', (req, res, next) => {
})
})
router.post('/img', wiki.disk.uploadImgHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) {
res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
return true
}
Promise.map(req.files, (f) => {
let destFilename = ''
let destFilePath = ''
return wiki.disk.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
return readChunk(f.path, 0, 262)
}).then((buf) => {
// -> Check MIME type by magic number
let mimeInfo = fileType(buf)
if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
return Promise.reject(new Error(wiki.lang.t('errors:invalidfiletype')))
}
return true
}).then(() => {
// -> Move file to final destination
return fs.moveAsync(f.path, destFilePath, { clobber: false })
}).then(() => {
return {
ok: true,
filename: destFilename,
filesize: f.size
}
}).reflect()
}, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => {
if (r.isFulfilled()) {
return r.value()
} else {
return {
ok: false,
msg: r.reason().message
}
}
})
res.json({ ok: true, results: uplResults })
return true
}).catch((err) => {
res.json({ ok: false, msg: err.message })
return true
})
})
})
router.post('/file', wiki.disk.uploadFileHandler, (req, res, next) => {
let destFolder = _.chain(req.body.folder).trim().toLower().value()
wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
if (!destFolderPath) {
res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
return true
}
Promise.map(req.files, (f) => {
let destFilename = ''
let destFilePath = ''
return wiki.disk.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
destFilename = fname
destFilePath = path.resolve(destFolderPath, destFilename)
// -> Move file to final destination
return fs.moveAsync(f.path, destFilePath, { clobber: false })
}).then(() => {
return {
ok: true,
filename: destFilename,
filesize: f.size
}
}).reflect()
}, {concurrency: 3}).then((results) => {
let uplResults = _.map(results, (r) => {
if (r.isFulfilled()) {
return r.value()
} else {
return {
ok: false,
msg: r.reason().message
}
}
})
res.json({ ok: true, results: uplResults })
return true
}).catch((err) => {
res.json({ ok: false, msg: err.message })
return true
})
})
})
router.get('/*', (req, res, next) => {
let fileName = req.params[0]
if (!validPathRe.test(fileName)) {
return res.sendStatus(404).end()
}
// todo: Authentication-based access
res.sendFile(fileName, {
root: wiki.git.getRepoPath() + '/uploads/',
dotfiles: 'deny'
}, (err) => {
if (err) {
res.status(err.status).end()
}
})
})
// router.post('/img', wiki.disk.uploadImgHandler, (req, res, next) => {
// let destFolder = _.chain(req.body.folder).trim().toLower().value()
// wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
// if (!destFolderPath) {
// res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
// return true
// }
// Promise.map(req.files, (f) => {
// let destFilename = ''
// let destFilePath = ''
// return wiki.disk.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
// destFilename = fname
// destFilePath = path.resolve(destFolderPath, destFilename)
// return readChunk(f.path, 0, 262)
// }).then((buf) => {
// // -> Check MIME type by magic number
// let mimeInfo = fileType(buf)
// if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
// return Promise.reject(new Error(wiki.lang.t('errors:invalidfiletype')))
// }
// return true
// }).then(() => {
// // -> Move file to final destination
// return fs.moveAsync(f.path, destFilePath, { clobber: false })
// }).then(() => {
// return {
// ok: true,
// filename: destFilename,
// filesize: f.size
// }
// }).reflect()
// }, {concurrency: 3}).then((results) => {
// let uplResults = _.map(results, (r) => {
// if (r.isFulfilled()) {
// return r.value()
// } else {
// return {
// ok: false,
// msg: r.reason().message
// }
// }
// })
// res.json({ ok: true, results: uplResults })
// return true
// }).catch((err) => {
// res.json({ ok: false, msg: err.message })
// return true
// })
// })
// })
// router.post('/file', wiki.disk.uploadFileHandler, (req, res, next) => {
// let destFolder = _.chain(req.body.folder).trim().toLower().value()
// wiki.upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
// if (!destFolderPath) {
// res.json({ ok: false, msg: wiki.lang.t('errors:invalidfolder') })
// return true
// }
// Promise.map(req.files, (f) => {
// let destFilename = ''
// let destFilePath = ''
// return wiki.disk.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
// destFilename = fname
// destFilePath = path.resolve(destFolderPath, destFilename)
// // -> Move file to final destination
// return fs.moveAsync(f.path, destFilePath, { clobber: false })
// }).then(() => {
// return {
// ok: true,
// filename: destFilename,
// filesize: f.size
// }
// }).reflect()
// }, {concurrency: 3}).then((results) => {
// let uplResults = _.map(results, (r) => {
// if (r.isFulfilled()) {
// return r.value()
// } else {
// return {
// ok: false,
// msg: r.reason().message
// }
// }
// })
// res.json({ ok: true, results: uplResults })
// return true
// }).catch((err) => {
// res.json({ ok: false, msg: err.message })
// return true
// })
// })
// })
// router.get('/*', (req, res, next) => {
// let fileName = req.params[0]
// if (!validPathRe.test(fileName)) {
// return res.sendStatus(404).end()
// }
// // todo: Authentication-based access
// res.sendFile(fileName, {
// root: wiki.git.getRepoPath() + '/uploads/',
// dotfiles: 'deny'
// }, (err) => {
// if (err) {
// res.status(err.status).end()
// }
// })
// })
module.exports = router

@ -1,6 +1,6 @@
/* global wiki */
module.exports = () => {
module.exports = async () => {
// ----------------------------------------
// Load global modules
// ----------------------------------------

@ -54,28 +54,52 @@ module.exports = {
* @param {Array} subsets Array of subsets to load
* @returns Promise
*/
loadFromDb(subsets) {
async loadFromDb(subsets) {
if (!_.isArray(subsets) || subsets.length === 0) {
subsets = wiki.data.configNamespaces
}
return wiki.db.Setting.findAll({
let results = await wiki.db.Setting.findAll({
attributes: ['key', 'config'],
where: {
key: {
$in: subsets
}
}
}).then(results => {
if (_.isArray(results) && results.length === subsets.length) {
results.forEach(result => {
wiki.config[result.key] = result.config
})
if (_.isArray(results) && results.length === subsets.length) {
results.forEach(result => {
wiki.config[result.key] = result.config
})
return true
} else {
wiki.logger.warn('DB Configuration is empty or incomplete.')
return false
}
},
/**
* Save config to DB
*
* @param {Array} subsets Array of subsets to save
* @returns Promise
*/
async saveToDb(subsets) {
if (!_.isArray(subsets) || subsets.length === 0) {
subsets = wiki.data.configNamespaces
}
try {
for (let set of subsets) {
await wiki.db.Setting.upsert({
key: set,
config: _.get(wiki.config, set, {})
})
return true
} else {
wiki.logger.warn('DB Configuration is empty or incomplete.')
return false
}
})
} catch (err) {
wiki.logger.error(`Failed to save configuration to DB: ${err.message}`)
return false
}
return true
}
}

@ -73,7 +73,7 @@ module.exports = {
min: 0,
idle: 10000
},
logging: log => { wiki.logger.log('verbose', log) },
logging: log => { wiki.logger.log('debug', log) },
operatorsAliases
})
@ -110,7 +110,7 @@ module.exports = {
syncSchemas() {
return self.inst.sync({
force: false,
logging: log => { wiki.logger.log('verbose', log) }
logging: log => { wiki.logger.log('debug', log) }
})
},
// -> Set Connection App Name

@ -11,10 +11,10 @@ module.exports = {
// Console
let logger = new (winston.Logger)({
level: (wiki.IS_DEBUG) ? 'debug' : 'info',
level: wiki.config.logLevel,
transports: [
new (winston.transports.Console)({
level: (wiki.IS_DEBUG) ? 'debug' : 'info',
level: wiki.config.logLevel,
prettyPrint: true,
colorize: true,
silent: false,

@ -14,6 +14,8 @@ module.exports = {
async upgradeFromMongo (opts) {
wiki.telemetry.sendEvent('setup', 'upgradeFromMongo')
wiki.logger.info('Upgrading from MongoDB...')
let mongo = require('mongodb').MongoClient
let parsedMongoConStr = cfgHelper.parseConfigValue(opts.mongoCnStr)

@ -112,7 +112,7 @@ block body
label.label(for='ipt-public') Public Access
span.desc Should the site be accessible (read only) without login.
p.control.is-fullwidth
input#ipt-selfregister(type='checkbox', v-model='conf.selfregister', data-vv-scope='general', name='ipt-selfregister')
input#ipt-selfregister(type='checkbox', v-model='conf.selfRegister', data-vv-scope='general', name='ipt-selfregister')
label.label(for='ipt-selfregister') Allow Self-Registration
span.desc Can users create their own account to gain access?
section
@ -208,7 +208,7 @@ block body
p.control.is-fullwidth
label.label Private Key location
input(type='text', placeholder='e.g. /etc/wiki/keys/git.pem', v-model='conf.gitAuthSSHKey')
span.desc The full path to the private key on disk.
span.desc The full path to the #[strong unencrypted] private key on disk.
.column(v-show='conf.gitAuthType === "sshenv"')
p.control.is-fullwidth
label.label Private Key Environment Variable

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save