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.

235 lines
8.4 KiB

  1. 'use strict'
  2. // =====================================================
  3. // Wiki.js
  4. // Installation Script
  5. // =====================================================
  6. const Promise = require('bluebird')
  7. const _ = require('lodash')
  8. const colors = require('colors/safe')
  9. const exec = require('execa')
  10. const fs = Promise.promisifyAll(require('fs-extra'))
  11. const https = require('follow-redirects').https
  12. const inquirer = require('inquirer')
  13. const os = require('os')
  14. const path = require('path')
  15. const pm2 = Promise.promisifyAll(require('pm2'))
  16. const tar = require('tar')
  17. const zlib = require('zlib')
  18. const installDir = path.resolve(__dirname, '../..')
  19. // =====================================================
  20. // INSTALLATION TASKS
  21. // =====================================================
  22. const tasks = {
  23. /**
  24. * Stop and delete existing instances
  25. */
  26. stopAndDeleteInstances () {
  27. ora.text = 'Looking for running instances...'
  28. return pm2.connectAsync().then(() => {
  29. return pm2.describeAsync('wiki').then(() => {
  30. ora.text = 'Stopping and deleting process from pm2...'
  31. return pm2.deleteAsync('wiki')
  32. }).catch(err => { // eslint-disable-line handle-callback-err
  33. return true
  34. }).finally(() => {
  35. pm2.disconnect()
  36. })
  37. })
  38. },
  39. /**
  40. * Check for sufficient memory
  41. */
  42. checkRequirements () {
  43. ora.text = 'Checking system requirements...'
  44. if (os.totalmem() < 1024 * 1024 * 768) {
  45. return Promise.reject(new Error('Not enough memory to install dependencies. Minimum is 768 MB.'))
  46. } else {
  47. return Promise.resolve(true)
  48. }
  49. },
  50. /**
  51. * Install via local tarball if present
  52. */
  53. installFromLocal () {
  54. let hasTarball = true
  55. let tbPath = path.join(installDir, 'wiki-js.tar.gz')
  56. return fs.accessAsync(tbPath)
  57. .catch(err => { // eslint-disable-line handle-callback-err
  58. hasTarball = false
  59. }).then(() => {
  60. if (hasTarball) {
  61. ora.text = 'Local tarball found. Extracting...'
  62. return new Promise((resolve, reject) => {
  63. fs.createReadStream(tbPath).pipe(zlib.createGunzip())
  64. .pipe(tar.Extract({ path: installDir }))
  65. .on('error', err => reject(err))
  66. .on('end', () => {
  67. ora.text = 'Tarball extracted successfully.'
  68. resolve(true)
  69. })
  70. })
  71. } else {
  72. return false
  73. }
  74. })
  75. },
  76. /**
  77. * Install from GitHub release download
  78. */
  79. installFromRemote () {
  80. // Fetch version from npm package
  81. return fs.readJsonAsync('package.json').then((packageObj) => {
  82. let versionGet = _.chain(packageObj.version).split('.').take(4).join('.')
  83. let remoteURL = _.replace('https://github.com/Requarks/wiki/releases/download/v{0}/wiki-js.tar.gz', '{0}', versionGet)
  84. return new Promise((resolve, reject) => {
  85. // Fetch tarball
  86. ora.text = 'Looking for latest release...'
  87. https.get(remoteURL, resp => {
  88. if (resp.statusCode !== 200) {
  89. return reject(new Error('Remote file not found'))
  90. }
  91. ora.text = 'Remote wiki.js tarball found. Downloading...'
  92. // Extract tarball
  93. resp.pipe(zlib.createGunzip())
  94. .pipe(tar.Extract({ path: installDir }))
  95. .on('error', err => reject(err))
  96. .on('end', () => {
  97. ora.text = 'Tarball extracted successfully.'
  98. resolve(true)
  99. })
  100. })
  101. })
  102. })
  103. },
  104. /**
  105. * Install npm dependencies
  106. */
  107. installDependencies () {
  108. ora.text = 'Installing Wiki.js npm dependencies...'
  109. return exec.stdout('npm', ['install', '--only=production', '--no-optional'], {
  110. cwd: installDir
  111. }).then(results => {
  112. ora.text = 'Wiki.js npm dependencies installed successfully.'
  113. return true
  114. })
  115. },
  116. /**
  117. * Create default config.yml file if new installation
  118. */
  119. ensureConfigFile () {
  120. return fs.accessAsync(path.join(installDir, 'config.yml')).then(() => {
  121. // Is Upgrade
  122. ora.succeed('Upgrade completed.')
  123. console.info(colors.yellow('\n!!! IMPORTANT !!!'))
  124. console.info(colors.yellow('Running the configuration wizard is optional but recommended after an upgrade to ensure your config file is using the latest available settings.'))
  125. console.info(colors.yellow('Note that the contents of your config file will be displayed during the configuration wizard. It is therefor highly recommended to run the wizard on a non-publicly accessible port or skip this step completely.\n'))
  126. return true
  127. }).catch(err => {
  128. // Is New Install
  129. if (err.code === 'ENOENT') {
  130. ora.text = 'First-time install, creating a new config.yml...'
  131. return fs.copyAsync(path.join(installDir, 'config.sample.yml'), path.join(installDir, 'config.yml')).then(() => {
  132. ora.succeed('Installation succeeded.')
  133. return true
  134. })
  135. } else {
  136. return err
  137. }
  138. })
  139. },
  140. startConfigurationWizard () {
  141. if (process.stdout.isTTY) {
  142. inquirer.prompt([{
  143. type: 'list',
  144. name: 'action',
  145. message: 'Continue with configuration wizard?',
  146. default: 'default',
  147. choices: [
  148. { name: 'Yes, run configuration wizard on port 3000 (recommended)', value: 'default', short: 'Yes' },
  149. { name: 'Yes, run configuration wizard on a custom port...', value: 'custom', short: 'Yes' },
  150. { name: 'No, I\'ll configure the config file manually', value: 'exit', short: 'No' }
  151. ]
  152. }, {
  153. type: 'input',
  154. name: 'customport',
  155. message: 'Custom port to use:',
  156. default: 3000,
  157. validate: (val) => {
  158. val = _.toNumber(val)
  159. return (_.isNumber(val) && val > 0) ? true : 'Invalid Port!'
  160. },
  161. when: (ans) => {
  162. return ans.action === 'custom'
  163. }
  164. }]).then((ans) => {
  165. switch (ans.action) {
  166. case 'default':
  167. console.info(colors.bold.cyan('> Browse to http://your-server:3000/ to configure your wiki! (Replaced your-server with the hostname or IP of your server!)'))
  168. ora = require('ora')({ text: 'I\'ll wait until you\'re done ;)', color: 'yellow', spinner: 'pong' }).start()
  169. return exec.stdout('node', ['wiki', 'configure'], {
  170. cwd: installDir
  171. })
  172. case 'custom':
  173. console.info(colors.bold.cyan('> Browse to http://your-server:' + ans.customport + '/ to configure your wiki! (Replaced your-server with the hostname or IP of your server!)'))
  174. ora = require('ora')({ text: 'I\'ll wait until you\'re done ;)', color: 'yellow', spinner: 'pong' }).start()
  175. return exec.stdout('node', ['wiki', 'configure', ans.customport], {
  176. cwd: installDir
  177. })
  178. default:
  179. console.info(colors.bold.cyan('\n> You can run the configuration wizard using command:') + colors.bold.white(' node wiki configure') + colors.bold.cyan('.\n> Then start Wiki.js using command: ') + colors.bold.white('node wiki start'))
  180. return Promise.delay(7000).then(() => {
  181. process.exit(0)
  182. })
  183. }
  184. }).then(() => {
  185. ora.succeed(colors.bold.green('Wiki.js has been configured successfully. It is now starting up and should be accessible very soon!'))
  186. return Promise.delay(3000).then(() => {
  187. console.info('npm is finishing... please wait...')
  188. })
  189. })
  190. } else {
  191. console.info(colors.cyan('[WARNING] Non-interactive terminal detected. You must manually start the configuration wizard using the command: node wiki configure'))
  192. }
  193. }
  194. }
  195. // =====================================================
  196. // INSTALL SEQUENCE
  197. // =====================================================
  198. if (!process.env.IS_HEROKU) {
  199. console.info(colors.yellow(
  200. ' __ __ _ _ _ _ \n' +
  201. '/ / /\\ \\ (_) | _(_) (_)___ \n' +
  202. '\\ \\/ \\/ / | |/ / | | / __| \n' +
  203. ' \\ /\\ /| | <| |_ | \\__ \\ \n' +
  204. ' \\/ \\/ |_|_|\\_\\_(_)/ |___/ \n' +
  205. ' |__/\n'))
  206. }
  207. let ora = require('ora')({ text: 'Initializing...', spinner: 'dots12' }).start()
  208. Promise.join(
  209. tasks.stopAndDeleteInstances(),
  210. tasks.checkRequirements()
  211. ).then(() => {
  212. return tasks.installFromLocal().then(succeeded => {
  213. return (!succeeded) ? tasks.installFromRemote() : true
  214. })
  215. }).then(() => {
  216. return tasks.installDependencies()
  217. }).then(() => {
  218. return tasks.ensureConfigFile()
  219. }).then(() => {
  220. return tasks.startConfigurationWizard()
  221. }).catch(err => {
  222. ora.fail(err)
  223. })