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