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.

136 lines
4.1 KiB

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