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.

134 lines
4.0 KiB

  1. 'use strict'
  2. const Promise = require('bluebird')
  3. const crypto = require('crypto')
  4. const fs = Promise.promisifyAll(require('fs-extra'))
  5. const https = require('follow-redirects').https
  6. const klaw = require('klaw')
  7. const path = require('path')
  8. const pm2 = Promise.promisifyAll(require('pm2'))
  9. const tar = require('tar')
  10. const through2 = require('through2')
  11. const zlib = require('zlib')
  12. const _ = require('lodash')
  13. module.exports = {
  14. _remoteFile: 'https://github.com/Requarks/wiki/releases/download/{0}/wiki-js.tar.gz',
  15. _installDir: '',
  16. /**
  17. * Install a version of Wiki.js
  18. *
  19. * @param {any} targetTag The version to install
  20. * @returns {Promise} Promise of the operation
  21. */
  22. install (targetTag) {
  23. let self = this
  24. self._installDir = path.resolve(ROOTPATH, appconfig.paths.data, 'install')
  25. return fs.ensureDirAsync(self._installDir).then(() => {
  26. return fs.emptyDirAsync(self._installDir)
  27. }).then(() => {
  28. let remoteURL = _.replace(self._remoteFile, '{0}', targetTag)
  29. return new Promise((resolve, reject) => {
  30. /**
  31. * Fetch tarball and extract to temporary folder
  32. */
  33. https.get(remoteURL, resp => {
  34. if (resp.statusCode !== 200) {
  35. return reject(new Error('Remote file not found'))
  36. }
  37. winston.info('[SERVER.System] Install tarball found. Downloading...')
  38. resp.pipe(zlib.createGunzip())
  39. .pipe(tar.Extract({ path: self._installDir }))
  40. .on('error', err => reject(err))
  41. .on('end', () => {
  42. winston.info('[SERVER.System] Tarball extracted. Comparing files...')
  43. /**
  44. * Replace old files
  45. */
  46. klaw(self._installDir)
  47. .on('error', err => reject(err))
  48. .on('end', () => {
  49. winston.info('[SERVER.System] All files were updated successfully.')
  50. resolve(true)
  51. })
  52. .pipe(self.replaceFile())
  53. })
  54. })
  55. })
  56. }).then(() => {
  57. winston.info('[SERVER.System] Cleaning install leftovers...')
  58. return fs.removeAsync(self._installDir).then(() => {
  59. winston.info('[SERVER.System] Restarting Wiki.js...')
  60. return pm2.restartAsync('wiki').catch(err => { // eslint-disable-line handle-callback-err
  61. winston.error('Unable to restart Wiki.js via pm2... Do a manual restart!')
  62. process.exit()
  63. })
  64. })
  65. }).catch(err => {
  66. winston.warn(err)
  67. })
  68. },
  69. /**
  70. * Replace file if different
  71. */
  72. replaceFile () {
  73. let self = this
  74. return through2.obj((item, enc, next) => {
  75. if (!item.stats.isDirectory()) {
  76. self.digestFile(item.path).then(sourceHash => {
  77. let destFilePath = _.replace(item.path, self._installDir, ROOTPATH)
  78. return self.digestFile(destFilePath).then(targetHash => {
  79. if (sourceHash === targetHash) {
  80. winston.log('verbose', '[SERVER.System] Skipping ' + destFilePath)
  81. return fs.removeAsync(item.path).then(() => {
  82. return next() || true
  83. })
  84. } else {
  85. winston.log('verbose', '[SERVER.System] Updating ' + destFilePath + '...')
  86. return fs.moveAsync(item.path, destFilePath, { overwrite: true }).then(() => {
  87. return next() || true
  88. })
  89. }
  90. })
  91. }).catch(err => {
  92. throw err
  93. })
  94. } else {
  95. next()
  96. }
  97. })
  98. },
  99. /**
  100. * Generate the hash of a file
  101. *
  102. * @param {String} filePath The absolute path of the file
  103. * @return {Promise<String>} Promise of the hash result
  104. */
  105. digestFile: (filePath) => {
  106. return new Promise((resolve, reject) => {
  107. let hash = crypto.createHash('sha1')
  108. hash.setEncoding('hex')
  109. fs.createReadStream(filePath)
  110. .on('error', err => { reject(err) })
  111. .on('end', () => {
  112. hash.end()
  113. resolve(hash.read())
  114. })
  115. .pipe(hash)
  116. }).catch(err => {
  117. if (err.code === 'ENOENT') {
  118. return '0'
  119. } else {
  120. throw err
  121. }
  122. })
  123. }
  124. }