Gruntfile.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. /* eslint-disable
  2. camelcase,
  3. no-return-assign,
  4. no-unreachable,
  5. no-unused-vars,
  6. node/handle-callback-err,
  7. */
  8. // TODO: This file was created by bulk-decaffeinate.
  9. // Fix any style issues and re-enable lint.
  10. /*
  11. * decaffeinate suggestions:
  12. * DS101: Remove unnecessary use of Array.from
  13. * DS102: Remove unnecessary code created because of implicit returns
  14. * DS103: Rewrite code to no longer use __guard__, or convert again using --optional-chaining
  15. * DS205: Consider reworking code to avoid use of IIFEs
  16. * DS207: Consider shorter variations of null checks
  17. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  18. */
  19. const coffee = require('coffee-script')
  20. const fs = require('fs')
  21. const { spawn } = require('child_process')
  22. const { exec } = require('child_process')
  23. const rimraf = require('rimraf')
  24. const Path = require('path')
  25. const semver = require('semver')
  26. const knox = require('knox')
  27. const crypto = require('crypto')
  28. const async = require('async')
  29. const settings = require('@overleaf/settings')
  30. const _ = require('underscore')
  31. const SERVICES = require('./config/services')
  32. module.exports = function (grunt) {
  33. let Helpers
  34. let service
  35. grunt.loadNpmTasks('grunt-bunyan')
  36. grunt.loadNpmTasks('grunt-execute')
  37. grunt.loadNpmTasks('grunt-available-tasks')
  38. grunt.loadNpmTasks('grunt-concurrent')
  39. grunt.loadNpmTasks('grunt-contrib-coffee')
  40. grunt.loadNpmTasks('grunt-shell')
  41. grunt.task.loadTasks('./tasks')
  42. const execute = {}
  43. for (service of Array.from(SERVICES)) {
  44. execute[service.name] = { src: `${service.name}/app.js` }
  45. }
  46. grunt.initConfig({
  47. execute,
  48. concurrent: {
  49. all: {
  50. tasks: (() => {
  51. const result = []
  52. for (service of Array.from(SERVICES)) {
  53. result.push(`run:${service.name}`)
  54. }
  55. return result
  56. })(),
  57. options: {
  58. limit: SERVICES.length,
  59. logConcurrentOutput: true,
  60. },
  61. },
  62. },
  63. availabletasks: {
  64. tasks: {
  65. options: {
  66. filter: 'exclude',
  67. tasks: ['concurrent', 'execute', 'bunyan', 'availabletasks'],
  68. groups: {
  69. 'Run tasks': ['run', 'run:all', 'default'].concat(
  70. (() => {
  71. const result1 = []
  72. for (service of Array.from(SERVICES)) {
  73. result1.push(`run:${service.name}`)
  74. }
  75. return result1
  76. })()
  77. ),
  78. Misc: ['help'],
  79. 'Install tasks': (() => {
  80. const result2 = []
  81. for (service of Array.from(SERVICES)) {
  82. result2.push(`install:${service.name}`)
  83. }
  84. return result2
  85. })().concat(['install:all', 'install']),
  86. 'Update tasks': (() => {
  87. const result3 = []
  88. for (service of Array.from(SERVICES)) {
  89. result3.push(`update:${service.name}`)
  90. }
  91. return result3
  92. })().concat(['update:all', 'update']),
  93. Checks: [
  94. 'check',
  95. 'check:redis',
  96. 'check:latexmk',
  97. 'check:s3',
  98. 'check:make',
  99. 'check:mongo',
  100. ],
  101. },
  102. },
  103. },
  104. },
  105. })
  106. for (service of Array.from(SERVICES)) {
  107. ;(service =>
  108. grunt.registerTask(
  109. `install:${service.name}`,
  110. `Download and set up the ${service.name} service`,
  111. function () {
  112. const done = this.async()
  113. return Helpers.installService(service, done)
  114. }
  115. ))(service)
  116. }
  117. grunt.registerTask(
  118. 'install:all',
  119. 'Download and set up all ShareLaTeX services',
  120. []
  121. .concat(
  122. (() => {
  123. const result4 = []
  124. for (service of Array.from(SERVICES)) {
  125. result4.push(`install:${service.name}`)
  126. }
  127. return result4
  128. })()
  129. )
  130. .concat(['postinstall'])
  131. )
  132. grunt.registerTask('install', 'install:all')
  133. grunt.registerTask('postinstall', 'Explain postinstall steps', function () {
  134. return Helpers.postinstallMessage(this.async())
  135. })
  136. grunt.registerTask(
  137. 'update:all',
  138. 'Checkout and update all ShareLaTeX services',
  139. ['check:make'].concat(
  140. (() => {
  141. const result5 = []
  142. for (service of Array.from(SERVICES)) {
  143. result5.push(`update:${service.name}`)
  144. }
  145. return result5
  146. })()
  147. )
  148. )
  149. grunt.registerTask('update', 'update:all')
  150. grunt.registerTask('run', 'Run all of the sharelatex processes', [
  151. 'concurrent:all',
  152. ])
  153. grunt.registerTask('run:all', 'run')
  154. grunt.registerTask('help', 'Display this help list', 'availabletasks')
  155. grunt.registerTask('default', 'run')
  156. grunt.registerTask(
  157. 'check:redis',
  158. 'Check that redis is installed and running',
  159. function () {
  160. return Helpers.checkRedisConnect(this.async())
  161. }
  162. )
  163. grunt.registerTask(
  164. 'check:mongo',
  165. 'Check that mongo is installed',
  166. function () {
  167. return Helpers.checkMongoConnect(this.async())
  168. }
  169. )
  170. grunt.registerTask(
  171. 'check',
  172. 'Check that you have the required dependencies installed',
  173. ['check:redis', 'check:mongo', 'check:make']
  174. )
  175. grunt.registerTask('check:make', 'Check that make is installed', function () {
  176. return Helpers.checkMake(this.async())
  177. })
  178. return (Helpers = {
  179. installService(service, callback) {
  180. if (callback == null) {
  181. callback = function (error) {}
  182. }
  183. console.log(`Installing ${service.name}`)
  184. return Helpers.cloneGitRepo(service, function (error) {
  185. if (error != null) {
  186. return callback(error)
  187. } else {
  188. return callback()
  189. }
  190. })
  191. },
  192. cloneGitRepo(service, callback) {
  193. if (callback == null) {
  194. callback = function (error) {}
  195. }
  196. const repo_src = service.repo
  197. const dir = service.name
  198. if (!fs.existsSync(dir)) {
  199. const proc = spawn('git', ['clone', repo_src, dir], {
  200. stdio: 'inherit',
  201. })
  202. return proc.on('close', () =>
  203. Helpers.checkoutVersion(service, callback)
  204. )
  205. } else {
  206. console.log(`${dir} already installed, skipping.`)
  207. return callback()
  208. }
  209. },
  210. checkoutVersion(service, callback) {
  211. if (callback == null) {
  212. callback = function (error) {}
  213. }
  214. const dir = service.name
  215. grunt.log.write(`checking out ${service.name} ${service.version}`)
  216. const proc = spawn('git', ['checkout', service.version], {
  217. stdio: 'inherit',
  218. cwd: dir,
  219. })
  220. return proc.on('close', () => callback())
  221. },
  222. postinstallMessage(callback) {
  223. if (callback == null) {
  224. callback = function (error) {}
  225. }
  226. grunt.log.write(`\
  227. Services cloned:
  228. ${(() => {
  229. const result6 = []
  230. for (service of Array.from(SERVICES)) {
  231. result6.push(service.name)
  232. }
  233. return result6
  234. })()}
  235. To install services run:
  236. $ source bin/install-services
  237. This will install the required node versions and run \`npm install\` for each service.
  238. See https://github.com/sharelatex/sharelatex/pull/549 for more info.\
  239. `)
  240. return callback()
  241. },
  242. checkMake(callback) {
  243. if (callback == null) {
  244. callback = function (error) {}
  245. }
  246. grunt.log.write('Checking make is installed... ')
  247. return exec('make --version', function (error, stdout, stderr) {
  248. if (error != null && error.message.match('not found')) {
  249. grunt.log.error('FAIL.')
  250. grunt.log.errorlns(`\
  251. Either make is not installed or is not in your path.
  252. On Ubuntu you can install make with:
  253. sudo apt-get install build-essential
  254. \
  255. `)
  256. return callback(error)
  257. } else if (error != null) {
  258. return callback(error)
  259. } else {
  260. grunt.log.write('OK.')
  261. return callback()
  262. }
  263. })
  264. },
  265. checkMongoConnect(callback) {
  266. if (callback == null) {
  267. callback = function (error) {}
  268. }
  269. grunt.log.write('Checking can connect to mongo')
  270. const mongojs = require('mongojs')
  271. const db = mongojs(settings.mongo.url, ['tags'])
  272. db.runCommand({ ping: 1 }, function (err, res) {
  273. if (!err && res.ok) {
  274. grunt.log.write('OK.')
  275. }
  276. return callback()
  277. })
  278. return db.on('error', function (err) {
  279. err = 'Can not connect to mongodb'
  280. grunt.log.error('FAIL.')
  281. grunt.log.errorlns(`\
  282. !!!!!!!!!!!!!! MONGO ERROR !!!!!!!!!!!!!!
  283. ShareLaTeX can not talk to the mongodb instance
  284. Check the mongodb instance is running and accessible on env var SHARELATEX_MONGO_URL
  285. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\
  286. `)
  287. throw new Error('Can not connect to Mongodb')
  288. return callback(err)
  289. })
  290. },
  291. checkRedisConnect(callback) {
  292. if (callback == null) {
  293. callback = function (error) {}
  294. }
  295. grunt.log.write('Checking can connect to redis\n')
  296. const rclient = require('redis').createClient(settings.redis.web)
  297. rclient.ping(function (err, res) {
  298. if (err == null) {
  299. grunt.log.write('OK.')
  300. } else {
  301. throw new Error('Can not connect to redis')
  302. }
  303. return callback()
  304. })
  305. const errorHandler = _.once(function (err) {
  306. err = 'Can not connect to redis'
  307. grunt.log.error('FAIL.')
  308. grunt.log.errorlns(`\
  309. !!!!!!!!!!!!!! REDIS ERROR !!!!!!!!!!!!!!
  310. ShareLaTeX can not talk to the redis instance
  311. Check the redis instance is running and accessible on env var SHARELATEX_REDIS_HOST
  312. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\
  313. `)
  314. throw new Error('Can not connect to redis')
  315. return callback(err)
  316. })
  317. return rclient.on('error', errorHandler)
  318. },
  319. })
  320. }
  321. function __guard__(value, transform) {
  322. return typeof value !== 'undefined' && value !== null
  323. ? transform(value)
  324. : undefined
  325. }