2014-02-12 05:21:20 -05:00
|
|
|
fs = require "fs"
|
|
|
|
spawn = require("child_process").spawn
|
2014-02-22 07:08:03 -05:00
|
|
|
exec = require("child_process").exec
|
2014-02-12 07:11:58 -05:00
|
|
|
rimraf = require "rimraf"
|
2014-02-12 07:15:47 -05:00
|
|
|
Path = require "path"
|
2014-02-22 07:08:03 -05:00
|
|
|
semver = require "semver"
|
2014-02-22 10:02:21 -05:00
|
|
|
knox = require "knox"
|
2014-02-12 05:21:20 -05:00
|
|
|
|
2014-02-12 06:18:25 -05:00
|
|
|
SERVICES = [{
|
|
|
|
name: "web"
|
2014-02-21 09:16:36 -05:00
|
|
|
repo: "https://github.com/sharelatex/web-sharelatex.git"
|
2014-02-12 06:18:25 -05:00
|
|
|
}, {
|
|
|
|
name: "document-updater"
|
2014-02-21 09:16:36 -05:00
|
|
|
repo: "https://github.com/sharelatex/document-updater-sharelatex.git"
|
2014-02-13 07:37:47 -05:00
|
|
|
}, {
|
|
|
|
name: "clsi"
|
2014-02-21 09:16:36 -05:00
|
|
|
repo: "https://github.com/sharelatex/clsi-sharelatex.git"
|
2014-02-14 12:30:43 -05:00
|
|
|
}, {
|
|
|
|
name: "filestore"
|
2014-02-21 09:16:36 -05:00
|
|
|
repo: "https://github.com/sharelatex/filestore-sharelatex.git"
|
2014-03-05 08:39:21 -05:00
|
|
|
}, {
|
2014-02-26 11:24:01 -05:00
|
|
|
name: "track-changes"
|
|
|
|
repo: "https://github.com/sharelatex/track-changes-sharelatex.git"
|
2014-04-29 07:05:18 -04:00
|
|
|
}, {
|
|
|
|
name: "docstore"
|
|
|
|
repo: "https://github.com/sharelatex/docstore-sharelatex.git"
|
2014-03-05 08:39:21 -05:00
|
|
|
}]
|
2014-02-12 05:21:20 -05:00
|
|
|
|
2014-02-08 09:44:47 -05:00
|
|
|
module.exports = (grunt) ->
|
|
|
|
grunt.loadNpmTasks 'grunt-bunyan'
|
|
|
|
grunt.loadNpmTasks 'grunt-execute'
|
|
|
|
grunt.loadNpmTasks 'grunt-available-tasks'
|
|
|
|
grunt.loadNpmTasks 'grunt-concurrent'
|
|
|
|
|
2014-02-13 07:37:47 -05:00
|
|
|
execute = {}
|
2014-03-05 08:39:21 -05:00
|
|
|
for service in SERVICES
|
2014-02-13 07:37:47 -05:00
|
|
|
execute[service.name] =
|
|
|
|
src: "#{service.name}/app.js"
|
|
|
|
|
2014-02-08 09:44:47 -05:00
|
|
|
grunt.initConfig
|
2014-02-13 07:37:47 -05:00
|
|
|
execute: execute
|
2014-02-08 09:44:47 -05:00
|
|
|
|
|
|
|
concurrent:
|
|
|
|
all:
|
2014-02-13 07:37:47 -05:00
|
|
|
tasks: ("run:#{service.name}" for service in SERVICES)
|
2014-02-08 09:44:47 -05:00
|
|
|
options:
|
2014-02-13 07:37:47 -05:00
|
|
|
limit: SERVICES.length
|
2014-02-08 09:44:47 -05:00
|
|
|
logConcurrentOutput: true
|
|
|
|
|
|
|
|
availabletasks:
|
|
|
|
tasks:
|
|
|
|
options:
|
2014-02-12 05:21:20 -05:00
|
|
|
filter: 'exclude',
|
|
|
|
tasks: [
|
|
|
|
'concurrent'
|
|
|
|
'execute'
|
|
|
|
'bunyan'
|
|
|
|
'availabletasks'
|
|
|
|
]
|
|
|
|
groups:
|
|
|
|
"Run tasks": [
|
|
|
|
"run"
|
|
|
|
"run:all"
|
|
|
|
"default"
|
2014-02-13 07:37:47 -05:00
|
|
|
].concat ("run:#{service.name}" for service in SERVICES)
|
2014-02-12 05:21:20 -05:00
|
|
|
"Misc": [
|
|
|
|
"help"
|
|
|
|
]
|
2014-02-23 06:53:46 -05:00
|
|
|
"Install tasks": ("install:#{service.name}" for service in SERVICES).concat(["install:all", "install", "install:config"])
|
2014-02-12 07:11:58 -05:00
|
|
|
"Update tasks": ("update:#{service.name}" for service in SERVICES).concat(["update:all", "update"])
|
|
|
|
"Config tasks": ["install:config"]
|
2014-02-23 06:53:46 -05:00
|
|
|
"Checks": ["check", "check:redis", "check:latexmk", "check:s3", "check:make"]
|
2014-02-08 09:44:47 -05:00
|
|
|
|
2014-03-05 08:39:21 -05:00
|
|
|
for service in SERVICES
|
2014-02-12 06:18:25 -05:00
|
|
|
do (service) ->
|
|
|
|
grunt.registerTask "install:#{service.name}", "Download and set up the #{service.name} service", () ->
|
|
|
|
done = @async()
|
|
|
|
Helpers.installService(service.repo, service.name, done)
|
|
|
|
grunt.registerTask "update:#{service.name}", "Checkout and update the #{service.name} service", () ->
|
|
|
|
done = @async()
|
|
|
|
Helpers.updateService(service.name, done)
|
2014-02-12 07:15:47 -05:00
|
|
|
grunt.registerTask "run:#{service.name}", "Run the ShareLaTeX #{service.name} service", ["bunyan", "execute:#{service.name}"]
|
2014-02-12 06:18:25 -05:00
|
|
|
|
2014-02-23 06:45:08 -05:00
|
|
|
grunt.registerTask 'install:config', "Copy the example config into the real config", () ->
|
|
|
|
Helpers.installConfig @async()
|
|
|
|
grunt.registerTask 'install:all', "Download and set up all ShareLaTeX services",
|
2014-02-23 06:53:46 -05:00
|
|
|
["check:make"].concat(
|
|
|
|
("install:#{service.name}" for service in SERVICES)
|
|
|
|
).concat(["install:config"])
|
2014-02-12 06:24:13 -05:00
|
|
|
grunt.registerTask 'install', 'install:all'
|
2014-02-23 06:45:08 -05:00
|
|
|
grunt.registerTask 'update:all', "Checkout and update all ShareLaTeX services",
|
2014-02-23 06:53:46 -05:00
|
|
|
["check:make"].concat(
|
|
|
|
("update:#{service.name}" for service in SERVICES)
|
|
|
|
)
|
2014-02-12 06:24:13 -05:00
|
|
|
grunt.registerTask 'update', 'update:all'
|
2014-02-08 09:44:47 -05:00
|
|
|
grunt.registerTask 'run', "Run all of the sharelatex processes", ['concurrent:all']
|
2014-02-08 16:52:45 -05:00
|
|
|
grunt.registerTask 'run:all', 'run'
|
2014-02-08 09:44:47 -05:00
|
|
|
|
2014-02-12 06:24:13 -05:00
|
|
|
grunt.registerTask 'help', 'Display this help list', 'availabletasks'
|
2014-02-08 09:44:47 -05:00
|
|
|
grunt.registerTask 'default', 'run'
|
|
|
|
|
2014-02-22 07:08:03 -05:00
|
|
|
grunt.registerTask "check:redis", "Check that redis is installed and running", () ->
|
|
|
|
Helpers.checkRedis @async()
|
|
|
|
grunt.registerTask "check:latexmk", "Check that latexmk is installed", () ->
|
|
|
|
Helpers.checkLatexmk @async()
|
2014-02-22 10:02:21 -05:00
|
|
|
grunt.registerTask "check:s3", "Check that Amazon S3 credentials are configured", () ->
|
|
|
|
Helpers.checkS3 @async()
|
2014-03-04 09:35:49 -05:00
|
|
|
grunt.registerTask "check:fs", "Check that local filesystem options are configured", () ->
|
|
|
|
Helpers.checkFS @async()
|
2014-02-23 06:53:46 -05:00
|
|
|
grunt.registerTask "check:make", "Check that make is installed", () ->
|
|
|
|
Helpers.checkMake @async()
|
2014-03-04 09:35:49 -05:00
|
|
|
grunt.registerTask "check", "Check that you have the required dependencies installed", ["check:redis", "check:latexmk", "check:s3", "check:fs"]
|
2014-02-22 07:08:03 -05:00
|
|
|
|
2014-05-15 12:45:24 -04:00
|
|
|
grunt.registerTask "build_deb", "Build an installable .deb file from the current directory", () ->
|
|
|
|
Helpers.buildDeb @async()
|
|
|
|
|
2014-02-22 07:08:03 -05:00
|
|
|
Helpers =
|
|
|
|
installService: (repo_src, dir, callback = (error) ->) ->
|
|
|
|
Helpers.cloneGitRepo repo_src, dir, (error) ->
|
2014-02-12 05:21:20 -05:00
|
|
|
return callback(error) if error?
|
2014-02-22 07:08:03 -05:00
|
|
|
Helpers.installNpmModules dir, (error) ->
|
2014-02-12 05:21:20 -05:00
|
|
|
return callback(error) if error?
|
2014-02-22 07:08:03 -05:00
|
|
|
Helpers.runGruntInstall dir, (error) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
callback()
|
2014-02-12 05:21:20 -05:00
|
|
|
|
2014-02-22 07:08:03 -05:00
|
|
|
updateService: (dir, callback = (error) ->) ->
|
|
|
|
Helpers.updateGitRepo dir, (error) ->
|
2014-02-12 05:21:20 -05:00
|
|
|
return callback(error) if error?
|
2014-02-22 07:08:03 -05:00
|
|
|
Helpers.installNpmModules dir, (error) ->
|
2014-02-12 05:21:20 -05:00
|
|
|
return callback(error) if error?
|
2014-02-22 07:08:03 -05:00
|
|
|
Helpers.runGruntInstall dir, (error) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
callback()
|
|
|
|
|
|
|
|
cloneGitRepo: (repo_src, dir, callback = (error) ->) ->
|
|
|
|
if !fs.existsSync(dir)
|
|
|
|
proc = spawn "git", ["clone", repo_src, dir], stdio: "inherit"
|
|
|
|
proc.on "close", () ->
|
|
|
|
callback()
|
|
|
|
else
|
|
|
|
console.log "#{dir} already installed, skipping."
|
|
|
|
callback()
|
|
|
|
|
|
|
|
updateGitRepo: (dir, callback = (error) ->) ->
|
|
|
|
proc = spawn "git", ["checkout", "master"], cwd: dir, stdio: "inherit"
|
|
|
|
proc.on "close", () ->
|
|
|
|
proc = spawn "git", ["pull"], cwd: dir, stdio: "inherit"
|
|
|
|
proc.on "close", () ->
|
2014-02-12 05:21:20 -05:00
|
|
|
callback()
|
|
|
|
|
2014-02-22 07:08:03 -05:00
|
|
|
installNpmModules: (dir, callback = (error) ->) ->
|
|
|
|
proc = spawn "npm", ["install"], stdio: "inherit", cwd: dir
|
2014-02-12 05:21:20 -05:00
|
|
|
proc.on "close", () ->
|
|
|
|
callback()
|
2014-02-22 07:08:03 -05:00
|
|
|
|
2014-02-23 06:45:08 -05:00
|
|
|
installConfig: (callback = (error) ->) ->
|
|
|
|
if !fs.existsSync("config/settings.development.coffee")
|
|
|
|
grunt.log.writeln "Copying example config into config/settings.development.coffee"
|
|
|
|
exec "cp config/settings.development.coffee.example config/settings.development.coffee", (error, stdout, stderr) ->
|
|
|
|
callback(error)
|
|
|
|
else
|
|
|
|
grunt.log.writeln "Config file already exists. Skipping."
|
|
|
|
callback()
|
|
|
|
|
2014-02-22 07:08:03 -05:00
|
|
|
runGruntInstall: (dir, callback = (error) ->) ->
|
|
|
|
proc = spawn "grunt", ["install"], stdio: "inherit", cwd: dir
|
2014-02-12 05:21:20 -05:00
|
|
|
proc.on "close", () ->
|
|
|
|
callback()
|
|
|
|
|
2014-02-22 07:08:03 -05:00
|
|
|
checkRedis: (callback = (error) ->) ->
|
|
|
|
grunt.log.write "Checking Redis is running... "
|
|
|
|
exec "redis-cli info", (error, stdout, stderr) ->
|
|
|
|
if error? and error.message.match("Could not connect")
|
|
|
|
grunt.log.error "FAIL. Redis is not running"
|
2014-02-22 10:02:21 -05:00
|
|
|
return callback(error)
|
2014-02-22 07:08:03 -05:00
|
|
|
else if error?
|
|
|
|
return callback(error)
|
|
|
|
else
|
|
|
|
m = stdout.match(/redis_version:(.*)/)
|
|
|
|
if !m?
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.error "Unknown redis version"
|
2014-02-22 10:02:21 -05:00
|
|
|
error = new Error("Unknown redis version")
|
2014-02-22 07:08:03 -05:00
|
|
|
else
|
|
|
|
version = m[1]
|
2014-04-09 05:56:00 -04:00
|
|
|
if semver.gte(version, "2.6.12")
|
2014-02-22 07:08:03 -05:00
|
|
|
grunt.log.writeln "OK."
|
|
|
|
grunt.log.writeln "Running Redis version #{version}"
|
|
|
|
else
|
|
|
|
grunt.log.error "FAIL."
|
2014-04-09 05:56:00 -04:00
|
|
|
grunt.log.error "Redis version is too old (#{version}). Must be 2.6.12 or greater."
|
|
|
|
error = new Error("Redis version is too old (#{version}). Must be 2.6.12 or greater.")
|
2014-02-22 10:02:21 -05:00
|
|
|
callback(error)
|
2014-02-22 07:08:03 -05:00
|
|
|
|
|
|
|
checkLatexmk: (callback = (error) ->) ->
|
|
|
|
grunt.log.write "Checking latexmk is installed... "
|
2014-02-22 09:08:49 -05:00
|
|
|
exec "latexmk --version", (error, stdout, stderr) ->
|
2014-02-22 07:08:03 -05:00
|
|
|
if error? and error.message.match("command not found")
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.errorlns """
|
|
|
|
Either latexmk is not installed or is not in your PATH.
|
|
|
|
|
|
|
|
latexmk comes with TexLive 2013, and must be a version from 2013 or later.
|
|
|
|
This is a not a fatal error, but compiling will not work without latexmk
|
|
|
|
"""
|
2014-02-22 10:02:21 -05:00
|
|
|
return callback(error)
|
2014-02-22 07:08:03 -05:00
|
|
|
else if error?
|
|
|
|
return callback(error)
|
|
|
|
else
|
|
|
|
m = stdout.match(/Version (.*)/)
|
|
|
|
if !m?
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.error "Unknown latexmk version"
|
2014-02-22 10:02:21 -05:00
|
|
|
error = new Error("Unknown latexmk version")
|
2014-02-22 07:08:03 -05:00
|
|
|
else
|
|
|
|
version = m[1]
|
2014-02-22 09:08:49 -05:00
|
|
|
if semver.gte(version + ".0", "4.39.0")
|
2014-02-22 07:08:03 -05:00
|
|
|
grunt.log.writeln "OK."
|
|
|
|
grunt.log.writeln "Running latexmk version #{version}"
|
|
|
|
else
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.errorlns """
|
|
|
|
latexmk version is too old (#{version}). Must be 4.39 or greater.
|
|
|
|
This is a not a fatal error, but compiling will not work without latexmk
|
|
|
|
"""
|
2014-02-22 10:02:21 -05:00
|
|
|
error = new Error("latexmk is too old")
|
|
|
|
callback(error)
|
|
|
|
|
|
|
|
checkS3: (callback = (error) ->) ->
|
2014-02-24 14:08:08 -05:00
|
|
|
Settings = require "settings-sharelatex"
|
2014-03-04 09:35:49 -05:00
|
|
|
if Settings.filestore.backend==""
|
|
|
|
grunt.log.writeln "No backend specified. Assuming Amazon S3"
|
|
|
|
Settings.filestore.backend = "s3"
|
|
|
|
if Settings.filestore.backend=="s3"
|
|
|
|
grunt.log.write "Checking S3 credentials... "
|
|
|
|
try
|
|
|
|
client = knox.createClient({
|
|
|
|
key: Settings.filestore.s3.key
|
|
|
|
secret: Settings.filestore.s3.secret
|
|
|
|
bucket: Settings.filestore.stores.user_files
|
|
|
|
})
|
|
|
|
catch e
|
2014-02-22 10:02:21 -05:00
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.errorlns """
|
2014-03-05 07:52:04 -05:00
|
|
|
Please configure your Amazon S3 credentials in config/settings.development.coffee
|
2014-03-04 09:35:49 -05:00
|
|
|
|
|
|
|
Amazon S3 (Simple Storage Service) is a cloud storage service provided by
|
|
|
|
Amazon. ShareLaTeX uses S3 for storing binary files like images. You can
|
|
|
|
sign up for an account and find out more at:
|
|
|
|
|
|
|
|
http://aws.amazon.com/s3/
|
|
|
|
|
2014-02-22 10:02:21 -05:00
|
|
|
"""
|
2014-03-04 09:35:49 -05:00
|
|
|
return callback()
|
|
|
|
client.getFile "does-not-exist", (error, response) ->
|
|
|
|
unless response? and response.statusCode == 404
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.errorlns """
|
|
|
|
Could not connect to Amazon S3. Please check your credentials.
|
|
|
|
"""
|
|
|
|
else
|
|
|
|
grunt.log.write "OK."
|
|
|
|
callback()
|
|
|
|
else
|
|
|
|
grunt.log.writeln "Filestore other than S3 configured. Not checking S3."
|
2014-02-22 07:08:03 -05:00
|
|
|
callback()
|
2014-02-12 05:21:20 -05:00
|
|
|
|
2014-03-04 09:35:49 -05:00
|
|
|
checkFS: (callback = (error) ->) ->
|
|
|
|
Settings = require "settings-sharelatex"
|
|
|
|
if Settings.filestore.backend=="fs"
|
|
|
|
grunt.log.write "Checking FS configuration..."
|
|
|
|
fs = require("fs")
|
|
|
|
fs.exists Settings.filestore.stores.user_files, (exists) ->
|
|
|
|
if exists
|
|
|
|
grunt.log.write "OK."
|
|
|
|
else
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.errorlns """
|
|
|
|
Could not find directory "#{Settings.filestore.stores.user_files}".
|
|
|
|
Please check your configuration.
|
|
|
|
"""
|
|
|
|
else
|
|
|
|
grunt.log.writeln "Filestore other than FS configured. Not checking FS."
|
|
|
|
callback()
|
|
|
|
|
|
|
|
|
2014-02-23 06:53:46 -05:00
|
|
|
checkMake: (callback = (error) ->) ->
|
|
|
|
grunt.log.write "Checking make is installed... "
|
|
|
|
exec "make --version", (error, stdout, stderr) ->
|
|
|
|
if error? and error.message.match("command not found")
|
|
|
|
grunt.log.error "FAIL."
|
|
|
|
grunt.log.errorlns """
|
|
|
|
Either make is not installed or is not in your path.
|
|
|
|
|
|
|
|
On Ubuntu you can install make with:
|
|
|
|
|
|
|
|
sudo apt-get install build-essential
|
|
|
|
|
|
|
|
"""
|
|
|
|
return callback(error)
|
|
|
|
else if error?
|
|
|
|
return callback(error)
|
|
|
|
else
|
|
|
|
grunt.log.write "OK."
|
|
|
|
return callback()
|
|
|
|
|
2014-05-15 12:45:24 -04:00
|
|
|
buildDeb: (callback = (error) ->) ->
|
|
|
|
# TODO: filestore uses local 'uploads' directory, not configurable in settings
|
|
|
|
command = ["fpm", "-s", "dir", "-t", "deb", "-n", "sharelatex", "-v", "0.0.1", "--verbose"]
|
|
|
|
command.push(
|
|
|
|
"--maintainer", "'ShareLaTeX <team@sharelatex.com>'"
|
|
|
|
"--config-files", "/etc/sharelatex/settings.coffee",
|
|
|
|
"--directories", "/var/data/sharelatex"
|
|
|
|
"--directories", "/var/log/sharelatex"
|
|
|
|
)
|
|
|
|
|
|
|
|
command.push(
|
|
|
|
"--depends", "'redis-server > 2.6.12'"
|
|
|
|
"--depends", "'mongodb-10gen > 2.4.0'"
|
|
|
|
"--depends", "'nodejs > 0.10.0'"
|
|
|
|
)
|
|
|
|
|
|
|
|
template = fs.readFileSync("package/upstart/sharelatex-template").toString()
|
|
|
|
for service in SERVICES
|
|
|
|
fs.writeFileSync "package/upstart/sharelatex-#{service.name}", template.replace(/SERVICE/g, service.name)
|
|
|
|
command.push(
|
|
|
|
"--deb-upstart", "package/upstart/sharelatex-#{service.name}"
|
|
|
|
)
|
|
|
|
|
|
|
|
after_install_script = """
|
|
|
|
#!/bin/sh
|
|
|
|
sudo adduser --system --group --home /var/www/sharelatex --no-create-home sharelatex
|
|
|
|
|
|
|
|
mkdir -p /var/log/sharelatex
|
|
|
|
chown sharelatex:sharelatex /var/log/sharelatex
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
for dir in ["user_files", "uploads", "compiles", "cache", "dump"]
|
|
|
|
after_install_script += """
|
|
|
|
mkdir -p /var/data/sharelatex/#{dir}
|
|
|
|
chown sharelatex:sharelatex /var/data/sharelatex/#{dir}
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
for service in SERVICES
|
|
|
|
after_install_script += "service sharelatex-#{service.name} restart\n"
|
|
|
|
fs.writeFileSync "package/scripts/after_install.sh", after_install_script
|
|
|
|
command.push("--after-install", "package/scripts/after_install.sh")
|
|
|
|
|
|
|
|
command.push("--exclude", "'**/.git'")
|
|
|
|
for path in ["filestore/user_files", "filestore/uploads", "clsi/cache", "clsi/compiles"]
|
|
|
|
command.push "--exclude", path
|
|
|
|
|
|
|
|
for service in SERVICES
|
|
|
|
command.push "#{service.name}=/var/www/sharelatex/"
|
|
|
|
|
|
|
|
command.push(
|
|
|
|
"package/config/settings.coffee=/etc/sharelatex/settings.coffee"
|
|
|
|
)
|
|
|
|
console.log command.join(" ")
|
|
|
|
exec command.join(" "), (error, stdout, stderr) ->
|
|
|
|
return callback(error) if error?
|
|
|
|
console.log stdout
|
|
|
|
console.error stderr if stderr?
|
|
|
|
callback()
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-02-23 06:53:46 -05:00
|
|
|
|
|
|
|
|
2014-02-12 07:11:58 -05:00
|
|
|
|