diff --git a/libs/system.js b/libs/system.js index 75d09b79..fbbd32fd 100644 --- a/libs/system.js +++ b/libs/system.js @@ -1,10 +1,14 @@ 'use strict' const Promise = require('bluebird') -const https = require('follow-redirects').https +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') @@ -13,30 +17,118 @@ module.exports = { _remoteFile: 'https://github.com/Requarks/wiki/releases/download/{0}/wiki-js.tar.gz', _installDir: '', + /** + * Install a version of Wiki.js + * + * @param {any} targetTag The version to install + * @returns {Promise} Promise of the operation + */ install (targetTag) { let self = this self._installDir = path.resolve(ROOTPATH, appconfig.paths.data, 'install') 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')) } + 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', () => { - resolve(true) + 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(() => { + }) + }).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 => { + winston.error('Unable to restart Wiki.js via pm2... Do a manual restart!') + process.exit() + }) + }) + }).catch(err => { + winston.warn(err) + }) + }, + + /** + * 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} 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 + } }) } } diff --git a/package.json b/package.json index c011d143..562447b9 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "passport-local": "^1.0.0", "passport-windowslive": "^1.0.2", "passport.socketio": "^3.7.0", + "pm2": "^2.4.0", "pug": "^2.0.0-beta11", "read-chunk": "^2.0.0", "remove-markdown": "^0.1.0", @@ -96,6 +97,7 @@ "stopword": "^0.1.1", "stream-to-promise": "^2.2.0", "tar": "^2.2.1", + "through2": "^2.0.3", "validator": "^6.2.0", "validator-as-promised": "^1.0.2", "winston": "^2.3.0"