Gruntfile.coffee 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. fs = require "fs"
  2. spawn = require("child_process").spawn
  3. exec = require("child_process").exec
  4. rimraf = require "rimraf"
  5. Path = require "path"
  6. semver = require "semver"
  7. knox = require "knox"
  8. SERVICES = [{
  9. name: "web"
  10. repo: "https://github.com/sharelatex/web-sharelatex.git"
  11. }, {
  12. name: "document-updater"
  13. repo: "https://github.com/sharelatex/document-updater-sharelatex.git"
  14. }, {
  15. name: "clsi"
  16. repo: "https://github.com/sharelatex/clsi-sharelatex.git"
  17. }, {
  18. name: "filestore"
  19. repo: "https://github.com/sharelatex/filestore-sharelatex.git"
  20. }, {
  21. name: "track-changes"
  22. repo: "https://github.com/sharelatex/track-changes-sharelatex.git"
  23. }, {
  24. name: "docstore"
  25. repo: "https://github.com/sharelatex/docstore-sharelatex.git"
  26. }, {
  27. name: "chat"
  28. repo: "https://github.com/sharelatex/chat-sharelatex.git"
  29. }, {
  30. name: "tags"
  31. repo: "https://github.com/sharelatex/tags-sharelatex.git"
  32. }, {
  33. name: "spelling"
  34. repo: "https://github.com/sharelatex/spelling-sharelatex.git"
  35. }]
  36. module.exports = (grunt) ->
  37. grunt.loadNpmTasks 'grunt-bunyan'
  38. grunt.loadNpmTasks 'grunt-execute'
  39. grunt.loadNpmTasks 'grunt-available-tasks'
  40. grunt.loadNpmTasks 'grunt-concurrent'
  41. execute = {}
  42. for service in SERVICES
  43. execute[service.name] =
  44. src: "#{service.name}/app.js"
  45. grunt.initConfig
  46. execute: execute
  47. concurrent:
  48. all:
  49. tasks: ("run:#{service.name}" for service in SERVICES)
  50. options:
  51. limit: SERVICES.length
  52. logConcurrentOutput: true
  53. availabletasks:
  54. tasks:
  55. options:
  56. filter: 'exclude',
  57. tasks: [
  58. 'concurrent'
  59. 'execute'
  60. 'bunyan'
  61. 'availabletasks'
  62. ]
  63. groups:
  64. "Run tasks": [
  65. "run"
  66. "run:all"
  67. "default"
  68. ].concat ("run:#{service.name}" for service in SERVICES)
  69. "Misc": [
  70. "help"
  71. ]
  72. "Install tasks": ("install:#{service.name}" for service in SERVICES).concat(["install:all", "install", "install:config"])
  73. "Update tasks": ("update:#{service.name}" for service in SERVICES).concat(["update:all", "update"])
  74. "Config tasks": ["install:config"]
  75. "Checks": ["check", "check:redis", "check:latexmk", "check:s3", "check:make"]
  76. for service in SERVICES
  77. do (service) ->
  78. grunt.registerTask "install:#{service.name}", "Download and set up the #{service.name} service", () ->
  79. done = @async()
  80. Helpers.installService(service.repo, service.name, done)
  81. grunt.registerTask "update:#{service.name}", "Checkout and update the #{service.name} service", () ->
  82. done = @async()
  83. Helpers.updateService(service.name, done)
  84. grunt.registerTask "run:#{service.name}", "Run the ShareLaTeX #{service.name} service", ["bunyan", "execute:#{service.name}"]
  85. grunt.registerTask 'install:config', "Copy the example config into the real config", () ->
  86. Helpers.installConfig @async()
  87. grunt.registerTask 'install:all', "Download and set up all ShareLaTeX services",
  88. ["check:make"].concat(
  89. ("install:#{service.name}" for service in SERVICES)
  90. ).concat(["install:config"])
  91. grunt.registerTask 'install', 'install:all'
  92. grunt.registerTask 'update:all', "Checkout and update all ShareLaTeX services",
  93. ["check:make"].concat(
  94. ("update:#{service.name}" for service in SERVICES)
  95. )
  96. grunt.registerTask 'update', 'update:all'
  97. grunt.registerTask 'run', "Run all of the sharelatex processes", ['concurrent:all']
  98. grunt.registerTask 'run:all', 'run'
  99. grunt.registerTask 'help', 'Display this help list', 'availabletasks'
  100. grunt.registerTask 'default', 'run'
  101. grunt.registerTask "check:redis", "Check that redis is installed and running", () ->
  102. Helpers.checkRedis @async()
  103. grunt.registerTask "check:latexmk", "Check that latexmk is installed", () ->
  104. Helpers.checkLatexmk @async()
  105. grunt.registerTask "check:s3", "Check that Amazon S3 credentials are configured", () ->
  106. Helpers.checkS3 @async()
  107. grunt.registerTask "check:fs", "Check that local filesystem options are configured", () ->
  108. Helpers.checkFS @async()
  109. grunt.registerTask "check:aspell", "Check that aspell is installed", () ->
  110. Helpers.checkAspell @async()
  111. grunt.registerTask "check:make", "Check that make is installed", () ->
  112. Helpers.checkMake @async()
  113. grunt.registerTask "check", "Check that you have the required dependencies installed", ["check:redis", "check:latexmk", "check:s3", "check:fs", "check:aspell"]
  114. grunt.registerTask "build_deb", "Build an installable .deb file from the current directory", () ->
  115. Helpers.buildDeb @async()
  116. Helpers =
  117. installService: (repo_src, dir, callback = (error) ->) ->
  118. Helpers.cloneGitRepo repo_src, dir, (error) ->
  119. return callback(error) if error?
  120. Helpers.installNpmModules dir, (error) ->
  121. return callback(error) if error?
  122. Helpers.runGruntInstall dir, (error) ->
  123. return callback(error) if error?
  124. callback()
  125. updateService: (dir, callback = (error) ->) ->
  126. Helpers.updateGitRepo dir, (error) ->
  127. return callback(error) if error?
  128. Helpers.installNpmModules dir, (error) ->
  129. return callback(error) if error?
  130. Helpers.runGruntInstall dir, (error) ->
  131. return callback(error) if error?
  132. callback()
  133. cloneGitRepo: (repo_src, dir, callback = (error) ->) ->
  134. if !fs.existsSync(dir)
  135. proc = spawn "git", ["clone", repo_src, dir], stdio: "inherit"
  136. proc.on "close", () ->
  137. callback()
  138. else
  139. console.log "#{dir} already installed, skipping."
  140. callback()
  141. updateGitRepo: (dir, callback = (error) ->) ->
  142. proc = spawn "git", ["checkout", "master"], cwd: dir, stdio: "inherit"
  143. proc.on "close", () ->
  144. proc = spawn "git", ["pull"], cwd: dir, stdio: "inherit"
  145. proc.on "close", () ->
  146. callback()
  147. installNpmModules: (dir, callback = (error) ->) ->
  148. proc = spawn "npm", ["install"], stdio: "inherit", cwd: dir
  149. proc.on "close", () ->
  150. callback()
  151. installConfig: (callback = (error) ->) ->
  152. if !fs.existsSync("config/settings.development.coffee")
  153. grunt.log.writeln "Copying example config into config/settings.development.coffee"
  154. exec "cp config/settings.development.coffee.example config/settings.development.coffee", (error, stdout, stderr) ->
  155. callback(error)
  156. else
  157. grunt.log.writeln "Config file already exists. Skipping."
  158. callback()
  159. runGruntInstall: (dir, callback = (error) ->) ->
  160. proc = spawn "grunt", ["install"], stdio: "inherit", cwd: dir
  161. proc.on "close", () ->
  162. callback()
  163. checkRedis: (callback = (error) ->) ->
  164. grunt.log.write "Checking Redis is running... "
  165. exec "redis-cli info", (error, stdout, stderr) ->
  166. if error? and error.message.match("Could not connect")
  167. grunt.log.error "FAIL. Redis is not running"
  168. return callback(error)
  169. else if error?
  170. return callback(error)
  171. else
  172. m = stdout.match(/redis_version:(.*)/)
  173. if !m?
  174. grunt.log.error "FAIL."
  175. grunt.log.error "Unknown redis version"
  176. error = new Error("Unknown redis version")
  177. else
  178. version = m[1]
  179. if semver.gte(version, "2.6.12")
  180. grunt.log.writeln "OK."
  181. grunt.log.writeln "Running Redis version #{version}"
  182. else
  183. grunt.log.error "FAIL."
  184. grunt.log.error "Redis version is too old (#{version}). Must be 2.6.12 or greater."
  185. error = new Error("Redis version is too old (#{version}). Must be 2.6.12 or greater.")
  186. callback(error)
  187. checkLatexmk: (callback = (error) ->) ->
  188. grunt.log.write "Checking latexmk is installed... "
  189. exec "latexmk --version", (error, stdout, stderr) ->
  190. if error? and error.message.match("not found")
  191. grunt.log.error "FAIL."
  192. grunt.log.errorlns """
  193. Either latexmk is not installed or is not in your PATH.
  194. latexmk comes with TexLive 2013, and must be a version from 2013 or later.
  195. This is a not a fatal error, but compiling will not work without latexmk
  196. """
  197. return callback(error)
  198. else if error?
  199. return callback(error)
  200. else
  201. m = stdout.match(/Version (.*)/)
  202. if !m?
  203. grunt.log.error "FAIL."
  204. grunt.log.error "Unknown latexmk version"
  205. error = new Error("Unknown latexmk version")
  206. else
  207. version = m[1]
  208. if semver.gte(version + ".0", "4.39.0")
  209. grunt.log.writeln "OK."
  210. grunt.log.writeln "Running latexmk version #{version}"
  211. else
  212. grunt.log.error "FAIL."
  213. grunt.log.errorlns """
  214. latexmk version is too old (#{version}). Must be 4.39 or greater.
  215. This is a not a fatal error, but compiling will not work without latexmk
  216. """
  217. error = new Error("latexmk is too old")
  218. callback(error)
  219. checkAspell: (callback = (error) ->) ->
  220. grunt.log.write "Checking aspell is installed... "
  221. exec "aspell dump dicts", (error, stdout, stderr) ->
  222. if error? and error.message.match("not found")
  223. grunt.log.error "FAIL."
  224. grunt.log.errorlns """
  225. Either aspell is not installed or is not in your PATH.
  226. On Ubuntu you can install aspell with:
  227. sudo apt-get install aspell
  228. Or on a mac:
  229. brew install aspell
  230. This is not a fatal error, but the spell-checker will not work
  231. without aspell
  232. """
  233. return callback(error)
  234. else if error?
  235. return callback(error)
  236. else
  237. grunt.log.writeln "OK."
  238. grunt.log.writeln "The following spell check dictionaries are available:"
  239. grunt.log.write stdout
  240. callback()
  241. callback(error)
  242. checkS3: (callback = (error) ->) ->
  243. Settings = require "settings-sharelatex"
  244. if Settings.filestore.backend==""
  245. grunt.log.writeln "No backend specified. Assuming Amazon S3"
  246. Settings.filestore.backend = "s3"
  247. if Settings.filestore.backend=="s3"
  248. grunt.log.write "Checking S3 credentials... "
  249. try
  250. client = knox.createClient({
  251. key: Settings.filestore.s3.key
  252. secret: Settings.filestore.s3.secret
  253. bucket: Settings.filestore.stores.user_files
  254. })
  255. catch e
  256. grunt.log.error "FAIL."
  257. grunt.log.errorlns """
  258. Please configure your Amazon S3 credentials in config/settings.development.coffee
  259. Amazon S3 (Simple Storage Service) is a cloud storage service provided by
  260. Amazon. ShareLaTeX uses S3 for storing binary files like images. You can
  261. sign up for an account and find out more at:
  262. http://aws.amazon.com/s3/
  263. """
  264. return callback()
  265. client.getFile "does-not-exist", (error, response) ->
  266. unless response? and response.statusCode == 404
  267. grunt.log.error "FAIL."
  268. grunt.log.errorlns """
  269. Could not connect to Amazon S3. Please check your credentials.
  270. """
  271. else
  272. grunt.log.writeln "OK."
  273. callback()
  274. else
  275. grunt.log.writeln "Filestore other than S3 configured. Not checking S3."
  276. callback()
  277. checkFS: (callback = (error) ->) ->
  278. Settings = require "settings-sharelatex"
  279. if Settings.filestore.backend=="fs"
  280. grunt.log.write "Checking FS configuration..."
  281. fs = require("fs")
  282. fs.exists Settings.filestore.stores.user_files, (exists) ->
  283. if exists
  284. grunt.log.write "OK."
  285. else
  286. grunt.log.error "FAIL."
  287. grunt.log.errorlns """
  288. Could not find directory "#{Settings.filestore.stores.user_files}".
  289. Please check your configuration.
  290. """
  291. else
  292. grunt.log.writeln "Filestore other than FS configured. Not checking FS."
  293. callback()
  294. checkMake: (callback = (error) ->) ->
  295. grunt.log.write "Checking make is installed... "
  296. exec "make --version", (error, stdout, stderr) ->
  297. if error? and error.message.match("not found")
  298. grunt.log.error "FAIL."
  299. grunt.log.errorlns """
  300. Either make is not installed or is not in your path.
  301. On Ubuntu you can install make with:
  302. sudo apt-get install build-essential
  303. """
  304. return callback(error)
  305. else if error?
  306. return callback(error)
  307. else
  308. grunt.log.write "OK."
  309. return callback()
  310. buildDeb: (callback = (error) ->) ->
  311. # TODO: filestore uses local 'uploads' directory, not configurable in settings
  312. command = ["-s", "dir", "-t", "deb", "-n", "sharelatex", "-v", "0.0.1", "--verbose"]
  313. command.push(
  314. "--maintainer", "ShareLaTeX <team@sharelatex.com>"
  315. "--config-files", "/etc/sharelatex/settings.coffee",
  316. "--directories", "/var/data/sharelatex"
  317. "--directories", "/var/log/sharelatex"
  318. )
  319. command.push(
  320. "--depends", "redis-server > 2.6.12"
  321. "--depends", "mongodb-10gen > 2.4.0"
  322. "--depends", "nodejs > 0.10.0"
  323. )
  324. template = fs.readFileSync("package/upstart/sharelatex-template").toString()
  325. for service in SERVICES
  326. fs.writeFileSync "package/upstart/sharelatex-#{service.name}", template.replace(/SERVICE/g, service.name)
  327. command.push(
  328. "--deb-upstart", "package/upstart/sharelatex-#{service.name}"
  329. )
  330. after_install_script = """
  331. #!/bin/sh
  332. sudo adduser --system --group --home /var/www/sharelatex --no-create-home sharelatex
  333. mkdir -p /var/log/sharelatex
  334. chown sharelatex:sharelatex /var/log/sharelatex
  335. """
  336. for dir in ["user_files", "uploads", "compiles", "cache", "dump"]
  337. after_install_script += """
  338. mkdir -p /var/data/sharelatex/#{dir}
  339. chown sharelatex:sharelatex /var/data/sharelatex/#{dir}
  340. """
  341. for service in SERVICES
  342. after_install_script += "service sharelatex-#{service.name} restart\n"
  343. fs.writeFileSync "package/scripts/after_install.sh", after_install_script
  344. command.push("--after-install", "package/scripts/after_install.sh")
  345. command.push("--exclude", "'**/.git'")
  346. for path in ["filestore/user_files", "filestore/uploads", "clsi/cache", "clsi/compiles"]
  347. command.push "--exclude", path
  348. for service in SERVICES
  349. command.push "#{service.name}=/var/www/sharelatex/"
  350. command.push(
  351. "package/config/settings.coffee=/etc/sharelatex/settings.coffee"
  352. )
  353. console.log "fpm " + command.join(" ")
  354. proc = spawn "fpm", command, stdio: "inherit"
  355. proc.on "close", (code) ->
  356. if code != 0
  357. callback(new Error("exit code: #{code}"))
  358. else
  359. callback()