2 ###############################################################################
3 # reddit dev environment installer
4 # --------------------------------
5 # This script installs a reddit stack suitable for development. DO NOT run this
6 # on a system that you use for other purposes as it might delete important
7 # files, truncate your databases, and otherwise do mean things to you.
9 # By default, this script will install the reddit code in the current user's
10 # home directory and all of its dependencies (including libraries and database
11 # servers) at the system level. The installed reddit will expect to be visited
12 # on the domain "reddit.local" unless specified otherwise. Configuring name
13 # resolution for the domain is expected to be done outside the installed
14 # environment (e.g. in your host machine's /etc/hosts file) and is not
15 # something this script handles.
17 # Several configuration options (listed in the "Configuration" section below)
18 # are overridable with environment variables. e.g.
20 # sudo REDDIT_DOMAIN=example.com ./install/reddit.sh
22 ###############################################################################
26 source $RUNDIR/install.cfg
29 ###############################################################################
31 ###############################################################################
32 if [[ $EUID -ne 0 ]]; then
33 echo "ERROR: Must be run with root privileges."
37 if [[ -z "$REDDIT_USER" ]]; then
38 # in a production install, you'd want the code to be owned by root and run
39 # by a less privileged user. this script is intended to build a development
40 # install, so we expect the owner to run the app and not be root.
42 ERROR: You have not specified a user. This usually means you're running this
43 script directly as root. It is not recommended to run reddit as the root user.
45 Please create a user to run reddit and set the REDDIT_USER variable
51 if [[ "amd64" != $
(dpkg
--print-architecture) ]]; then
53 ERROR: This host is running the $(dpkg --print-architecture) architecture!
55 Because of the pre-built dependencies in our PPA, and some extra picky things
56 like ID generation in liveupdate, installing reddit is only supported on amd64
62 # seriously! these checks are here for a reason. the packages from the
63 # reddit ppa aren't built for anything but trusty (14.04) right now, so
64 # if you try and use this install script on another release you're gonna
66 source /etc
/lsb-release
67 if [ "$DISTRIB_ID" != "Ubuntu" -o "$DISTRIB_RELEASE" != "14.04" ]; then
68 echo "ERROR: Only Ubuntu 14.04 is supported."
72 if [[ "2000000" -gt $
(awk '/MemTotal/{print $2}' /proc
/meminfo
) ]]; then
73 LOW_MEM_PROMPT
="reddit requires at least 2GB of memory to work properly, continue anyway? [y/n] "
74 read -er -n1 -p "$LOW_MEM_PROMPT" response
75 if [[ "$response" != "y" ]]; then
81 REDDIT_AVAILABLE_PLUGINS
=""
82 for plugin
in $REDDIT_PLUGINS; do
83 if [ -d $REDDIT_SRC/$plugin ]; then
84 if [[ -z "$REDDIT_PLUGINS" ]]; then
85 REDDIT_AVAILABLE_PLUGINS
+="$plugin"
87 REDDIT_AVAILABLE_PLUGINS
+=" $plugin"
89 echo "plugin $plugin found"
91 echo "plugin $plugin not found"
95 ###############################################################################
96 # Install prerequisites
97 ###############################################################################
99 # install primary packages
100 $RUNDIR/install_apt.sh
102 # install cassandra from datastax
103 $RUNDIR/install_cassandra.sh
106 $RUNDIR/install_zookeeper.sh
108 # install services (rabbitmq, postgres, memcached, etc.)
109 $RUNDIR/install_services.sh
111 ###############################################################################
112 # Install the reddit source repositories
113 ###############################################################################
114 if [ ! -d $REDDIT_SRC ]; then
116 chown
$REDDIT_USER $REDDIT_SRC
119 function copy_upstart
{
120 if [ -d ${1}/upstart
]; then
121 cp ${1}/upstart
/* /etc
/init
/
125 function clone_reddit_repo
{
126 local destination
=$REDDIT_SRC/${1}
127 local repository_url
=https
://github.com
/${2}.git
129 if [ ! -d $destination ]; then
130 sudo
-u $REDDIT_USER -H git clone
$repository_url $destination
133 copy_upstart
$destination
136 function clone_reddit_service_repo
{
137 clone_reddit_repo
$1 reddit
/reddit-service-
$1
140 clone_reddit_repo reddit reddit
/reddit
141 clone_reddit_repo i18n reddit
/reddit-i18n
142 clone_reddit_service_repo websockets
143 clone_reddit_service_repo activity
145 ###############################################################################
147 ###############################################################################
149 # Configure Cassandra
150 $RUNDIR/setup_cassandra.sh
152 # Configure PostgreSQL
153 $RUNDIR/setup_postgres.sh
156 $RUNDIR/setup_mcrouter.sh
159 $RUNDIR/setup_rabbitmq.sh
161 ###############################################################################
162 # Install and configure the reddit code
163 ###############################################################################
164 function install_reddit_repo
{
166 sudo
-u $REDDIT_USER python setup.py build
167 python setup.py develop
--no-deps
171 install_reddit_repo reddit
/r2
172 install_reddit_repo i18n
173 for plugin
in $REDDIT_AVAILABLE_PLUGINS; do
174 copy_upstart
$REDDIT_SRC/$plugin
175 install_reddit_repo
$plugin
177 install_reddit_repo websockets
178 install_reddit_repo activity
180 # generate binary translation files from source
181 sudo
-u $REDDIT_USER make -C $REDDIT_SRC/i18n clean all
183 # this builds static files and should be run *after* languages are installed
184 # so that the proper language-specific static files can be generated and after
185 # plugins are installed so all the static files are available.
186 pushd $REDDIT_SRC/reddit
/r2
187 sudo
-u $REDDIT_USER make clean pyx
189 plugin_str
=$
(echo -n "$REDDIT_AVAILABLE_PLUGINS" |
tr " " ,)
190 if [ ! -f development.update
]; then
191 cat > development.update
<<DEVELOPMENT
192 # after editing this file, run "make ini" to
193 # generate a new development.ini
196 # global debug flag -- displays pylons stacktrace rather than 500 page on error when true
197 # WARNING: a pylons stacktrace allows remote code execution. Make sure this is false
198 # if your server is publicly accessible.
202 disable_captcha = true
203 disable_ratelimit = true
204 disable_require_admin_otp = true
206 domain = $REDDIT_DOMAIN
207 oauth_domain = $REDDIT_DOMAIN
209 plugins = $plugin_str
211 media_provider = filesystem
212 media_fs_root = /srv/www/media
213 media_fs_base_url_http = http://%(domain)s/media/
218 chown
$REDDIT_USER development.update
220 sed -i "s/^plugins = .*$/plugins = $plugin_str/" $REDDIT_SRC/reddit
/r
2/development.update
221 sed -i "s/^domain = .*$/domain = $REDDIT_DOMAIN/" $REDDIT_SRC/reddit
/r
2/development.update
222 sed -i "s/^oauth_domain = .*$/oauth_domain = $REDDIT_DOMAIN/" $REDDIT_SRC/reddit
/r
2/development.update
225 sudo
-u $REDDIT_USER make ini
227 if [ ! -L run.ini
]; then
228 sudo
-u $REDDIT_USER ln -nsf development.ini run.ini
233 ###############################################################################
234 # some useful helper scripts
235 ###############################################################################
236 function helper-script
() {
241 helper-script
/usr
/local
/bin
/reddit-run
<<REDDITRUN
243 exec paster --plugin=r2 run $REDDIT_SRC/reddit/r2/run.ini "\$@"
246 helper-script
/usr
/local
/bin
/reddit-shell
<<REDDITSHELL
248 exec paster --plugin=r2 shell $REDDIT_SRC/reddit/r2/run.ini
251 helper-script
/usr
/local
/bin
/reddit-start
<<REDDITSTART
253 initctl emit reddit-start
256 helper-script
/usr
/local
/bin
/reddit-stop
<<REDDITSTOP
258 initctl emit reddit-stop
261 helper-script
/usr
/local
/bin
/reddit-restart
<<REDDITRESTART
263 initctl emit reddit-restart TARGET=${1:-all}
266 helper-script
/usr
/local
/bin
/reddit-flush
<<REDDITFLUSH
268 echo flush_all | nc localhost 11211
271 helper-script
/usr
/local
/bin
/reddit-serve
<<REDDITSERVE
273 exec paster serve --reload $REDDIT_SRC/reddit/r2/run.ini
276 ###############################################################################
277 # pixel and click server
278 ###############################################################################
279 mkdir
-p /var
/opt
/reddit
/
280 chown
$REDDIT_USER:$REDDIT_GROUP /var
/opt
/reddit
/
282 mkdir
-p /srv
/www
/pixel
283 chown
$REDDIT_USER:$REDDIT_GROUP /srv
/www
/pixel
284 cp $REDDIT_SRC/reddit
/r
2/r
2/public
/static
/pixel.png
/srv
/www
/pixel
286 if [ ! -f /etc
/gunicorn.d
/click.conf
]; then
287 cat > /etc
/gunicorn.d
/click.conf
<<CLICK
290 "working_dir": "$REDDIT_SRC/reddit/scripts",
291 "user": "$REDDIT_USER",
292 "group": "$REDDIT_USER",
294 "--bind=unix:/var/opt/reddit/click.sock",
296 "tracker:application",
302 service gunicorn start
304 ###############################################################################
306 ###############################################################################
308 mkdir
-p /srv
/www
/media
309 chown
$REDDIT_USER:$REDDIT_GROUP /srv
/www
/media
311 cat > /etc
/nginx
/sites-available
/reddit-media
<<MEDIA
318 alias /srv/www/media/;
323 cat > /etc
/nginx
/sites-available
/reddit-pixel
<<PIXEL
324 upstream click_server {
325 server unix:/var/opt/reddit/click.sock fail_timeout=0;
331 log_format directlog '\$remote_addr - \$remote_user [\$time_local] '
332 '"\$request_method \$request_uri \$server_protocol" \$status \$body_bytes_sent '
333 '"\$http_referer" "\$http_user_agent"';
334 access_log /var/log/nginx/traffic/traffic.log directlog;
338 rewrite ^/pixel/of_ /pixel.png;
340 add_header Last-Modified "";
341 add_header Pragma "no-cache";
344 root /srv/www/pixel/;
348 proxy_pass http://click_server;
353 cat > /etc
/nginx
/sites-available
/reddit-ssl
<<SSL
354 map \$http_upgrade \$connection_upgrade {
363 ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
364 ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
366 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
367 ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
368 ssl_prefer_server_ciphers on;
370 ssl_session_cache shared:SSL:1m
;
373 proxy_pass http
://127.0.0.1:8080;
374 proxy_set_header Host \
$http_host;
375 proxy_http_version
1.1;
376 proxy_set_header X-Forwarded-For \
$remote_addr;
377 proxy_pass_header Server
;
379 # allow websockets through if desired
380 proxy_set_header Upgrade \
$http_upgrade;
381 proxy_set_header Connection \
$connection_upgrade;
386 # remove the default nginx site that may conflict with haproxy
387 rm -rf /etc
/nginx
/sites-enabled
/default
388 # put our config in place
389 ln -nsf /etc
/nginx
/sites-available
/reddit-media
/etc
/nginx
/sites-enabled
/
390 ln -nsf /etc
/nginx
/sites-available
/reddit-pixel
/etc
/nginx
/sites-enabled
/
391 ln -nsf /etc
/nginx
/sites-available
/reddit-ssl
/etc
/nginx
/sites-enabled
/
393 # make the pixel log directory
394 mkdir
-p /var
/log
/nginx
/traffic
396 # link the ini file for the Flask click tracker
397 ln -nsf $REDDIT_SRC/reddit
/r
2/development.ini
$REDDIT_SRC/reddit
/scripts
/production.ini
399 service nginx restart
401 ###############################################################################
403 ###############################################################################
404 if [ -e /etc
/haproxy
/haproxy.cfg
]; then
405 BACKUP_HAPROXY
=$
(mktemp
/etc
/haproxy
/haproxy.cfg.XXX
)
406 echo "Backing up /etc/haproxy/haproxy.cfg to $BACKUP_HAPROXY"
407 cat /etc
/haproxy
/haproxy.cfg
> $BACKUP_HAPROXY
410 # make sure haproxy is enabled
411 cat > /etc
/default
/haproxy
<<DEFAULT
416 cat > /etc
/haproxy
/haproxy.cfg
<<HAPROXY
427 option forwardfor except 127.0.0.1
430 # make sure that requests have x-forwarded-proto: https iff tls
431 reqidel ^X-Forwarded-Proto:.*
432 acl is-ssl dst_port 8080
433 reqadd X-Forwarded-Proto:\ https if is-ssl
435 # send websockets to the websocket service
436 acl is-websocket hdr(Upgrade) -i WebSocket
437 use_backend websockets if is-websocket
439 # send media stuff to the local nginx
440 acl is-media path_beg /media/
441 use_backend media if is-media
443 # send pixel stuff to local nginx
444 acl is-pixel path_beg /pixel/
445 acl is-click path_beg /click
446 use_backend pixel if is-pixel || is-click
448 default_backend reddit
457 server app01-8001 localhost:8001 maxconn 30
465 server websockets localhost:9001 maxconn 250
474 server nginx localhost:9000 maxconn 20
483 server nginx localhost:8082 maxconn 20
486 # this will start it even if currently stopped
487 service haproxy restart
489 ###############################################################################
491 ###############################################################################
493 if [ ! -f /etc
/init
/reddit-websockets.conf
]; then
494 cat > /etc
/init
/reddit-websockets.conf
<< UPSTART_WEBSOCKETS
495 description "websockets service"
497 stop on runlevel [!2345] or reddit-restart all or reddit-restart websockets
498 start on runlevel [2345] or reddit-restart all or reddit-restart websockets
504 limit nofile 65535 65535
506 exec baseplate-serve2 --bind localhost:9001 $REDDIT_SRC/websockets/example.ini
510 service reddit-websockets restart
512 ###############################################################################
514 ###############################################################################
516 if [ ! -f /etc
/init
/reddit-activity.conf
]; then
517 cat > /etc
/init
/reddit-activity.conf
<< UPSTART_ACTIVITY
518 description "activity service"
520 stop on runlevel [!2345] or reddit-restart all or reddit-restart activity
521 start on runlevel [2345] or reddit-restart all or reddit-restart activity
527 exec baseplate-serve2 --bind localhost:9002 $REDDIT_SRC/activity/example.ini
531 service reddit-activity restart
533 ###############################################################################
535 ###############################################################################
536 if [ ! -f /etc
/gunicorn.d
/geoip.conf
]; then
537 cat > /etc
/gunicorn.d
/geoip.conf
<<GEOIP
540 "working_dir": "$REDDIT_SRC/reddit/scripts",
541 "user": "$REDDIT_USER",
542 "group": "$REDDIT_USER",
544 "--bind=127.0.0.1:5000",
546 "--limit-request-line=8190",
547 "geoip_service:application",
553 service gunicorn start
555 ###############################################################################
557 ###############################################################################
558 CONSUMER_CONFIG_ROOT
=$REDDIT_HOME/consumer-count.d
560 if [ ! -f /etc
/default
/reddit
]; then
561 cat > /etc
/default
/reddit
<<DEFAULT
562 export REDDIT_ROOT=$REDDIT_SRC/reddit/r2
563 export REDDIT_INI=$REDDIT_SRC/reddit/r2/run.ini
564 export REDDIT_USER=$REDDIT_USER
565 export REDDIT_GROUP=$REDDIT_GROUP
566 export REDDIT_CONSUMER_CONFIG=$CONSUMER_CONFIG_ROOT
567 alias wrap-job=$REDDIT_SRC/reddit/scripts/wrap-job
568 alias manage-consumers=$REDDIT_SRC/reddit/scripts/manage-consumers
572 ###############################################################################
574 ###############################################################################
575 mkdir
-p $CONSUMER_CONFIG_ROOT
577 function set_consumer_count
{
578 if [ ! -f $CONSUMER_CONFIG_ROOT/$1 ]; then
579 echo $2 > $CONSUMER_CONFIG_ROOT/$1
583 set_consumer_count search_q
0
584 set_consumer_count del_account_q
1
585 set_consumer_count scraper_q
1
586 set_consumer_count markread_q
1
587 set_consumer_count commentstree_q
1
588 set_consumer_count newcomments_q
1
589 set_consumer_count vote_link_q
1
590 set_consumer_count vote_comment_q
1
591 set_consumer_count automoderator_q
0
592 set_consumer_count butler_q
1
593 set_consumer_count author_query_q
1
594 set_consumer_count subreddit_query_q
1
595 set_consumer_count domain_query_q
1
597 chown
-R $REDDIT_USER:$REDDIT_GROUP $CONSUMER_CONFIG_ROOT/
600 ###############################################################################
601 # Start everything up
602 ###############################################################################
604 # the initial database setup should be done by one process rather than a bunch
605 # vying with eachother to get there first
606 reddit-run
-c 'print "ok done"'
608 # ok, now start everything else up
609 initctl emit reddit-stop
610 initctl emit reddit-start
612 ###############################################################################
614 ###############################################################################
615 if [ ! -f /etc
/cron.d
/reddit
]; then
616 cat > /etc
/cron.d
/reddit
<<CRON
617 0 3 * * * root /sbin/start --quiet reddit-job-update_sr_names
618 30 16 * * * root /sbin/start --quiet reddit-job-update_reddits
619 0 * * * * root /sbin/start --quiet reddit-job-update_promos
620 */5 * * * * root /sbin/start --quiet reddit-job-clean_up_hardcache
621 */2 * * * * root /sbin/start --quiet reddit-job-broken_things
622 */2 * * * * root /sbin/start --quiet reddit-job-rising
623 0 * * * * root /sbin/start --quiet reddit-job-trylater
626 * * * * * root /sbin/start --quiet reddit-job-liveupdate_activity
628 # jobs that recalculate time-limited listings (e.g. top this year)
630 */15 * * * * $REDDIT_USER $REDDIT_SRC/reddit/scripts/compute_time_listings link year "['hour', 'day', 'week', 'month', 'year']"
631 */15 * * * * $REDDIT_USER $REDDIT_SRC/reddit/scripts/compute_time_listings comment year "['hour', 'day', 'week', 'month', 'year']"
633 # disabled by default, uncomment if you need these jobs
634 #* * * * * root /sbin/start --quiet reddit-job-email
635 #0 0 * * * root /sbin/start --quiet reddit-job-update_gold_users
639 ###############################################################################
640 # Complete plugin setup, if setup.sh exists
641 ###############################################################################
642 for plugin
in $REDDIT_AVAILABLE_PLUGINS; do
643 if [ -x $REDDIT_SRC/$plugin/setup.sh
]; then
644 echo "Found setup.sh for $plugin; running setup script"
645 $REDDIT_SRC/$plugin/setup.sh
$REDDIT_SRC $REDDIT_USER
649 ###############################################################################
650 # Finished with install script
651 ###############################################################################
652 # print this out here. if vagrant's involved, it's gonna do more steps
653 # afterwards and then re-run this script but that's ok.