2 # The contents of this file are subject to the Common Public Attribution
3 # License Version 1.0. (the "License"); you may not use this file except in
4 # compliance with the License. You may obtain a copy of the License at
5 # http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
6 # License Version 1.1, but Sections 14 and 15 have been added to cover use of
7 # software over a computer network and provide for limited attribution for the
8 # Original Developer. In addition, Exhibit A has been modified to be consistent
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
13 # the specific language governing rights and limitations under the License.
15 # The Original Code is reddit.
17 # The Original Developer is the Initial Developer. The Initial Developer of
18 # the Original Code is reddit Inc.
20 # All portions of the code written by reddit are Copyright (c) 2006-2014 reddit
21 # Inc. All Rights Reserved.
22 ###############################################################################
24 ###############################################################################
25 # reddit dev environment installer
26 # --------------------------------
27 # This script installs a reddit stack suitable for development. DO NOT run this
28 # on a system that you use for other purposes as it might delete important
29 # files, truncate your databases, and otherwise do mean things to you.
31 # By default, this script will install the reddit code in the current user's
32 # home directory and all of its dependencies (including libraries and database
33 # servers) at the system level. The installed reddit will expect to be visited
34 # on the domain "reddit.local" unless specified otherwise. Configuring name
35 # resolution for the domain is expected to be done outside the installed
36 # environment (e.g. in your host machine's /etc/hosts file) and is not
37 # something this script handles.
39 # Several configuration options (listed in the "Configuration" section below)
40 # are overridable with environment variables. e.g.
42 # sudo REDDIT_DOMAIN=example.com ./install-reddit.sh
44 ###############################################################################
47 ###############################################################################
49 ###############################################################################
50 # which user to install the code for; defaults to the user invoking this script
51 REDDIT_USER
=${REDDIT_USER:-$SUDO_USER}
53 # the group to run reddit code as; must exist already
54 REDDIT_GROUP
=${REDDIT_GROUP:-nogroup}
56 # the root directory to base the install in. must exist already
57 REDDIT_HOME
=${REDDIT_HOME:-/home/$REDDIT_USER}
59 # the domain that you will connect to your reddit install with.
60 # MUST contain a . in it somewhere as browsers won't do cookies for dotless
61 # domains. an IP address will suffice if nothing else is available.
62 REDDIT_DOMAIN
=${REDDIT_DOMAIN:-reddit.local}
64 #The plugins to clone and register in the ini file
65 REDDIT_PLUGINS
=${REDDIT_PLUGINS:-meatspace about liveupdate}
67 ###############################################################################
69 ###############################################################################
70 if [[ $EUID -ne 0 ]]; then
71 echo "ERROR: Must be run with root privileges."
75 # seriously! these checks are here for a reason. the packages from the
76 # reddit ppa aren't built for anything but precise (12.04) right now, so
77 # if you try and use this install script on another release you're gonna
79 source /etc
/lsb-release
80 if [ "$DISTRIB_ID" != "Ubuntu" -o "$DISTRIB_RELEASE" != "12.04" ]; then
81 echo "ERROR: Only Ubuntu 12.04 is supported."
85 if [[ "2000000" -gt $
(awk '/MemTotal/{print $2}' /proc
/meminfo
) ]]; then
86 LOW_MEM_PROMPT
="reddit requires at least 2GB of memory to work properly, continue anyway? [y/n] "
87 read -er -n1 -p "$LOW_MEM_PROMPT" response
88 if [[ "$response" != "y" ]]; then
94 ###############################################################################
95 # Install prerequisites
96 ###############################################################################
99 # aptitude configuration
100 APTITUDE_OPTIONS
="-y"
101 export DEBIAN_FRONTEND
=noninteractive
103 # run an aptitude update to make sure python-software-properties
104 # dependencies are found
107 # add the reddit ppa for some custom packages
108 apt-get
install $APTITUDE_OPTIONS python-software-properties
109 apt-add-repository
-y ppa
:reddit
/ppa
111 # pin the ppa -- packages present in the ppa will take precedence over
112 # ones in other repositories (unless further pinning is done)
113 cat <<HERE > /etc/apt/preferences.d/reddit
115 Pin: release o=LP-PPA-reddit
119 # grab the new ppas' package listings
122 # install prerequisites
123 cat <<PACKAGES | xargs apt-get install $APTITUDE_OPTIONS
149 python-zope.interface
180 # paper over stack size issues with cassandra
181 sed -i s
/-Xss128k/-Xss228k/ /etc
/cassandra
/cassandra-env.sh
183 ###############################################################################
184 # Wait for all the services to be up
185 ###############################################################################
186 # cassandra doesn't auto-start after install
187 service cassandra start
189 # check each port for connectivity
190 echo "Waiting for services to be available, see source for port meanings..."
195 for port
in 11211 5432 5672 9160; do
196 while ! nc
-vz localhost
$port; do
201 ###############################################################################
202 # Install the reddit source repositories
203 ###############################################################################
204 if [ ! -d $REDDIT_HOME/src
]; then
205 mkdir
-p $REDDIT_HOME/src
206 chown
$REDDIT_USER $REDDIT_HOME/src
209 function clone_reddit_repo
{
210 local destination
=$REDDIT_HOME/src
/${1}
211 local repository_url
=https
://github.com
/${2}.git
213 if [ ! -d $destination ]; then
214 sudo
-u $REDDIT_USER git clone
$repository_url $destination
217 if [ -d $destination/upstart
]; then
218 cp $destination/upstart
/* /etc
/init
/
222 function clone_reddit_plugin_repo
{
223 clone_reddit_repo
$1 reddit
/reddit-plugin-
$1
226 clone_reddit_repo reddit reddit
/reddit
227 clone_reddit_repo i18n reddit
/reddit-i18n
228 for plugin
in $REDDIT_PLUGINS; do
229 clone_reddit_plugin_repo
$plugin
232 ###############################################################################
233 # Configure Cassandra
234 ###############################################################################
235 if ! echo | cassandra-cli
-h localhost
-k reddit
&> /dev
/null
; then
236 echo "create keyspace reddit;" | cassandra-cli
-h localhost
-B
239 cat <<CASS | cassandra-cli -B -h localhost -k reddit || true
240 create column family permacache with column_type = 'Standard' and
241 comparator = 'BytesType';
244 ###############################################################################
245 # Configure PostgreSQL
246 ###############################################################################
247 SQL
="SELECT COUNT(1) FROM pg_catalog.pg_database WHERE datname = 'reddit';"
248 IS_DATABASE_CREATED
=$
(sudo
-u postgres psql
-t -c "$SQL")
250 if [ $IS_DATABASE_CREATED -ne 1 ]; then
251 cat <<PGSCRIPT | sudo -u postgres psql
252 CREATE DATABASE reddit WITH ENCODING = 'utf8' TEMPLATE template0 LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8';
253 CREATE USER reddit WITH PASSWORD 'password';
257 sudo
-u postgres psql reddit
< $REDDIT_HOME/src
/reddit
/sql
/functions.sql
259 ###############################################################################
261 ###############################################################################
262 if ! rabbitmqctl list_vhosts |
egrep "^/$"
264 rabbitmqctl add_vhost
/
267 if ! rabbitmqctl list_users |
egrep "^reddit"
269 rabbitmqctl add_user reddit reddit
272 rabbitmqctl set_permissions
-p / reddit
".*" ".*" ".*"
274 ###############################################################################
275 # Install and configure the reddit code
276 ###############################################################################
277 function install_reddit_repo
{
278 cd $REDDIT_HOME/src
/$1
279 sudo
-u $REDDIT_USER python setup.py build
280 python setup.py develop
--no-deps
283 install_reddit_repo reddit
/r2
284 install_reddit_repo i18n
285 for plugin
in $REDDIT_PLUGINS; do
286 install_reddit_repo
$plugin
289 # generate binary translation files from source
290 cd $REDDIT_HOME/src
/i18n
/
291 sudo
-u $REDDIT_USER make clean all
293 # this builds static files and should be run *after* languages are installed
294 # so that the proper language-specific static files can be generated and after
295 # plugins are installed so all the static files are available.
296 cd $REDDIT_HOME/src
/reddit
/r2
297 sudo
-u $REDDIT_USER make clean all
299 plugin_str
=$
(echo -n "$REDDIT_PLUGINS" |
tr " " ,)
300 if [ ! -f development.update
]; then
301 cat > development.update
<<DEVELOPMENT
302 # after editing this file, run "make ini" to
303 # generate a new development.ini
309 disable_captcha = true
310 disable_ratelimit = true
311 disable_require_admin_otp = true
315 domain = $REDDIT_DOMAIN
317 plugins = $plugin_str
319 media_provider = filesystem
320 media_fs_root = /srv/www/media
321 media_fs_base_url_http = http://%(domain)s/media/
322 media_fs_base_url_https = https://%(domain)s/media/
327 chown
$REDDIT_USER development.update
329 sed -i "s/^plugins = .*$/plugins = $plugin_str/" $REDDIT_HOME/src
/reddit
/r
2/development.update
330 sed -i "s/^domain = .*$/domain = $REDDIT_DOMAIN/" $REDDIT_HOME/src
/reddit
/r
2/development.update
333 sudo
-u $REDDIT_USER make ini
335 if [ ! -L run.ini
]; then
336 sudo
-u $REDDIT_USER ln -nsf development.ini run.ini
339 ###############################################################################
340 # some useful helper scripts
341 ###############################################################################
342 function helper-script
() {
347 helper-script
/usr
/local
/bin
/reddit-run
<<REDDITRUN
349 exec paster --plugin=r2 run $REDDIT_HOME/src/reddit/r2/run.ini "\$@"
352 helper-script
/usr
/local
/bin
/reddit-shell
<<REDDITSHELL
354 exec paster --plugin=r2 shell $REDDIT_HOME/src/reddit/r2/run.ini
357 helper-script
/usr
/local
/bin
/reddit-start
<<REDDITSTART
359 initctl emit reddit-start
362 helper-script
/usr
/local
/bin
/reddit-stop
<<REDDITSTOP
364 initctl emit reddit-stop
367 helper-script
/usr
/local
/bin
/reddit-restart
<<REDDITRESTART
369 initctl emit reddit-restart TARGET=${1:-all}
372 helper-script
/usr
/local
/bin
/reddit-flush
<<REDDITFLUSH
374 echo flush_all | nc localhost 11211
377 ###############################################################################
378 # pixel and click server
379 ###############################################################################
380 mkdir
-p /var
/opt
/reddit
/
381 chown
$REDDIT_USER:$REDDIT_GROUP /var
/opt
/reddit
/
383 mkdir
-p /srv
/www
/pixel
384 chown
$REDDIT_USER:$REDDIT_GROUP /srv
/www
/pixel
385 cp $REDDIT_HOME/src
/reddit
/r
2/r
2/public
/static
/pixel.png
/srv
/www
/pixel
387 if [ ! -f /etc
/gunicorn.d
/click.conf
]; then
388 cat > /etc
/gunicorn.d
/click.conf
<<CLICK
391 "working_dir": "$REDDIT_HOME/src/reddit/scripts",
392 "user": "$REDDIT_USER",
393 "group": "$REDDIT_USER",
395 "--bind=unix:/var/opt/reddit/click.sock",
397 "tracker:application",
403 service gunicorn start
405 ###############################################################################
407 ###############################################################################
409 mkdir
-p /srv
/www
/media
410 chown
$REDDIT_USER:$REDDIT_GROUP /srv
/www
/media
412 cat > /etc
/nginx
/sites-available
/reddit-media
<<MEDIA
419 alias /srv/www/media/;
424 cat > /etc
/nginx
/sites-available
/reddit-pixel
<<PIXEL
425 upstream click_server {
426 server unix:/var/opt/reddit/click.sock fail_timeout=0;
432 log_format directlog '\$remote_addr - \$remote_user [\$time_local] '
433 '"\$request_method \$request_uri \$server_protocol" \$status \$body_bytes_sent '
434 '"\$http_referer" "\$http_user_agent"';
435 access_log /var/log/nginx/traffic/traffic.log directlog;
439 rewrite ^/pixel/of_ /pixel.png;
441 add_header Last-Modified "";
442 add_header Pragma "no-cache";
445 root /srv/www/pixel/;
449 proxy_pass http://click_server;
454 # remove the default nginx site that may conflict with haproxy
455 rm -rf /etc
/nginx
/sites-enabled
/default
456 # put our config in place
457 ln -nsf /etc
/nginx
/sites-available
/reddit-media
/etc
/nginx
/sites-enabled
/
458 ln -nsf /etc
/nginx
/sites-available
/reddit-pixel
/etc
/nginx
/sites-enabled
/
460 # make the pixel log directory
461 mkdir
-p /var
/log
/nginx
/traffic
463 # link the ini file for the Flask click tracker
464 ln -nsf $REDDIT_HOME/src
/reddit
/r
2/development.ini
$REDDIT_HOME/src
/reddit
/scripts
/production.ini
466 service nginx restart
468 ###############################################################################
470 ###############################################################################
471 if [ -e /etc
/haproxy
/haproxy.cfg
]; then
472 BACKUP_HAPROXY
=$
(mktemp
/etc
/haproxy
/haproxy.cfg.XXX
)
473 echo "Backing up /etc/haproxy/haproxy.cfg to $BACKUP_HAPROXY"
474 cat /etc
/haproxy
/haproxy.cfg
> $BACKUP_HAPROXY
477 # make sure haproxy is enabled
478 cat > /etc
/default
/haproxy
<<DEFAULT
483 cat > /etc
/haproxy
/haproxy.cfg
<<HAPROXY
494 option forwardfor except 127.0.0.1
497 # make sure that requests have x-forwarded-proto: https iff tls
498 reqidel ^X-Forwarded-Proto:.*
499 acl is-ssl dst_port 8080
500 reqadd X-Forwarded-Proto:\ https if is-ssl
502 # send websockets to sutro
503 acl is-websocket hdr(Upgrade) -i WebSocket
504 use_backend sutro if is-websocket
506 # send media stuff to the local nginx
507 acl is-media path_beg /media/
508 use_backend media if is-media
510 # send pixel stuff to local nginx
511 acl is-pixel path_beg /pixel/
512 acl is-click path_beg /click
513 use_backend pixel if is-pixel || is-click
515 default_backend reddit
524 server app01-8001 localhost:8001 maxconn 30
532 server sutro localhost:8002 maxconn 250
541 server nginx localhost:9000 maxconn 20
550 server nginx localhost:8082 maxconn 20
553 # this will start it even if currently stopped
554 service haproxy restart
556 ###############################################################################
558 ###############################################################################
559 cat > /etc
/stunnel
/stunnel.conf
<<STUNNELCONF
562 ; replace these with real certificates
563 cert = /etc/ssl/certs/ssl-cert-snakeoil.pem
564 key = /etc/ssl/private/ssl-cert-snakeoil.key
566 ; protocol version and ciphers
568 ciphers = ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:SRP-DSS-AES-256-CBC-SHA:SRP-RSA-AES-256-CBC-SHA:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA256:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-CAMELLIA256-SHA:DHE-DSS-CAMELLIA256-SHA:ECDH-RSA-AES256-GCM-SHA384:ECDH-ECDSA-AES256-GCM-SHA384:ECDH-RSA-AES256-SHA384:ECDH-ECDSA-AES256-SHA384:ECDH-RSA-AES256-SHA:ECDH-ECDSA-AES256-SHA:AES256-GCM-SHA384:AES256-SHA256:AES256-SHA:CAMELLIA256-SHA:PSK-AES256-CBC-SHA:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:SRP-DSS-AES-128-CBC-SHA:SRP-RSA-AES-128-CBC-SHA:DHE-DSS-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-DSS-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:DHE-RSA-CAMELLIA128-SHA:DHE-DSS-CAMELLIA128-SHA:ECDH-RSA-AES128-GCM-SHA256:ECDH-ECDSA-AES128-GCM-SHA256:ECDH-RSA-AES128-SHA256:ECDH-ECDSA-AES128-SHA256:ECDH-RSA-AES128-SHA:ECDH-ECDSA-AES128-SHA:AES128-GCM-SHA256:AES128-SHA256:AES128-SHA:SEED-SHA:CAMELLIA128-SHA:PSK-AES128-CBC-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5
570 options = DONT_INSERT_EMPTY_FRAGMENTS
571 options = CIPHER_SERVER_PREFERENCE
574 chroot = /var/lib/stunnel4/
580 socket = l:TCP_NODELAY=1
581 socket = r:TCP_NODELAY=1
584 output = /var/log/stunnel4/stunnel.log
592 ; this requires a patched version of stunnel which is in the reddit ppa
596 sed -i s
/ENABLED
=0/ENABLED
=1/ /etc
/default
/stunnel4
598 service stunnel4 restart
600 ###############################################################################
601 # sutro (websocket server)
602 ###############################################################################
604 if [ ! -f /etc
/sutro.ini
]; then
605 cat > /etc
/sutro.ini
<<SUTRO
607 paste.app_factory = sutro.app:make_app
609 amqp.host = localhost
612 amqp.username = reddit
613 amqp.password = reddit
615 web.allowed_origins = $REDDIT_DOMAIN
616 web.mac_secret = YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5
617 web.ping_interval = 300
623 use = egg:gunicorn#main
624 worker_class = sutro.socketserver.SutroWorker
626 worker_connections = 250
630 forward_allow_ips = 127.0.0.1
646 class = handlers.SysLogHandler
647 args = ("/dev/log", "local7")
652 format = [%(name)s] %(message)s
656 if [ ! -f /etc
/init
/sutro.conf
]; then
657 cat > /etc
/init
/sutro.conf
<< UPSTART_SUTRO
658 description "sutro websocket server"
660 stop on runlevel [!2345]
661 start on runlevel [2345]
667 limit nofile 65535 65535
669 exec gunicorn_paster /etc/sutro.ini
673 service sutro restart
675 ###############################################################################
677 ###############################################################################
678 if [ ! -f /etc
/gunicorn.d
/geoip.conf
]; then
679 cat > /etc
/gunicorn.d
/geoip.conf
<<GEOIP
682 "working_dir": "$REDDIT_HOME/src/reddit/scripts",
683 "user": "$REDDIT_USER",
684 "group": "$REDDIT_USER",
686 "--bind=127.0.0.1:5000",
688 "--limit-request-line=8190",
689 "geoip_service:application",
695 service gunicorn start
697 ###############################################################################
699 ###############################################################################
700 CONSUMER_CONFIG_ROOT
=$REDDIT_HOME/consumer-count.d
702 if [ ! -f /etc
/default
/reddit
]; then
703 cat > /etc
/default
/reddit
<<DEFAULT
704 export REDDIT_ROOT=$REDDIT_HOME/src/reddit/r2
705 export REDDIT_INI=$REDDIT_HOME/src/reddit/r2/run.ini
706 export REDDIT_USER=$REDDIT_USER
707 export REDDIT_GROUP=$REDDIT_GROUP
708 export REDDIT_CONSUMER_CONFIG=$CONSUMER_CONFIG_ROOT
709 alias wrap-job=$REDDIT_HOME/src/reddit/scripts/wrap-job
710 alias manage-consumers=$REDDIT_HOME/src/reddit/scripts/manage-consumers
714 ###############################################################################
716 ###############################################################################
717 mkdir
-p $CONSUMER_CONFIG_ROOT
719 function set_consumer_count
{
720 if [ ! -f $CONSUMER_CONFIG_ROOT/$1 ]; then
721 echo $2 > $CONSUMER_CONFIG_ROOT/$1
725 set_consumer_count log_q
0
726 set_consumer_count cloudsearch_q
0
727 set_consumer_count scraper_q
1
728 set_consumer_count markread_q
1
729 set_consumer_count commentstree_q
1
730 set_consumer_count newcomments_q
1
731 set_consumer_count vote_link_q
1
732 set_consumer_count vote_comment_q
1
734 chown
-R $REDDIT_USER:$REDDIT_GROUP $CONSUMER_CONFIG_ROOT/
736 initctl emit reddit-stop
737 initctl emit reddit-start
739 ###############################################################################
741 ###############################################################################
742 if [ ! -f /etc
/cron.d
/reddit
]; then
743 cat > /etc
/cron.d
/reddit
<<CRON
744 0 3 * * * root /sbin/start --quiet reddit-job-update_sr_names
745 30 16 * * * root /sbin/start --quiet reddit-job-update_reddits
746 0 * * * * root /sbin/start --quiet reddit-job-update_promos
747 */5 * * * * root /sbin/start --quiet reddit-job-clean_up_hardcache
748 */2 * * * * root /sbin/start --quiet reddit-job-broken_things
749 */2 * * * * root /sbin/start --quiet reddit-job-rising
750 0 * * * * root /sbin/start --quiet reddit-job-trylater
753 * * * * * root /sbin/start --quiet reddit-job-liveupdate_activity
755 # jobs that recalculate time-limited listings (e.g. top this year)
757 */15 * * * * $REDDIT_USER $REDDIT_HOME/src/reddit/scripts/compute_time_listings link year '("hour", "day", "week", "month", "year")'
758 */15 * * * * $REDDIT_USER $REDDIT_HOME/src/reddit/scripts/compute_time_listings comment year '("hour", "day", "week", "month", "year")'
760 # disabled by default, uncomment if you need these jobs
761 #* * * * * root /sbin/start --quiet reddit-job-email
762 #0 0 * * * root /sbin/start --quiet reddit-job-update_gold_users
766 ###############################################################################
768 ###############################################################################
773 Congratulations! reddit is now installed.
775 The reddit application code is managed with upstart, to see what's currently
778 sudo initctl list | grep reddit
780 Cron jobs start with "reddit-job-" and queue processors start with
781 "reddit-consumer-". The crons are managed by /etc/cron.d/reddit. You can
782 initiate a restart of all the consumers by running:
786 or target specific ones:
788 sudo reddit-restart scraper_q
790 See the GitHub wiki for more information on these jobs:
792 * https://github.com/reddit/reddit/wiki/Cron-jobs
793 * https://github.com/reddit/reddit/wiki/Services
795 The reddit code can be shut down or started up with
800 And if you think caching might be hurting you, you can flush memcache with
804 Now that the core of reddit is installed, you may want to do some additional
807 * Ensure that $REDDIT_DOMAIN resolves to this machine.
809 * To populate the database with test data, run:
811 cd $REDDIT_HOME/src/reddit/r2
812 reddit-run r2/models/populatedb.py -c 'populate()'
814 * Manually run reddit-job-update_reddits immediately after populating the db
815 or adding your own subreddits.