2 DIR
="$( cd "$
( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 ## Make sure only root can run our script
9 if [[ $EUID -ne 0 ]]; then
10 echo "This script must be run as root. Please sudo or log in as root first." 1>&2
16 ## Check whether a connection to HOSTNAME ($1) on PORT ($2) is possible
21 VERIFY
=$
(date +%s | sha256sum | base64 |
head -c 20)
22 if ! [ -x "$(command -v nc)" ]; then
23 echo "In order to check the connection to $HOST:$PORT we need to open a socket using netcat."
24 echo However netcat is not installed on your system. You can
continue without this check
25 echo or abort the setup
, install netcat and try again.
27 read -p "Would you like to continue without this check? [yn] " yn
31 *) echo "Please answer y or n." ;;
35 echo -e "HTTP/1.1 200 OK\n\n $VERIFY" | nc
-w 4 -l -p $PORT >/dev
/null
2>&1 &
36 if curl
--proto =http
-s $HOST:$PORT --connect-timeout 3 |
grep $VERIFY >/dev
/null
2>&1; then
39 curl
--proto =http
-s localhost
:$PORT >/dev
/null
2>&1
48 echo Checking your domain name . . .
49 connect_to_port
$HOST 443; ec
=$?
52 echo "Connection to $HOST succeeded."
55 echo "WARNING:: This server does not appear to be accessible at $HOST:443."
57 if connect_to_port
$HOST 80; then
58 echo A connection to port
80 succeeds
, however.
59 echo This suggests that your DNS settings are correct
,
60 echo but something is keeping traffic to port
443 from getting to your server.
61 echo Check your networking configuration to see that connections to port
443 are allowed.
63 echo "A connection to http://$HOST (port 80) also fails."
65 echo "This suggests that $HOST resolves to the wrong IP address"
66 echo or that traffic is not being routed to your server.
69 echo Google
: \"open ports YOUR CLOUD SERVICE
\" for information
for resolving this problem.
71 echo If you want to proceed anyway
, you will need to
72 echo edit the containers
/app.yml
file manually.
76 echo "Continuing without port check."
84 check_and_install_docker
() {
85 docker_path
=`which docker.io || which docker`
86 if [ -z $docker_path ]; then
87 read -p "Docker not installed. Enter to install from https://get.docker.com/ or Ctrl+C to exit"
88 curl https
://get.docker.com
/ | sh
90 docker_path
=`which docker.io || which docker`
91 if [ -z $docker_path ]; then
92 echo Docker
install failed. Quitting.
98 ## What are we running on
105 ## OS X available memory
108 echo `free -m | awk '/Mem:/ {print $2}'`
112 ## Linux available memory
114 check_linux_memory
() {
115 ## some VMs report just under 1GB of RAM, so
116 ## make an exception and allow those with more
118 mem
=`free -m --si | awk ' /Mem:/ {print $2}'`
119 if [ "$mem" -ge 990 -a "$mem" -lt 1000 ]; then
122 echo `free -g --si | awk ' /Mem:/ {print $2} '`
127 ## Do we have enough memory and disk space for Discourse?
129 check_disk_and_memory
() {
133 if [ "$os_type" == "Darwin" ]; then
134 avail_mem
=$
(check_osx_memory
)
136 avail_mem
=$
(check_linux_memory
)
139 if [ "$avail_mem" -lt 1 ]; then
140 echo "WARNING: Discourse requires 1GB RAM to run. This system does not appear"
141 echo "to have sufficient memory."
143 echo "Your site may not work properly, or future upgrades of Discourse may not"
144 echo "complete successfully."
148 if [ "$avail_mem" -le 2 ]; then
149 total_swap
=`free -g --si | awk ' /Swap:/ {print $2} '`
151 if [ "$total_swap" -lt 2 ]; then
152 echo "WARNING: Discourse requires at least 2GB of swap when running with 2GB of RAM"
153 echo "or less. This system does not appear to have sufficient swap space."
155 echo "Without sufficient swap space, your site may not work properly, and future"
156 echo "upgrades of Discourse may not complete successfully."
158 echo "Ctrl+C to exit or wait 5 seconds to have a 2GB swapfile created."
162 ## derived from https://meta.discourse.org/t/13880
164 install -o root
-g root
-m 0600 /dev
/null
/swapfile
165 fallocate
-l 2G
/swapfile
168 echo "/swapfile swap swap auto 0 0" |
tee -a /etc
/fstab
169 sysctl
-w vm.swappiness
=10
170 echo 'vm.swappiness = 10' > /etc
/sysctl.d
/30-discourse-swap.conf
172 total_swap
=`free -g --si | awk ' /Swap:/ {print $2} '`
173 if [ "$total_swap" -lt 2 ]; then
174 echo "Failed to create swap: are you root? Are you running on real hardware, or a fully virtualized server?"
182 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
183 if [ "$free_disk" -lt 5000 ]; then
184 echo "WARNING: Discourse requires at least 5GB free disk space. This system"
185 echo "does not appear to have sufficient disk space."
187 echo "Insufficient disk space may result in problems running your site, and"
188 echo "may not even allow Discourse installation to complete successfully."
190 echo "Please free up some space, or expand your disk, before continuing."
192 echo "Run \`apt-get autoremove && apt-get autoclean\` to clean up unused"
193 echo "packages and \`./launcher cleanup\` to remove stale Docker containers."
201 ## If we have lots of RAM or lots of CPUs, bump up the defaults to scale better
203 scale_ram_and_cpu
() {
205 local changelog
=/tmp
/changelog.
$PPID
206 # grab info about total system ram and physical (NOT LOGICAL!) CPU cores
210 if [ "$os_type" == "Darwin" ]; then
211 avail_gb
=$
(check_osx_memory
)
212 avail_cores
=`sysctl hw.ncpu | awk '/hw.ncpu:/ {print $2}'`
214 avail_gb
=$
(check_linux_memory
)
215 avail_cores
=$
((`awk '/cpu cores/ {print $4;exit}' /proc/cpuinfo`*`sort /proc/cpuinfo | uniq | grep -c "physical id"`))
217 echo "Found ${avail_gb}GB of memory and $avail_cores physical CPU cores"
219 # db_shared_buffers: 128MB for 1GB, 256MB for 2GB, or 256MB * GB, max 4096MB
220 if [ "$avail_gb" -eq "1" ]
222 db_shared_buffers
=128
224 if [ "$avail_gb" -eq "2" ]
226 db_shared_buffers
=256
228 db_shared_buffers
=$
(( 256 * $avail_gb ))
231 db_shared_buffers
=$
(( db_shared_buffers
< 4096 ? db_shared_buffers
: 4096 ))
233 sed -i -e "s/^ #\?db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}MB\"/w $changelog" $data_file
236 echo "setting db_shared_buffers = ${db_shared_buffers}MB"
240 # UNICORN_WORKERS: 2 * GB for 2GB or less, or 2 * CPU, max 8
241 if [ "$avail_gb" -le "2" ]
243 unicorn_workers
=$
(( 2 * $avail_gb ))
245 unicorn_workers
=$
(( 2 * $avail_cores ))
247 unicorn_workers
=$
(( unicorn_workers
< 8 ? unicorn_workers
: 8 ))
249 sed -i -e "s/^ #\?UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $web_file
252 echo "setting UNICORN_WORKERS = ${unicorn_workers}"
256 echo $data_file memory parameters updated.
261 ## standard http / https ports must not be occupied
266 echo "Ports 80 and 443 are free for use"
271 ## check a port to see if it is already in use
275 local valid
=$
(netstat
-tln |
awk '{print $4}' |
grep ":${1}\$")
277 if [ -n "$valid" ]; then
278 echo "Port ${1} appears to already be in use."
280 echo "This will show you what command is using port ${1}"
281 lsof
-i tcp
:${1} -s tcp
:listen
283 echo "If you are trying to run Discourse simultaneously with another web"
284 echo "server like Apache or nginx, you will need to bind to a different port"
286 echo "See https://meta.discourse.org/t/17247"
288 echo "If you are reconfiguring an already-configured Discourse, use "
290 echo "./launcher stop app"
292 echo "to stop Discourse before you reconfigure it and try again."
298 ## read a variable from the config file
301 config_line
=`egrep "^ #?$1:" $web_file`
302 read_config_result
=`echo $config_line | awk -F":" '{print $2}'`
303 read_config_result
=`echo $read_config_result | sed "s/^\([\"']\)\(.*\)\1\$/\2/g"`
307 config_line
=`egrep "^ #?$1:" samples/standalone.yml`
308 read_default_result
=`echo $config_line | awk -F":" '{print $2}'`
309 read_default_result
=`echo $read_config_result | sed "s/^\([\"']\)\(.*\)\1\$/\2/g"`
313 ## prompt user for typical Discourse config file values
315 ask_user_for_config
() {
317 # NOTE: Defaults now come from standalone.yml
319 local changelog
=/tmp
/changelog.
$PPID
320 read_config
"DISCOURSE_SMTP_ADDRESS"
321 local smtp_address
=$read_config_result
322 # NOTE: if there are spaces between emails, this breaks, but a human should be paying attention
323 read_config
"DISCOURSE_DEVELOPER_EMAILS"
324 local developer_emails
=$read_config_result
325 read_config
"DISCOURSE_SMTP_PASSWORD"
326 local smtp_password
=$read_config_result
327 read_config
"DISCOURSE_SMTP_PORT"
328 local smtp_port
=$read_config_result
329 read_config
"DISCOURSE_SMTP_USER_NAME"
330 local smtp_user_name
=$read_config_result
331 if [ "$smtp_password" = "pa$$word" ]
335 read_config
"LETSENCRYPT_ACCOUNT_EMAIL"
336 local letsencrypt_account_email
=$read_config_result
337 if [ -z $letsencrypt_account_email ]
339 letsencrypt_account_email
="me@example.com"
341 if [ "$letsencrypt_account_email" = "me@example.com" ]
343 local letsencrypt_status
="ENTER to skip"
345 local letsencrypt_status
="Enter 'OFF' to disable."
348 read_config
"DISCOURSE_HOSTNAME"
349 hostname
=$read_config_result
357 while [[ "$config_ok" == "n" ]]
359 if [ ! -z "$hostname" ]
361 read -p "Hostname for your Discourse? [$hostname]: " new_value
362 if [ ! -z "$new_value" ]
364 hostname
="$new_value"
366 if [[ $hostname =~ ^
[0-9]{1,3}\.
[0-9]{1,3}\.
[0-9]{1,3}\.
[0-9]{1,3}$
]]
369 echo "Discourse requires a DNS hostname. IP addresses are unsupported and will not work."
371 hostname
="discourse.example.com"
375 check_IP_match
$hostname
377 if [ ! -z "$developer_emails" ]
379 read -p "Email address for admin account(s)? [$developer_emails]: " new_value
380 if [ ! -z "$new_value" ]
382 developer_emails
="$new_value"
386 if [ ! -z "$smtp_address" ]
388 read -p "SMTP server address? [$smtp_address]: " new_value
389 if [ ! -z "$new_value" ]
391 smtp_address
="$new_value"
395 if [ ! -z "$smtp_port" ]
397 read -p "SMTP port? [$smtp_port]: " new_value
398 if [ ! -z "$new_value" ]
400 smtp_port
="$new_value"
405 ## automatically set correct user name based on common mail providers unless it's been set
407 if [ "$smtp_user_name" == "user@example.com" ]
409 if [ "$smtp_address" == "smtp.sparkpostmail.com" ]
411 smtp_user_name
="SMTP_Injection"
413 if [ "$smtp_address" == "smtp.sendgrid.net" ]
415 smtp_user_name
="apikey"
417 if [ "$smtp_address" == "smtp.mailgun.org" ]
419 smtp_user_name
="postmaster@$hostname"
423 if [ ! -z "$smtp_user_name" ]
425 read -p "SMTP user name? [$smtp_user_name]: " new_value
426 if [ ! -z "$new_value" ]
428 smtp_user_name
="$new_value"
432 read -p "SMTP password? [$smtp_password]: " new_value
433 if [ ! -z "$new_value" ]
435 smtp_password
="$new_value"
438 if [ ! -z $letsencrypt_account_email ]
440 read -p "Optional email address for Let's Encrypt warnings? ($letsencrypt_status) [$letsencrypt_account_email]: " new_value
441 if [ ! -z "$new_value" ]
443 letsencrypt_account_email
="$new_value"
444 if [ "${new_value,,}" = "off" ]
446 letsencrypt_status
="ENTER to skip"
448 letsencrypt_status
="Enter 'OFF' to disable."
453 echo -e "\nDoes this look right?\n"
454 echo "Hostname : $hostname"
455 echo "Email : $developer_emails"
456 echo "SMTP address : $smtp_address"
457 echo "SMTP port : $smtp_port"
458 echo "SMTP username : $smtp_user_name"
459 echo "SMTP password : $smtp_password"
461 if [ "$letsencrypt_status" == "Enter 'OFF' to disable." ]
463 echo "Let's Encrypt : $letsencrypt_account_email"
468 read -p "ENTER to continue, 'n' to try again, Ctrl+C to exit: " config_ok
471 sed -i -e "s/^ DISCOURSE_HOSTNAME:.*/ DISCOURSE_HOSTNAME: $hostname/w $changelog" $web_file
476 echo "DISCOURSE_HOSTNAME change failed."
480 sed -i -e "s/^ DISCOURSE_DEVELOPER_EMAILS:.*/ DISCOURSE_DEVELOPER_EMAILS: \'$developer_emails\'/w $changelog" $web_file
485 echo "DISCOURSE_DEVELOPER_EMAILS change failed."
489 sed -i -e "s/^ DISCOURSE_SMTP_ADDRESS:.*/ DISCOURSE_SMTP_ADDRESS: $smtp_address/w $changelog" $web_file
494 echo "DISCOURSE_SMTP_ADDRESS change failed."
498 sed -i -e "s/^ #\?DISCOURSE_SMTP_PORT:.*/ DISCOURSE_SMTP_PORT: $smtp_port/w $changelog" $web_file
503 echo "DISCOURSE_SMTP_PORT change failed."
507 sed -i -e "s/^ #\?DISCOURSE_SMTP_USER_NAME:.*/ DISCOURSE_SMTP_USER_NAME: $smtp_user_name/w $changelog" $web_file
512 echo "DISCOURSE_SMTP_USER_NAME change failed."
516 if [[ "$smtp_password" == *"\""* ]]
519 echo "========================================"
521 echo "Your password contains a quote (\")"
522 echo "Your SMTP Password will not be set. You will need to edit app.yml to enter it."
523 echo "========================================"
527 if [[ "$smtp_password" == *"$SLASH"* ]]
529 if [[ "$smtp_password" == *"$SLASH"* ]]
532 if [[ "$smtp_password" == *"$SLASH"* ]]
535 echo "========================================"
537 echo "Your password contains all available delimiters (+, |, and Q). "
538 echo "Your SMTP Password will not be set. You will need to edit app.yml to enter it."
539 echo "========================================"
545 if [[ "$SLASH" != "BROKEN" ]]
547 sed -i -e "s${SLASH}^ #\?DISCOURSE_SMTP_PASSWORD:.*${SLASH} DISCOURSE_SMTP_PASSWORD: \"${smtp_password}\"${SLASH}w $changelog" $web_file
553 echo "DISCOURSE_SMTP_PASSWORD change failed."
558 echo "Enabling Let's Encrypt"
559 sed -i -e "s/^ #\?LETSENCRYPT_ACCOUNT_EMAIL:.*/ LETSENCRYPT_ACCOUNT_EMAIL: $letsencrypt_account_email/w $changelog" $web_file
564 echo "LETSENCRYPT_ACCOUNT_EMAIL change failed."
567 local src
='^ #\?- "templates\/web.ssl.template.yml"'
568 local dst
=' \- "templates\/web.ssl.template.yml"'
569 sed -i -e "s/$src/$dst/w $changelog" $web_file
572 echo "web.ssl.template.yml enabled"
575 echo "web.ssl.template.yml NOT ENABLED--was it on already?"
577 local src
='^ #\?- "templates\/web.letsencrypt.ssl.template.yml"'
578 local dst
=' - "templates\/web.letsencrypt.ssl.template.yml"'
580 sed -i -e "s/$src/$dst/w $changelog" $web_file
583 echo "letsencrypt.ssl.template.yml enabled"
586 echo "letsencrypt.ssl.template.yml NOT ENABLED -- was it on already?"
589 if [ "$update_ok" == "y" ]
591 echo -e "\nConfiguration file at $config_file updated successfully!\n"
593 echo -e "\nUnfortunately, there was an error changing $config_file\n"
594 echo -d "This may happen if you have made unexpected changes."
600 ## is our config file valid? Does it have the required fields set?
606 for x
in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
607 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
610 local result
=$read_config_result
612 local default
=$read_default_result
614 if [ ! -z "$result" ]
616 if [[ "$config_line" = *"$default"* ]]
618 echo "$x left at incorrect default of $default"
621 config_val
=`echo $config_line | awk '{print $2}'`
622 if [ -z $config_val ]
624 echo "$x was not configured"
628 echo "$x not present"
633 if [ "$valid_config" != "y" ]; then
634 echo -e "\nSorry, these $web_file settings aren't valid -- can't continue!"
635 echo "If you have unusual requirements, edit $web_file and then: "
636 echo "./launcher bootstrap $app_name"
643 ## template file names
646 if [ "$1" == "2container" ]
650 web_template
=samples
/web_only.yml
651 data_template
=samples
/data.yml
652 web_file
=containers
/$app_name.yml
653 data_file
=containers
/$data_name.yml
657 web_template
=samples
/standalone.yml
659 web_file
=containers
/$app_name.yml
660 data_file
=containers
/$app_name.yml
662 changelog
=/tmp
/changelog
665 ## Check requirements before creating a copy of a config file we won't edit
668 check_and_install_docker
669 check_disk_and_memory
671 if [ -a "$web_file" ]
673 echo "The configuration file $web_file already exists!"
675 echo ". . . reconfiguring . . ."
678 DATE
=`date +"%Y-%m-%d-%H%M%S"`
679 BACKUP
=$app_name.yml.
$DATE.bak
680 echo Saving old
file as
$BACKUP
681 cp $web_file containers
/$BACKUP
682 echo "Stopping existing container in 5 seconds or Control-C to cancel."
688 cp -v $web_template $web_file
689 if [ "$data_name" == "data" ]
691 echo "--------------------------------------------------"
692 echo "This two container setup is currently unsupported. Use at your own risk!"
693 echo "--------------------------------------------------"
694 DISCOURSE_DB_PASSWORD
=`date +%s | sha256sum | base64 | head -c 20`
696 sed -i -e "s/DISCOURSE_DB_PASSWORD: SOME_SECRET/DISCOURSE_DB_PASSWORD: $DISCOURSE_DB_PASSWORD/w $changelog" $web_file
701 echo "Problem changing DISCOURSE_DB_PASSWORD" in $web_file
704 cp -v $data_template $data_file
706 sed -i -e "s/password ${quote}SOME_SECRET${quote}/password '$DISCOURSE_DB_PASSWORD'/w $changelog" $data_file
711 echo "Problem changing DISCOURSE_DB_PASSWORD" in $data_file
721 ## if we reach this point without exiting, OK to proceed
722 ## rebuild won't fail if there's nothing to rebuild and does the restart
724 echo "Updates successful. Rebuilding in 5 seconds."
725 sleep 5 # Just a chance to ^C in case they were too fast on the draw
726 if [ "$data_name" == "$app_name" ]
728 echo Building
$app_name
729 .
/launcher rebuild
$app_name
731 echo Building
$data_name now . . .
732 .
/launcher rebuild
$data_name
733 echo Building
$app_name now . . .
734 .
/launcher rebuild
$app_name