Update samples (#387)
[discourse_docker.git] / discourse-setup
1 #!/usr/bin/env bash
2
3 ##
4 ## Make sure only root can run our script
5 ##
6 check_root() {
7 if [[ $EUID -ne 0 ]]; then
8 echo "This script must be run as root. Please sudo or log in as root first." 1>&2
9 exit 1
10 fi
11 }
12
13 ##
14 ## Check whether a connection to HOSTNAME ($1) on PORT ($2) is possible
15 ##
16 connect_to_port () {
17 HOST="$1"
18 PORT="$2"
19 VERIFY=`date +%s | sha256sum | base64 | head -c 20`
20 echo -e "HTTP/1.1 200 OK\n\n $VERIFY" | nc -w 4 -l -p $PORT >/dev/null 2>&1 &
21 if curl --proto =http -s $HOST:$PORT --connect-timeout 3 | grep $VERIFY >/dev/null 2>&1
22 then
23 return 0
24 else
25 curl --proto =http -s localhost:$PORT >/dev/null 2>&1
26 return 1
27 fi
28 }
29
30 check_IP_match () {
31 HOST="$1"
32 echo
33 echo Checking your domain name . . .
34 if connect_to_port $HOST 443
35 then
36 echo
37 echo "Connection to $HOST succeeded."
38 else
39 echo WARNING:: This server does not appear to be accessible at $HOST:443.
40 echo
41 if connect_to_port $HOST 80
42 then
43 echo A connection to port 80 succeeds, however.
44 echo This suggests that your DNS settings are correct,
45 echo but something is keeping traffic to port 443 from getting to your server.
46 echo Check your networking configuration to see that connections to port 443 are allowed.
47 else
48 echo "A connection to http://$HOST (port 80) also fails."
49 echo
50 echo This suggests that $HOST resolves to the wrong IP address
51 echo or that traffic is not being routed to your server.
52 fi
53 echo
54 echo Google: \"open ports YOUR CLOUD SERVICE\" for information for resolving this problem.
55 echo
56 echo You should probably answer \"n\" at the next prompt and disable Let\'s Encrypt.
57 echo
58 echo This test might not work for all situations,
59 echo so if you can access Discourse at http://$HOST, you might try anyway.
60 sleep 3
61 fi
62 }
63
64 ##
65 ## Do we have docker?
66 ##
67 check_and_install_docker () {
68 docker_path=`which docker.io || which docker`
69 if [ -z $docker_path ]; then
70 read -p "Docker not installed. Enter to install from https://get.docker.com/ or Ctrl+C to exit"
71 curl https://get.docker.com/ | sh
72 fi
73 docker_path=`which docker.io || which docker`
74 if [ -z $docker_path ]; then
75 echo Docker install failed. Quitting.
76 exit
77 fi
78 }
79
80 ##
81 ## What are we running on
82 ##
83 check_OS() {
84 echo `uname -s`
85 }
86
87 ##
88 ## OS X available memory
89 ##
90 check_osx_memory() {
91 echo `top -l 1 | awk '/PhysMem:/ {print $2}' | sed s/G//`
92 }
93
94 ##
95 ## Linux available memory
96 ##
97 check_linux_memory() {
98 echo `free -g --si | awk ' /Mem:/ {print $2} '`
99 }
100
101 ##
102 ## Do we have enough memory and disk space for Discourse?
103 ##
104 check_disk_and_memory() {
105
106 os_type=$(check_OS)
107 avail_mem=0
108 if [ "$os_type" == "Darwin" ]; then
109 avail_mem=$(check_osx_memory)
110 else
111 avail_mem=$(check_linux_memory)
112 fi
113
114 if [ "$avail_mem" -lt 1 ]; then
115 echo "WARNING: Discourse requires 1GB RAM to run. This system does not appear"
116 echo "to have sufficient memory."
117 echo
118 echo "Your site may not work properly, or future upgrades of Discourse may not"
119 echo "complete successfully."
120 exit 1
121 fi
122
123 if [ "$avail_mem" -le 2 ]; then
124 total_swap=`free -g --si | awk ' /Swap:/ {print $2} '`
125
126 if [ "$total_swap" -lt 2 ]; then
127 echo "WARNING: Discourse requires at least 2GB of swap when running with 2GB of RAM"
128 echo "or less. This system does not appear to have sufficient swap space."
129 echo
130 echo "Without sufficient swap space, your site may not work properly, and future"
131 echo "upgrades of Discourse may not complete successfully."
132 echo
133 echo "Ctrl+C to exit or wait 5 seconds to have a 2GB swapfile created."
134 sleep 5
135
136 ##
137 ## derived from https://meta.discourse.org/t/13880
138 ##
139 install -o root -g root -m 0600 /dev/null /swapfile
140 dd if=/dev/zero of=/swapfile bs=1k count=2048k
141 mkswap /swapfile
142 swapon /swapfile
143 echo "/swapfile swap swap auto 0 0" | tee -a /etc/fstab
144 sysctl -w vm.swappiness=10
145 echo vm.swappiness = 10 | tee -a /etc/sysctl.conf
146
147 total_swap=`free -g --si | awk ' /Swap:/ {print $2} '`
148 if [ "$total_swap" -lt 2 ]; then
149 echo "Failed to create swap, sorry!"
150 exit 1
151 fi
152
153 fi
154 fi
155
156
157 free_disk="$(df /var | tail -n 1 | awk '{print $4}')"
158 if [ "$free_disk" -lt 5000 ]; then
159 echo "WARNING: Discourse requires at least 5GB free disk space. This system"
160 echo "does not appear to have sufficient disk space."
161 echo
162 echo "Insufficient disk space may result in problems running your site, and"
163 echo "may not even allow Discourse installation to complete successfully."
164 echo
165 echo "Please free up some space, or expand your disk, before continuing."
166 echo
167 echo "Run \`apt-get autoremove && apt-get autoclean\` to clean up unused"
168 echo "packages and \`./launcher cleanup\` to remove stale Docker containers."
169 exit 1
170 fi
171
172 }
173
174
175 ##
176 ## If we have lots of RAM or lots of CPUs, bump up the defaults to scale better
177 ##
178 scale_ram_and_cpu() {
179
180 local changelog=/tmp/changelog.$PPID
181 # grab info about total system ram and physical (NOT LOGICAL!) CPU cores
182 avail_gb=0
183 avail_cores=0
184 os_type=$(check_OS)
185 if [ "$os_type" == "Darwin" ]; then
186 avail_gb=$(check_osx_memory)
187 avail_cores=`sysctl hw.ncpu | awk '/hw.ncpu:/ {print $2}'`
188 else
189 avail_gb=$(check_linux_memory)
190 avail_cores=$((`awk '/cpu cores/ {print $4;exit}' /proc/cpuinfo`*`sort /proc/cpuinfo | uniq | grep -c "physical id"`))
191 fi
192 echo "Found ${avail_gb}GB of memory and $avail_cores physical CPU cores"
193
194 # db_shared_buffers: 128MB for 1GB, 256MB for 2GB, or 256MB * GB, max 4096MB
195 if [ "$avail_gb" -eq "1" ]
196 then
197 db_shared_buffers=128
198 else
199 if [ "$avail_gb" -eq "2" ]
200 then
201 db_shared_buffers=256
202 else
203 db_shared_buffers=$(( 256 * $avail_gb ))
204 fi
205 fi
206 db_shared_buffers=$(( db_shared_buffers < 4096 ? db_shared_buffers : 4096 ))
207
208 sed -i -e "s/^ #\?db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}MB\"/w $changelog" $data_file
209 if [ -s $changelog ]
210 then
211 echo "setting db_shared_buffers = ${db_shared_buffers}MB"
212 rm $changelog
213 fi
214
215 # UNICORN_WORKERS: 2 * GB for 2GB or less, or 2 * CPU, max 8
216 if [ "$avail_gb" -le "2" ]
217 then
218 unicorn_workers=$(( 2 * $avail_gb ))
219 else
220 unicorn_workers=$(( 2 * $avail_cores ))
221 fi
222 unicorn_workers=$(( unicorn_workers < 8 ? unicorn_workers : 8 ))
223
224 sed -i -e "s/^ #\?UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $web_file
225 if [ -s $changelog ]
226 then
227 echo "setting UNICORN_WORKERS = ${unicorn_workers}"
228 rm $changelog
229 fi
230
231 echo $data_file memory parameters updated.
232 }
233
234
235 ##
236 ## standard http / https ports must not be occupied
237 ##
238 check_ports() {
239 check_port "80"
240 check_port "443"
241 echo "Ports 80 and 443 are free for use"
242 }
243
244
245 ##
246 ## check a port to see if it is already in use
247 ##
248 check_port() {
249
250 local valid=$(netstat -tln | awk '{print $4}' | grep ":${1}\$")
251
252 if [ -n "$valid" ]; then
253 echo "Port ${1} appears to already be in use."
254 echo
255 echo "This will show you what command is using port ${1}"
256 lsof -i tcp:${1} -s tcp:listen
257 echo
258 echo "If you are trying to run Discourse simultaneously with another web"
259 echo "server like Apache or nginx, you will need to bind to a different port"
260 echo
261 echo "See https://meta.discourse.org/t/17247"
262 echo
263 echo "If you are reconfiguring an already-configured Discourse, use "
264 echo
265 echo "./launcher stop app"
266 echo
267 echo "to stop Discourse before you reconfigure it and try again."
268 exit 1
269 fi
270 }
271
272 ##
273 ## read a variable from the config file
274 ##
275 read_config() {
276 config_line=`egrep "^ #?$1:" $web_file`
277 read_config_result=`echo $config_line | awk '{print $2}'`
278 read_config_result=`echo $read_config_result | sed "s/^\([\"']\)\(.*\)\1\$/\2/g"`
279 }
280
281
282
283 ##
284 ## prompt user for typical Discourse config file values
285 ##
286 ask_user_for_config() {
287
288 # NOTE: Defaults now come from standalone.yml
289
290 local changelog=/tmp/changelog.$PPID
291 read_config "DISCOURSE_SMTP_ADDRESS"
292 local smtp_address=$read_config_result
293 # NOTE: if there are spaces between emails, this breaks, but a human should be paying attention
294 read_config "DISCOURSE_DEVELOPER_EMAILS"
295 local developer_emails=$read_config_result
296 read_config "DISCOURSE_SMTP_PASSWORD"
297 local smtp_password=$read_config_result
298 read_config "DISCOURSE_SMTP_PORT"
299 local smtp_port=$read_config_result
300 read_config "DISCOURSE_SMTP_USER_NAME"
301 local smtp_user_name=$read_config_result
302 if [ "$smtp_password" = "pa$$word" ]
303 then
304 smtp_password = ""
305 fi
306 read_config "LETSENCRYPT_ACCOUNT_EMAIL"
307 local letsencrypt_account_email=$read_config_result
308 if [ -z $letsencrypt_account_email ]
309 then
310 letsencrypt_account_email="me@example.com"
311 fi
312 if [ "$letsencrypt_account_email" = "me@example.com" ]
313 then
314 local letsencrypt_status="ENTER to skip"
315 else
316 local letsencrypt_status="Enter 'OFF' to disable."
317 fi
318
319 read_config "DISCOURSE_HOSTNAME"
320 hostname=$read_config_result
321
322 local new_value=""
323 local config_ok="n"
324 local update_ok="y"
325
326 echo ""
327
328 while [[ "$config_ok" == "n" ]]
329 do
330 if [ ! -z $hostname ]
331 then
332 read -p "Hostname for your Discourse? [$hostname]: " new_value
333 if [ ! -z $new_value ]
334 then
335 hostname=$new_value
336 fi
337 fi
338
339 if [ ! -z $developer_emails ]
340 then
341 read -p "Email address for admin account(s)? [$developer_emails]: " new_value
342 if [ ! -z $new_value ]
343 then
344 developer_emails=$new_value
345 fi
346 fi
347
348 if [ ! -z $smtp_address ]
349 then
350 read -p "SMTP server address? [$smtp_address]: " new_value
351 if [ ! -z $new_value ]
352 then
353 smtp_address=$new_value
354 fi
355 fi
356
357 if [ ! -z $smtp_port ]
358 then
359 read -p "SMTP port? [$smtp_port]: " new_value
360 if [ ! -z $new_value ]
361 then
362 smtp_port=$new_value
363 fi
364 fi
365
366 ##
367 ## automatically set correct user name based on common mail providers
368 ##
369 if [ "$smtp_address" == "smtp.sparkpostmail.com" ]
370 then
371 smtp_user_name="SMTP_Injection"
372 fi
373 if [ "$smtp_address" == "smtp.sendgrid.net" ]
374 then
375 smtp_user_name="apikey"
376 fi
377 if [ "$smtp_address" == "smtp.mailgun.org" ]
378 then
379 smtp_user_name="postmaster@$hostname"
380 fi
381
382 if [ ! -z $smtp_user_name ]
383 then
384 read -p "SMTP user name? [$smtp_user_name]: " new_value
385 if [ ! -z "$new_value" ]
386 then
387 smtp_user_name="$new_value"
388 fi
389 fi
390
391 read -p "SMTP password? [$smtp_password]: " new_value
392 if [ ! -z $new_value ]
393 then
394 smtp_password=$new_value
395 fi
396
397 if [ ! -z $letsencrypt_account_email ]
398 then
399 read -p "Let's Encrypt account email? ($letsencrypt_status) [$letsencrypt_account_email]: " new_value
400 if [ ! -z $new_value ]
401 then
402 letsencrypt_account_email=$new_value
403 if [ "${new_value,,}" = "off" ]
404 then
405 letsencrypt_status="ENTER to skip"
406 else
407 letsencrypt_status="Enter 'OFF' to disable."
408 fi
409 fi
410 fi
411
412 if [ "$letsencrypt_status" == "Enter 'OFF' to disable." ]
413 then
414 check_IP_match $hostname
415 fi
416
417 echo -e "\nDoes this look right?\n"
418 echo "Hostname : $hostname"
419 echo "Email : $developer_emails"
420 echo "SMTP address : $smtp_address"
421 echo "SMTP port : $smtp_port"
422 echo "SMTP username : $smtp_user_name"
423 echo "SMTP password : $smtp_password"
424
425 if [ "$letsencrypt_status" == "Enter 'OFF' to disable." ]
426 then
427 echo "Let's Encrypt : $letsencrypt_account_email"
428 fi
429
430
431 echo ""
432 read -p "ENTER to continue, 'n' to try again, Ctrl+C to exit: " config_ok
433 done
434
435 sed -i -e "s/^ DISCOURSE_HOSTNAME:.*/ DISCOURSE_HOSTNAME: $hostname/w $changelog" $web_file
436 if [ -s $changelog ]
437 then
438 rm $changelog
439 else
440 echo "DISCOURSE_HOSTNAME change failed."
441 update_ok="n"
442 fi
443
444 sed -i -e "s/^ DISCOURSE_DEVELOPER_EMAILS:.*/ DISCOURSE_DEVELOPER_EMAILS: \'$developer_emails\'/w $changelog" $web_file
445 if [ -s $changelog ]
446 then
447 rm $changelog
448 else
449 echo "DISCOURSE_DEVELOPER_EMAILS change failed."
450 update_ok="n"
451 fi
452
453 sed -i -e "s/^ DISCOURSE_SMTP_ADDRESS:.*/ DISCOURSE_SMTP_ADDRESS: $smtp_address/w $changelog" $web_file
454 if [ -s $changelog ]
455 then
456 rm $changelog
457 else
458 echo "DISCOURSE_SMTP_ADDRESS change failed."
459 update_ok="n"
460 fi
461
462 sed -i -e "s/^ #\?DISCOURSE_SMTP_PORT:.*/ DISCOURSE_SMTP_PORT: $smtp_port/w $changelog" $web_file
463 if [ -s $changelog ]
464 then
465 rm $changelog
466 else
467 echo "DISCOURSE_SMTP_PORT change failed."
468 update_ok="n"
469 fi
470
471 sed -i -e "s/^ #\?DISCOURSE_SMTP_USER_NAME:.*/ DISCOURSE_SMTP_USER_NAME: $smtp_user_name/w $changelog" $web_file
472 if [ -s $changelog ]
473 then
474 rm $changelog
475 else
476 echo "DISCOURSE_SMTP_USER_NAME change failed."
477 update_ok="n"
478 fi
479
480 sed -i -e "s/^ #\?DISCOURSE_SMTP_PASSWORD:.*/ DISCOURSE_SMTP_PASSWORD: \"${smtp_password/\//\\/}\"/w $changelog" $web_file
481 if [ -s $changelog ]
482 then
483 rm $changelog
484 else
485 echo "DISCOURSE_SMTP_PASSWORD change failed."
486 update_ok="n"
487 fi
488
489 if [ "$letsencrypt_status" = "ENTER to skip" ]
490 then
491 local src='^ #\?- "templates\/web.ssl.template.yml"'
492 local dst=' #\- "templates\/web.ssl.template.yml"'
493 sed -i -e "s/$src/$dst/w $changelog" $web_file
494 if [ ! -s $changelog ]
495 then
496 update_ok="n"
497 echo "web.ssl.template.yml NOT DISABLED--Are you using a non-standard template?"
498 fi
499 local src='^ #\?- "templates\/web.letsencrypt.ssl.template.yml"'
500 local dst=' #- "templates\/web.letsencrypt.ssl.template.yml"'
501
502 sed -i -e "s/$src/$dst/w $changelog" $web_file
503 if [ ! -s $changelog ]
504 then
505 update_ok="n"
506 echo "web.ssl.template.yml NOT DISABLED--Are you using a non-standard template?"
507 fi
508 else # enable let's encrypt
509 echo "Let's Encrypt will be enabled for $letsencrypt_account_email"
510 sed -i -e "s/^ #\?LETSENCRYPT_ACCOUNT_EMAIL:.*/ LETSENCRYPT_ACCOUNT_EMAIL: $letsencrypt_account_email/w $changelog" $web_file
511 if [ -s $changelog ]
512 then
513 rm $changelog
514 else
515 echo "LETSENCRYPT_ACCOUNT_EMAIL change failed."
516 update_ok="n"
517 fi
518 local src='^ #\?- "templates\/web.ssl.template.yml"'
519 local dst=' \- "templates\/web.ssl.template.yml"'
520 sed -i -e "s/$src/$dst/w $changelog" $web_file
521 if [ -s $changelog ]
522 then
523 echo "web.ssl.template.yml enabled"
524 else
525 update_ok="n"
526 echo "web.ssl.template.yml NOT ENABLED--was it on already?"
527 fi
528 local src='^ #\?- "templates\/web.letsencrypt.ssl.template.yml"'
529 local dst=' - "templates\/web.letsencrypt.ssl.template.yml"'
530
531 sed -i -e "s/$src/$dst/w $changelog" $web_file
532 if [ -s $changelog ]
533 then
534 echo "letsencrypt.ssl.template.yml enabled"
535 else
536 update_ok="n"
537 echo "letsencrypt.ssl.template.yml NOT ENABLED -- was it on already?"
538 fi
539 fi
540
541 if [ "$update_ok" == "y" ]
542 then
543 echo -e "\nConfiguration file at $config_file updated successfully!\n"
544 else
545 echo -e "\nUnfortunately, there was an error changing $config_file\n"
546 echo -d "This may happen if you have made unexpected changes."
547 exit 1
548 fi
549 }
550
551 ##
552 ## is our config file valid? Does it have the required fields set?
553 ##
554 validate_config() {
555
556 valid_config="y"
557
558 for x in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
559 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
560 do
561 config_line=`grep "^ $x:" $web_file`
562 local result=$?
563 local default="example.com"
564
565 if (( result == 0 ))
566 then
567 if [[ "$config_line" = *"$default"* ]]
568 then
569 echo "$x left at incorrect default of example.com"
570 valid_config="n"
571 fi
572 config_val=`echo $config_line | awk '{print $2}'`
573 if [ -z $config_val ]
574 then
575 echo "$x was left blank"
576 valid_config="n"
577 fi
578 else
579 echo "$x not present"
580 valid_config="n"
581 fi
582 done
583
584 if [ "$valid_config" != "y" ]; then
585 echo -e "\nSorry, these $web_file settings aren't valid -- can't continue!"
586 echo "If you have unusual requirements, edit $web_file and then: "
587 echo "./launcher bootstrap $app_name"
588 exit 1
589 fi
590 }
591
592
593 ##
594 ## template file names
595 ##
596
597 if [ "$1" == "2container" ]
598 then
599 app_name=web_only
600 data_name=data
601 web_template=samples/web_only.yml
602 data_template=samples/data.yml
603 web_file=containers/$app_name.yml
604 data_file=containers/$data_name.yml
605 else
606 app_name=app
607 data_name=app
608 web_template=samples/standalone.yml
609 data_template=""
610 web_file=containers/$app_name.yml
611 data_file=containers/$app_name.yml
612 fi
613 changelog=/tmp/changelog
614
615 ##
616 ## Check requirements before creating a copy of a config file we won't edit
617 ##
618 check_root
619 check_and_install_docker
620 check_disk_and_memory
621
622 if [ -a "$web_file" ]
623 then
624 echo "The configuration file $web_file already exists!"
625 echo
626 echo ". . . reconfiguring . . ."
627 echo
628 echo
629 DATE=`date +"%Y-%m-%d-%H%M%S"`
630 BACKUP=$app_name.yml.$DATE.bak
631 echo Saving old file as $BACKUP
632 cp $config_file containers/$BACKUP
633 echo "Stopping existing container in 5 seconds or Control-C to cancel."
634 sleep 5
635 ./launcher stop app
636 echo
637 else
638 check_ports
639 cp -v $web_template $web_file
640 if [ "$data_name" == "data" ]
641 then
642 echo "--------------------------------------------------"
643 echo "This multisite setup is currently unsupported. Use at your own risk!"
644 echo "--------------------------------------------------"
645 DISCOURSE_DB_PASSWORD=`date +%s | sha256sum | base64 | head -c 20`
646
647 sed -i -e "s/DISCOURSE_DB_PASSWORD: SOME_SECRET/DISCOURSE_DB_PASSWORD: $DISCOURSE_DB_PASSWORD/w $changelog" $web_file
648 if [ -s $changelog ]
649 then
650 rm $changelog
651 else
652 echo "Problem changing DISCOURSE_DB_PASSWORD" in $web_file
653 fi
654
655 cp -v $data_template $data_file
656 quote=\'
657 sed -i -e "s/password ${quote}SOME_SECRET${quote}/password '$DISCOURSE_DB_PASSWORD'/w $changelog" $data_file
658 if [ -s $changelog ]
659 then
660 rm $changelog
661 else
662 echo "Problem changing DISCOURSE_DB_PASSWORD" in $data_file
663 fi
664 fi
665 fi
666
667 scale_ram_and_cpu
668 ask_user_for_config
669 validate_config
670
671 ##
672 ## if we reach this point without exiting, OK to proceed
673 ## rebuild won't fail if there's nothing to rebuild and does the restart
674 ##
675 echo "Updates successful. Rebuilding in 5 seconds."
676 sleep 5 # Just a chance to ^C in case they were too fast on the draw
677 if [ "$data_name" == "$app_name" ]
678 then
679 echo Building $app_name
680 ./launcher rebuild $app_name
681 else
682 echo Building $data_name now . . .
683 ./launcher rebuild $data_name
684 echo Building $app_name now . . .
685 ./launcher rebuild $app_name
686 fi