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-08-15 10:05:07 -04:00
crypto = require " crypto "
async = require " async "
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-08-15 07:00:39 -04:00
} , {
name: " chat "
repo: " https://github.com/sharelatex/chat-sharelatex.git "
} , {
name: " tags "
repo: " https://github.com/sharelatex/tags-sharelatex.git "
2014-08-15 07:39:55 -04:00
} , {
name: " spelling "
repo: " https://github.com/sharelatex/spelling-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-08-15 10:05:07 -04:00
" Install tasks " : ( " install: #{ service . name } " for service in SERVICES ) . concat ( [ " install:all " , " install " , " install:dirs " , " 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 ( )
2014-08-15 10:05:07 -04:00
grunt . registerTask ' install:dirs ' , " Copy the example config into the real config " , () ->
Helpers . createDataDirs @ async ( )
2014-02-23 06:45:08 -05:00
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-08-15 07:57:06 -04:00
grunt . registerTask " check:aspell " , " Check that aspell is installed " , () ->
Helpers . checkAspell @ async ( )
2014-02-23 06:53:46 -05:00
grunt . registerTask " check:make " , " Check that make is installed " , () ->
Helpers . checkMake @ async ( )
2014-08-15 07:57:06 -04:00
grunt . registerTask " check " , " Check that you have the required dependencies installed " , [ " check:redis " , " check:latexmk " , " check:s3 " , " check:fs " , " check:aspell " ]
2014-02-22 07:08:03 -05:00
2014-08-18 05:46:42 -04:00
grunt . registerTask " build:deb " , " Build an installable .deb file from the current directory " , () ->
2014-05-15 12:45:24 -04:00
Helpers . buildDeb @ async ( )
2014-08-18 05:46:42 -04:00
grunt . registerTask " build:upstart_scripts " , " Create upstart scripts for each service " , () ->
Helpers . buildUpstartScripts ( )
2014-08-18 10:13:48 -04:00
2014-05-15 12:45:24 -04:00
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-08-15 10:05:07 -04:00
createDataDirs: ( callback = (error) -> ) ->
DIRS = [
" tmp/dumpFolder "
" tmp/uploads "
" data/user_files "
" data/compiles "
" data/cache "
]
jobs = [ ]
for dir in DIRS
do (dir) ->
jobs . push (callback) ->
path = Path . join ( __dirname , dir )
grunt . log . writeln " Ensuring ' #{ path } ' exists "
exec " mkdir -p #{ path } " , callback
async . series jobs , callback
2014-02-22 07:08:03 -05:00
2014-02-23 06:45:08 -05:00
installConfig: ( callback = (error) -> ) ->
2014-08-15 10:05:07 -04:00
src = " config/settings.development.coffee.example "
dest = " config/settings.development.coffee "
if ! fs . existsSync ( dest )
grunt . log . writeln " Creating config at #{ dest } "
config = fs . readFileSync ( src ) . toString ( )
config = config . replace / CRYPTO_RANDOM / g , () ->
crypto . randomBytes ( 64 ) . toString ( " hex " )
fs . writeFileSync dest , config
callback ( )
2014-02-23 06:45:08 -05:00
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-08-15 08:34:57 -04:00
if error ? and error . message . match ( " not found " )
2014-02-22 07:08:03 -05:00
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 .
2014-08-15 08:44:21 -04:00
If you have already have TeXLive installed , then make sure it is
included in your PATH ( example for 64 - bit linux ) :
export PATH = $PATH : / usr / local / texlive / 2014 / bin / x86_64 - linux /
This is a not a fatal error , but compiling will not work without latexmk .
2014-02-22 07:08:03 -05:00
"""
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 )
2014-08-15 07:57:06 -04:00
checkAspell: ( callback = (error) -> ) ->
grunt . log . write " Checking aspell is installed... "
exec " aspell dump dicts " , (error, stdout, stderr) ->
2014-08-15 08:34:57 -04:00
if error ? and error . message . match ( " not found " )
2014-08-15 07:57:06 -04:00
grunt . log . error " FAIL. "
grunt . log . errorlns """
Either aspell is not installed or is not in your PATH .
On Ubuntu you can install aspell with:
2014-08-15 08:36:32 -04:00
2014-08-15 07:57:06 -04:00
sudo apt - get install aspell
Or on a mac:
2014-08-15 08:36:32 -04:00
2014-08-15 07:57:06 -04:00
brew install aspell
2014-08-15 08:44:21 -04:00
This is not a fatal error , but the spell - checker will not work without aspell
2014-08-15 07:57:06 -04:00
"""
return callback ( error )
else if error ?
return callback ( error )
else
grunt . log . writeln " OK. "
grunt . log . writeln " The following spell check dictionaries are available: "
grunt . log . write stdout
callback ( )
callback ( error )
2014-02-22 10:02:21 -05:00
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
2014-08-15 07:57:06 -04:00
grunt . log . writeln " OK. "
2014-03-04 09:35:49 -05:00
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 "
2014-08-15 08:45:26 -04:00
grunt . log . write " Checking FS configuration... "
2014-03-04 09:35:49 -05:00
fs = require ( " fs " )
fs . exists Settings . filestore . stores . user_files , (exists) ->
if exists
2014-08-15 08:45:26 -04:00
grunt . log . writeln " OK. "
2014-03-04 09:35:49 -05:00
else
grunt . log . error " FAIL. "
grunt . log . errorlns """
Could not find directory " #{ Settings . filestore . stores . user_files } " .
Please check your configuration .
"""
2014-08-15 08:46:06 -04:00
callback ( )
2014-03-04 09:35:49 -05:00
else
grunt . log . writeln " Filestore other than FS configured. Not checking FS. "
2014-08-15 08:46:06 -04:00
callback ( )
2014-03-04 09:35:49 -05:00
2014-02-23 06:53:46 -05:00
checkMake: ( callback = (error) -> ) ->
grunt . log . write " Checking make is installed... "
exec " make --version " , (error, stdout, stderr) ->
2014-08-15 08:34:57 -04:00
if error ? and error . message . match ( " not found " )
2014-02-23 06:53:46 -05:00
grunt . log . error " FAIL. "
grunt . log . errorlns """
Either make is not installed or is not in your path .
2014-08-15 08:36:32 -04:00
2014-02-23 06:53:46 -05:00
On Ubuntu you can install make with:
2014-08-15 08:36:32 -04:00
2014-02-23 06:53:46 -05:00
sudo apt - get install build - essential
2014-08-15 08:36:32 -04:00
2014-02-23 06:53:46 -05:00
"""
return callback ( error )
else if error ?
return callback ( error )
else
grunt . log . write " OK. "
return callback ( )
2014-08-18 05:46:42 -04:00
buildUpstartScripts: () ->
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 )
2014-08-18 10:13:48 -04:00
buildPackageSettingsFile: () ->
config = fs . readFileSync ( " config/settings.development.coffee.example " ) . toString ( )
config = config . replace / DATA_DIR . * / , " DATA_DIR = ' /var/lib/sharelatex/data ' "
config = config . replace / TMP_DIR . * / , " TMP_DIR = ' /var/lib/sharelatex/tmp ' "
fs . writeFileSync " package/config/settings.coffee " , config
2014-05-15 12:45:24 -04:00
buildDeb: ( callback = (error) -> ) ->
2014-05-28 07:28:43 -04:00
command = [ " -s " , " dir " , " -t " , " deb " , " -n " , " sharelatex " , " -v " , " 0.0.1 " , " --verbose " ]
2014-05-15 12:45:24 -04:00
command . push (
2014-05-28 07:28:43 -04:00
" --maintainer " , " ShareLaTeX <team@sharelatex.com> "
2014-08-19 05:41:04 -04:00
" --config-files " , " /etc/sharelatex/settings.coffee "
" --config-files " , " /etc/nginx/sites-enabled/sharelatex "
2014-08-18 05:46:42 -04:00
" --directories " , " /var/lib/sharelatex "
2014-05-15 12:45:24 -04:00
" --directories " , " /var/log/sharelatex "
)
command . push (
2014-05-28 07:28:43 -04:00
" --depends " , " redis-server > 2.6.12 "
2014-08-18 10:13:48 -04:00
" --depends " , " mongodb-org > 2.4.0 "
2014-05-28 07:28:43 -04:00
" --depends " , " nodejs > 0.10.0 "
2014-05-15 12:45:24 -04:00
)
2014-08-18 10:13:48 -04:00
@ buildPackageSettingsFile ( )
2014-05-15 12:45:24 -04:00
2014-08-18 05:46:42 -04:00
@ buildUpstartScripts ( )
2014-05-15 12:45:24 -04:00
for service in SERVICES
command . push (
" --deb-upstart " , " package/upstart/sharelatex- #{ service . name } "
)
after_install_script = """
#!/bin/sh
2014-08-18 10:13:48 -04:00
# Create random secret keys
sed - i " s/CRYPTO_RANDOM/$(cat /dev/urandom | tr -dc ' a-z0-9 ' | fold -w 64 | head -n 1)/ " / etc / sharelatex / settings . coffee
sed - i " s/CRYPTO_RANDOM/$(cat /dev/urandom | tr -dc ' a-z0-9 ' | fold -w 64 | head -n 1)/ " / etc / sharelatex / settings . coffee
2014-05-15 12:45:24 -04:00
sudo adduser - - system - - group - - home / var / www / sharelatex - - no - create - home sharelatex
mkdir - p / var / log / sharelatex
chown sharelatex : sharelatex / var / log / sharelatex
2014-08-18 10:13:48 -04:00
mkdir - p / var / lib / sharelatex
chown sharelatex : sharelatex / var / lib / sharelatex
2014-05-15 12:45:24 -04:00
"""
2014-08-18 10:13:48 -04:00
for dir in [ " data/user_files " , " tmp/uploads " , " data/compiles " , " data/cache " , " tmp/dumpFolder " ]
2014-05-15 12:45:24 -04:00
after_install_script += """
2014-08-18 05:46:42 -04:00
mkdir - p / var / lib / sharelatex / #{dir}
chown sharelatex : sharelatex / var / lib / sharelatex / #{dir}
2014-05-15 12:45:24 -04:00
"""
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 "
2014-08-19 05:41:04 -04:00
" package/nginx/sharelatex=/etc/nginx/sites-enabled/sharelatex "
2014-05-15 12:45:24 -04:00
)
2014-05-28 07:28:43 -04:00
console . log " fpm " + command . join ( " " )
proc = spawn " fpm " , command , stdio: " inherit "
2014-05-28 07:42:49 -04:00
proc . on " close " , (code) ->
if code != 0
callback ( new Error ( " exit code: #{ code } " ) )
else
callback ( )
2014-05-15 12:45:24 -04:00
2014-02-23 06:53:46 -05:00
2014-02-12 07:11:58 -05:00