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-2015 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 if [[ -z "$REDDIT_USER" ]]; then
76 # in a production install, you'd want the code to be owned by root and run
77 # by a less privileged user. this script is intended to build a development
78 # install, so we expect the owner to run the app and not be root.
80 ERROR: You have not specified a user. This usually means you're running this
81 script directly as root. It is not recommended to run reddit as the root user.
83 Please create a user to run reddit and set the REDDIT_USER variable
89 if [[ "amd64" != $
(dpkg
--print-architecture) ]]; then
91 ERROR: This host is running the $(dpkg --print-architecture) architecture!
93 Because of the pre-built dependencies in our PPA, and some extra picky things
94 like ID generation in liveupdate, installing reddit is only supported on amd64
100 # seriously! these checks are here for a reason. the packages from the
101 # reddit ppa aren't built for anything but trusty (14.04) right now, so
102 # if you try and use this install script on another release you're gonna
104 source /etc
/lsb-release
105 if [ "$DISTRIB_ID" != "Ubuntu" -o "$DISTRIB_RELEASE" != "14.04" ]; then
106 echo "ERROR: Only Ubuntu 14.04 is supported."
110 if [[ "2000000" -gt $
(awk '/MemTotal/{print $2}' /proc
/meminfo
) ]]; then
111 LOW_MEM_PROMPT
="reddit requires at least 2GB of memory to work properly, continue anyway? [y/n] "
112 read -er -n1 -p "$LOW_MEM_PROMPT" response
113 if [[ "$response" != "y" ]]; then
119 ###############################################################################
120 # Install prerequisites
121 ###############################################################################
124 # aptitude configuration
125 APTITUDE_OPTIONS
="-y"
126 export DEBIAN_FRONTEND
=noninteractive
128 # run an aptitude update to make sure python-software-properties
129 # dependencies are found
132 # add the reddit ppa for some custom packages
133 apt-get
install $APTITUDE_OPTIONS python-software-properties
134 apt-add-repository
-y ppa
:reddit
/ppa
136 # pin the ppa -- packages present in the ppa will take precedence over
137 # ones in other repositories (unless further pinning is done)
138 cat <<HERE > /etc/apt/preferences.d/reddit
140 Pin: release o=LP-PPA-reddit
144 # add the datastax cassandra repos
145 echo deb http
://debian.datastax.com
/community stable main
> /etc
/apt
/sources.list.d
/cassandra.sources.list
146 wget
-qO- -L https
://debian.datastax.com
/debian
/repo_key | sudo apt-key add
-
148 # grab the new ppas' package listings
151 # install prerequisites
152 cat <<PACKAGES | xargs apt-get install $APTITUDE_OPTIONS
173 python-pylibmc=1.2.2-1~trusty5
213 # we don't want to upgrade to C* 2.0 yet, so we'll put it on hold
214 apt-mark hold cassandra
216 ###############################################################################
217 # Wait for all the services to be up
218 ###############################################################################
219 # cassandra doesn't auto-start after install
220 service cassandra start
222 # check each port for connectivity
223 echo "Waiting for services to be available, see source for port meanings..."
228 for port
in 11211 5432 5672 9160; do
229 while ! nc
-vz localhost
$port; do
234 ###############################################################################
235 # Install the reddit source repositories
236 ###############################################################################
237 if [ ! -d $REDDIT_HOME/src
]; then
238 mkdir
-p $REDDIT_HOME/src
239 chown
$REDDIT_USER $REDDIT_HOME/src
242 function clone_reddit_repo
{
243 local destination
=$REDDIT_HOME/src
/${1}
244 local repository_url
=https
://github.com
/${2}.git
246 if [ ! -d $destination ]; then
247 sudo
-u $REDDIT_USER -H git clone
$repository_url $destination
250 if [ -d $destination/upstart
]; then
251 cp $destination/upstart
/* /etc
/init
/
255 function clone_reddit_plugin_repo
{
256 clone_reddit_repo
$1 reddit
/reddit-plugin-
$1
259 clone_reddit_repo reddit reddit
/reddit
260 clone_reddit_repo i18n reddit
/reddit-i18n
261 for plugin
in $REDDIT_PLUGINS; do
262 clone_reddit_plugin_repo
$plugin
265 ###############################################################################
266 # Configure Cassandra
267 ###############################################################################
270 sys = pycassa.SystemManager("localhost:9160")
272 if "reddit" not in sys.list_keyspaces():
273 print "creating keyspace 'reddit'"
274 sys.create_keyspace("reddit", "SimpleStrategy", {"replication_factor": "1"})
277 if "permacache" not in sys.get_keyspace_column_families("reddit"):
278 print "creating column family 'permacache'"
279 sys.create_column_family("reddit", "permacache")
283 ###############################################################################
284 # Configure PostgreSQL
285 ###############################################################################
286 SQL
="SELECT COUNT(1) FROM pg_catalog.pg_database WHERE datname = 'reddit';"
287 IS_DATABASE_CREATED
=$
(sudo
-u postgres psql
-t -c "$SQL")
289 if [ $IS_DATABASE_CREATED -ne 1 ]; then
290 cat <<PGSCRIPT | sudo -u postgres psql
291 CREATE DATABASE reddit WITH ENCODING = 'utf8' TEMPLATE template0 LC_COLLATE='en_US.utf8' LC_CTYPE='en_US.utf8';
292 CREATE USER reddit WITH PASSWORD 'password';
296 sudo
-u postgres psql reddit
< $REDDIT_HOME/src
/reddit
/sql
/functions.sql
298 ###############################################################################
300 ###############################################################################
301 if [ ! -d /etc
/mcrouter
]; then
302 mkdir
-p /etc
/mcrouter
305 if [ ! -f /etc
/mcrouter
/global.conf
]; then
306 cat > /etc
/mcrouter
/global.conf
<<MCROUTER
308 // route all valid prefixes to the local memcached
315 "keep_routing_prefix": false,
319 "type": "PrefixSelectorRoute",
342 ###############################################################################
344 ###############################################################################
345 if ! rabbitmqctl list_vhosts |
egrep "^/$"
347 rabbitmqctl add_vhost
/
350 if ! rabbitmqctl list_users |
egrep "^reddit"
352 rabbitmqctl add_user reddit reddit
355 rabbitmqctl set_permissions
-p / reddit
".*" ".*" ".*"
357 ###############################################################################
358 # Install and configure the reddit code
359 ###############################################################################
360 function install_reddit_repo
{
361 cd $REDDIT_HOME/src
/$1
362 sudo
-u $REDDIT_USER python setup.py build
363 python setup.py develop
--no-deps
366 install_reddit_repo reddit
/r2
367 install_reddit_repo i18n
368 for plugin
in $REDDIT_PLUGINS; do
369 install_reddit_repo
$plugin
372 # generate binary translation files from source
373 cd $REDDIT_HOME/src
/i18n
/
374 sudo
-u $REDDIT_USER make clean all
376 # this builds static files and should be run *after* languages are installed
377 # so that the proper language-specific static files can be generated and after
378 # plugins are installed so all the static files are available.
379 cd $REDDIT_HOME/src
/reddit
/r2
380 sudo
-u $REDDIT_USER make clean all
382 plugin_str
=$
(echo -n "$REDDIT_PLUGINS" |
tr " " ,)
383 if [ ! -f development.update
]; then
384 cat > development.update
<<DEVELOPMENT
385 # after editing this file, run "make ini" to
386 # generate a new development.ini
389 # global debug flag -- displays pylons stacktrace rather than 500 page on error when true
390 # WARNING: a pylons stacktrace allows remote code execution. Make sure this is false
391 # if your server is publicly accessible.
395 disable_captcha = true
396 disable_ratelimit = true
397 disable_require_admin_otp = true
401 domain = $REDDIT_DOMAIN
402 oauth_domain = $REDDIT_DOMAIN
404 plugins = $plugin_str
406 media_provider = filesystem
407 media_fs_root = /srv/www/media
408 media_fs_base_url_http = http://%(domain)s/media/
413 chown
$REDDIT_USER development.update
415 sed -i "s/^plugins = .*$/plugins = $plugin_str/" $REDDIT_HOME/src
/reddit
/r
2/development.update
416 sed -i "s/^domain = .*$/domain = $REDDIT_DOMAIN/" $REDDIT_HOME/src
/reddit
/r
2/development.update
417 sed -i "s/^oauth_domain = .*$/oauth_domain = $REDDIT_DOMAIN/" $REDDIT_HOME/src
/reddit
/r
2/development.update
420 sudo
-u $REDDIT_USER make ini
422 if [ ! -L run.ini
]; then
423 sudo
-u $REDDIT_USER ln -nsf development.ini run.ini
426 ###############################################################################
427 # some useful helper scripts
428 ###############################################################################
429 function helper-script
() {
434 helper-script
/usr
/local
/bin
/reddit-run
<<REDDITRUN
436 exec paster --plugin=r2 run $REDDIT_HOME/src/reddit/r2/run.ini "\$@"
439 helper-script
/usr
/local
/bin
/reddit-shell
<<REDDITSHELL
441 exec paster --plugin=r2 shell $REDDIT_HOME/src/reddit/r2/run.ini
444 helper-script
/usr
/local
/bin
/reddit-start
<<REDDITSTART
446 initctl emit reddit-start
449 helper-script
/usr
/local
/bin
/reddit-stop
<<REDDITSTOP
451 initctl emit reddit-stop
454 helper-script
/usr
/local
/bin
/reddit-restart
<<REDDITRESTART
456 initctl emit reddit-restart TARGET=${1:-all}
459 helper-script
/usr
/local
/bin
/reddit-flush
<<REDDITFLUSH
461 echo flush_all | nc localhost 11211
464 ###############################################################################
465 # pixel and click server
466 ###############################################################################
467 mkdir
-p /var
/opt
/reddit
/
468 chown
$REDDIT_USER:$REDDIT_GROUP /var
/opt
/reddit
/
470 mkdir
-p /srv
/www
/pixel
471 chown
$REDDIT_USER:$REDDIT_GROUP /srv
/www
/pixel
472 cp $REDDIT_HOME/src
/reddit
/r
2/r
2/public
/static
/pixel.png
/srv
/www
/pixel
474 if [ ! -f /etc
/gunicorn.d
/click.conf
]; then
475 cat > /etc
/gunicorn.d
/click.conf
<<CLICK
478 "working_dir": "$REDDIT_HOME/src/reddit/scripts",
479 "user": "$REDDIT_USER",
480 "group": "$REDDIT_USER",
482 "--bind=unix:/var/opt/reddit/click.sock",
484 "tracker:application",
490 service gunicorn start
492 ###############################################################################
494 ###############################################################################
496 mkdir
-p /srv
/www
/media
497 chown
$REDDIT_USER:$REDDIT_GROUP /srv
/www
/media
499 cat > /etc
/nginx
/sites-available
/reddit-media
<<MEDIA
506 alias /srv/www/media/;
511 cat > /etc
/nginx
/sites-available
/reddit-pixel
<<PIXEL
512 upstream click_server {
513 server unix:/var/opt/reddit/click.sock fail_timeout=0;
519 log_format directlog '\$remote_addr - \$remote_user [\$time_local] '
520 '"\$request_method \$request_uri \$server_protocol" \$status \$body_bytes_sent '
521 '"\$http_referer" "\$http_user_agent"';
522 access_log /var/log/nginx/traffic/traffic.log directlog;
526 rewrite ^/pixel/of_ /pixel.png;
528 add_header Last-Modified "";
529 add_header Pragma "no-cache";
532 root /srv/www/pixel/;
536 proxy_pass http://click_server;
541 cat > /etc
/nginx
/sites-available
/reddit-ssl
<<SSL
542 map \$http_upgrade \$connection_upgrade {
551 ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
552 ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
554 ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
555 ssl_ciphers EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
556 ssl_prefer_server_ciphers on;
558 ssl_session_cache shared:SSL:1m
;
561 proxy_pass http
://127.0.0.1:8080;
562 proxy_set_header Host \
$http_host;
563 proxy_http_version
1.1;
564 proxy_set_header X-Forwarded-For \
$remote_addr;
565 proxy_pass_header Server
;
567 # allow websockets through if desired
568 proxy_set_header Upgrade \
$http_upgrade;
569 proxy_set_header Connection \
$connection_upgrade;
574 # remove the default nginx site that may conflict with haproxy
575 rm -rf /etc
/nginx
/sites-enabled
/default
576 # put our config in place
577 ln -nsf /etc
/nginx
/sites-available
/reddit-media
/etc
/nginx
/sites-enabled
/
578 ln -nsf /etc
/nginx
/sites-available
/reddit-pixel
/etc
/nginx
/sites-enabled
/
579 ln -nsf /etc
/nginx
/sites-available
/reddit-ssl
/etc
/nginx
/sites-enabled
/
581 # make the pixel log directory
582 mkdir
-p /var
/log
/nginx
/traffic
584 # link the ini file for the Flask click tracker
585 ln -nsf $REDDIT_HOME/src
/reddit
/r
2/development.ini
$REDDIT_HOME/src
/reddit
/scripts
/production.ini
587 service nginx restart
589 ###############################################################################
591 ###############################################################################
592 if [ -e /etc
/haproxy
/haproxy.cfg
]; then
593 BACKUP_HAPROXY
=$
(mktemp
/etc
/haproxy
/haproxy.cfg.XXX
)
594 echo "Backing up /etc/haproxy/haproxy.cfg to $BACKUP_HAPROXY"
595 cat /etc
/haproxy
/haproxy.cfg
> $BACKUP_HAPROXY
598 # make sure haproxy is enabled
599 cat > /etc
/default
/haproxy
<<DEFAULT
604 cat > /etc
/haproxy
/haproxy.cfg
<<HAPROXY
615 option forwardfor except 127.0.0.1
618 # make sure that requests have x-forwarded-proto: https iff tls
619 reqidel ^X-Forwarded-Proto:.*
620 acl is-ssl dst_port 8080
621 reqadd X-Forwarded-Proto:\ https if is-ssl
623 # send websockets to sutro
624 acl is-websocket hdr(Upgrade) -i WebSocket
625 use_backend sutro if is-websocket
627 # send media stuff to the local nginx
628 acl is-media path_beg /media/
629 use_backend media if is-media
631 # send pixel stuff to local nginx
632 acl is-pixel path_beg /pixel/
633 acl is-click path_beg /click
634 use_backend pixel if is-pixel || is-click
636 default_backend reddit
645 server app01-8001 localhost:8001 maxconn 30
653 server sutro localhost:8002 maxconn 250
662 server nginx localhost:9000 maxconn 20
671 server nginx localhost:8082 maxconn 20
674 # this will start it even if currently stopped
675 service haproxy restart
677 ###############################################################################
678 # sutro (websocket server)
679 ###############################################################################
681 if [ ! -f /etc
/sutro.ini
]; then
682 cat > /etc
/sutro.ini
<<SUTRO
684 paste.app_factory = sutro.app:make_app
686 amqp.host = localhost
689 amqp.username = reddit
690 amqp.password = reddit
692 web.allowed_origins = $REDDIT_DOMAIN
693 web.mac_secret = YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5
694 web.ping_interval = 300
700 use = egg:gunicorn#main
701 worker_class = sutro.socketserver.SutroWorker
703 worker_connections = 250
707 forward_allow_ips = 127.0.0.1
723 class = handlers.SysLogHandler
724 args = ("/dev/log", "local7")
729 format = [%(name)s] %(message)s
733 if [ ! -f /etc
/init
/sutro.conf
]; then
734 cat > /etc
/init
/sutro.conf
<< UPSTART_SUTRO
735 description "sutro websocket server"
737 stop on runlevel [!2345]
738 start on runlevel [2345]
744 limit nofile 65535 65535
746 exec gunicorn_paster /etc/sutro.ini
750 service sutro restart
752 ###############################################################################
754 ###############################################################################
755 if [ ! -f /etc
/gunicorn.d
/geoip.conf
]; then
756 cat > /etc
/gunicorn.d
/geoip.conf
<<GEOIP
759 "working_dir": "$REDDIT_HOME/src/reddit/scripts",
760 "user": "$REDDIT_USER",
761 "group": "$REDDIT_USER",
763 "--bind=127.0.0.1:5000",
765 "--limit-request-line=8190",
766 "geoip_service:application",
772 service gunicorn start
774 ###############################################################################
776 ###############################################################################
777 CONSUMER_CONFIG_ROOT
=$REDDIT_HOME/consumer-count.d
779 if [ ! -f /etc
/default
/reddit
]; then
780 cat > /etc
/default
/reddit
<<DEFAULT
781 export REDDIT_ROOT=$REDDIT_HOME/src/reddit/r2
782 export REDDIT_INI=$REDDIT_HOME/src/reddit/r2/run.ini
783 export REDDIT_USER=$REDDIT_USER
784 export REDDIT_GROUP=$REDDIT_GROUP
785 export REDDIT_CONSUMER_CONFIG=$CONSUMER_CONFIG_ROOT
786 alias wrap-job=$REDDIT_HOME/src/reddit/scripts/wrap-job
787 alias manage-consumers=$REDDIT_HOME/src/reddit/scripts/manage-consumers
791 ###############################################################################
793 ###############################################################################
794 mkdir
-p $CONSUMER_CONFIG_ROOT
796 function set_consumer_count
{
797 if [ ! -f $CONSUMER_CONFIG_ROOT/$1 ]; then
798 echo $2 > $CONSUMER_CONFIG_ROOT/$1
802 set_consumer_count log_q
0
803 set_consumer_count search_q
0
804 set_consumer_count del_account_q
1
805 set_consumer_count scraper_q
1
806 set_consumer_count markread_q
1
807 set_consumer_count commentstree_q
1
808 set_consumer_count newcomments_q
1
809 set_consumer_count vote_link_q
1
810 set_consumer_count vote_comment_q
1
811 set_consumer_count automoderator_q
0
813 chown
-R $REDDIT_USER:$REDDIT_GROUP $CONSUMER_CONFIG_ROOT/
816 ###############################################################################
817 # Start everything up
818 ###############################################################################
820 # the initial database setup should be done by one process rather than a bunch
821 # vying with eachother to get there first
822 reddit-run
-c 'print "ok done"'
824 # ok, now start everything else up
825 initctl emit reddit-stop
826 initctl emit reddit-start
828 ###############################################################################
830 ###############################################################################
831 if [ ! -f /etc
/cron.d
/reddit
]; then
832 cat > /etc
/cron.d
/reddit
<<CRON
833 0 3 * * * root /sbin/start --quiet reddit-job-update_sr_names
834 30 16 * * * root /sbin/start --quiet reddit-job-update_reddits
835 0 * * * * root /sbin/start --quiet reddit-job-update_promos
836 */5 * * * * root /sbin/start --quiet reddit-job-clean_up_hardcache
837 */2 * * * * root /sbin/start --quiet reddit-job-broken_things
838 */2 * * * * root /sbin/start --quiet reddit-job-rising
839 0 * * * * root /sbin/start --quiet reddit-job-trylater
842 * * * * * root /sbin/start --quiet reddit-job-liveupdate_activity
844 # jobs that recalculate time-limited listings (e.g. top this year)
846 */15 * * * * $REDDIT_USER $REDDIT_HOME/src/reddit/scripts/compute_time_listings link year '("hour", "day", "week", "month", "year")'
847 */15 * * * * $REDDIT_USER $REDDIT_HOME/src/reddit/scripts/compute_time_listings comment year '("hour", "day", "week", "month", "year")'
849 # disabled by default, uncomment if you need these jobs
850 #* * * * * root /sbin/start --quiet reddit-job-email
851 #0 0 * * * root /sbin/start --quiet reddit-job-update_gold_users
855 ###############################################################################
857 ###############################################################################
862 Congratulations! reddit is now installed.
864 The reddit application code is managed with upstart, to see what's currently
867 sudo initctl list | grep reddit
869 Cron jobs start with "reddit-job-" and queue processors start with
870 "reddit-consumer-". The crons are managed by /etc/cron.d/reddit. You can
871 initiate a restart of all the consumers by running:
875 or target specific ones:
877 sudo reddit-restart scraper_q
879 See the GitHub wiki for more information on these jobs:
881 * https://github.com/reddit/reddit/wiki/Cron-jobs
882 * https://github.com/reddit/reddit/wiki/Services
884 The reddit code can be shut down or started up with
889 And if you think caching might be hurting you, you can flush memcache with
893 Now that the core of reddit is installed, you may want to do some additional
896 * Ensure that $REDDIT_DOMAIN resolves to this machine.
898 * To populate the database with test data, run:
900 cd $REDDIT_HOME/src/reddit
901 reddit-run scripts/inject_test_data.py -c 'inject_test_data()'
903 * Manually run reddit-job-update_reddits immediately after populating the db
904 or adding your own subreddits.