2a0ba78f5fa5b6dce6c3dfa0bb96f6ca5b745151
[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" $config_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" $config_file
225 if [ -s $changelog ]
226 then
227 echo "setting UNICORN_WORKERS = ${unicorn_workers}"
228 rm $changelog
229 fi
230
231 echo $config_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 "If you are trying to run Discourse simultaneously with another web"
256 echo "server like Apache or nginx, you will need to bind to a different port"
257 echo
258 echo "See https://meta.discourse.org/t/17247"
259 echo
260 echo "If you are reconfiguring an already-configured Discourse, use "
261 echo
262 echo "./launcher stop app"
263 echo
264 echo "to stop Discourse before you reconfigure it and try again."
265 exit 1
266 fi
267 }
268
269 ##
270 ## read a variable from the config file
271 ##
272 read_config() {
273
274 config_line=`egrep "^ #?$1:" $config_file`
275 read_config_result=`echo $config_line | awk '{print $2}'`
276 read_config_result=`echo $read_config_result | sed "s/^\([\"']\)\(.*\)\1\$/\2/g"`
277 }
278
279
280
281 ##
282 ## prompt user for typical Discourse config file values
283 ##
284 ask_user_for_config() {
285
286 # NOTE: Defaults now come from standalone.yml
287
288 local changelog=/tmp/changelog.$PPID
289 read_config "DISCOURSE_SMTP_ADDRESS"
290 local smtp_address=$read_config_result
291 # NOTE: if there are spaces between emails, this breaks, but a human should be paying attention
292 read_config "DISCOURSE_DEVELOPER_EMAILS"
293 local developer_emails=$read_config_result
294 read_config "DISCOURSE_SMTP_PASSWORD"
295 local smtp_password=$read_config_result
296 read_config "DISCOURSE_SMTP_PORT"
297 local smtp_port=$read_config_result
298 read_config "DISCOURSE_SMTP_USER_NAME"
299 local smtp_user_name=$read_config_result
300 if [ "$smtp_password" = "pa$$word" ]
301 then
302 smtp_password = ""
303 fi
304 read_config "LETSENCRYPT_ACCOUNT_EMAIL"
305 local letsencrypt_account_email=$read_config_result
306 if [ -z $letsencrypt_account_email ]
307 then
308 letsencrypt_account_email="me@example.com"
309 fi
310 if [ "$letsencrypt_account_email" = "me@example.com" ]
311 then
312 local letsencrypt_status="ENTER to skip"
313 else
314 local letsencrypt_status="Enter 'OFF' to disable."
315 fi
316
317 read_config "DISCOURSE_HOSTNAME"
318 hostname=$read_config_result
319
320 local new_value=""
321 local config_ok="n"
322 local update_ok="y"
323
324 echo ""
325
326 while [[ "$config_ok" == "n" ]]
327 do
328 if [ ! -z $hostname ]
329 then
330 read -p "Hostname for your Discourse? [$hostname]: " new_value
331 if [ ! -z $new_value ]
332 then
333 hostname=$new_value
334 fi
335 fi
336
337 if [ ! -z $developer_emails ]
338 then
339 read -p "Email address for admin account(s)? [$developer_emails]: " new_value
340 if [ ! -z $new_value ]
341 then
342 developer_emails=$new_value
343 fi
344 fi
345
346 if [ ! -z $smtp_address ]
347 then
348 read -p "SMTP server address? [$smtp_address]: " new_value
349 if [ ! -z $new_value ]
350 then
351 smtp_address=$new_value
352 fi
353 fi
354
355 if [ ! -z $smtp_port ]
356 then
357 read -p "SMTP port? [$smtp_port]: " new_value
358 if [ ! -z $new_value ]
359 then
360 smtp_port=$new_value
361 fi
362 fi
363
364 ##
365 ## automatically set correct user name based on common mail providers
366 ##
367 if [ "$smtp_address" == "smtp.sparkpostmail.com" ]
368 then
369 smtp_user_name="SMTP_Injection"
370 fi
371 if [ "$smtp_address" == "smtp.sendgrid.net" ]
372 then
373 smtp_user_name="apikey"
374 fi
375 if [ "$smtp_address" == "smtp.mailgun.org" ]
376 then
377 smtp_user_name="postmaster@$hostname"
378 fi
379
380 if [ ! -z $smtp_user_name ]
381 then
382 read -p "SMTP user name? [$smtp_user_name]: " new_value
383 if [ ! -z "$new_value" ]
384 then
385 smtp_user_name="$new_value"
386 fi
387 fi
388
389 read -p "SMTP password? [$smtp_password]: " new_value
390 if [ ! -z $new_value ]
391 then
392 smtp_password=$new_value
393 fi
394
395 if [ ! -z $letsencrypt_account_email ]
396 then
397 read -p "Let's Encrypt account email? ($letsencrypt_status) [$letsencrypt_account_email]: " new_value
398 if [ ! -z $new_value ]
399 then
400 letsencrypt_account_email=$new_value
401 if [ "${new_value,,}" = "off" ]
402 then
403 letsencrypt_status="ENTER to skip"
404 else
405 letsencrypt_status="Enter 'OFF' to disable."
406 fi
407 fi
408 fi
409
410 if [ "$letsencrypt_status" == "Enter 'OFF' to disable." ]
411 then
412 check_IP_match $hostname
413 fi
414
415 echo -e "\nDoes this look right?\n"
416 echo "Hostname : $hostname"
417 echo "Email : $developer_emails"
418 echo "SMTP address : $smtp_address"
419 echo "SMTP port : $smtp_port"
420 echo "SMTP username : $smtp_user_name"
421 echo "SMTP password : $smtp_password"
422
423 if [ "$letsencrypt_status" == "Enter 'OFF' to disable." ]
424 then
425 echo "Let's Encrypt : $letsencrypt_account_email"
426 fi
427
428
429 echo ""
430 read -p "ENTER to continue, 'n' to try again, Ctrl+C to exit: " config_ok
431 done
432
433 sed -i -e "s/^ DISCOURSE_HOSTNAME:.*/ DISCOURSE_HOSTNAME: $hostname/w $changelog" $config_file
434 if [ -s $changelog ]
435 then
436 rm $changelog
437 else
438 echo "DISCOURSE_HOSTNAME change failed."
439 update_ok="n"
440 fi
441
442 sed -i -e "s/^ DISCOURSE_DEVELOPER_EMAILS:.*/ DISCOURSE_DEVELOPER_EMAILS: \'$developer_emails\'/w $changelog" $config_file
443 if [ -s $changelog ]
444 then
445 rm $changelog
446 else
447 echo "DISCOURSE_DEVELOPER_EMAILS change failed."
448 update_ok="n"
449 fi
450
451 sed -i -e "s/^ DISCOURSE_SMTP_ADDRESS:.*/ DISCOURSE_SMTP_ADDRESS: $smtp_address/w $changelog" $config_file
452 if [ -s $changelog ]
453 then
454 rm $changelog
455 else
456 echo "DISCOURSE_SMTP_ADDRESS change failed."
457 update_ok="n"
458 fi
459
460 sed -i -e "s/^ #\?DISCOURSE_SMTP_PORT:.*/ DISCOURSE_SMTP_PORT: $smtp_port/w $changelog" $config_file
461 if [ -s $changelog ]
462 then
463 rm $changelog
464 else
465 echo "DISCOURSE_SMTP_PORT change failed."
466 update_ok="n"
467 fi
468
469 sed -i -e "s/^ #\?DISCOURSE_SMTP_USER_NAME:.*/ DISCOURSE_SMTP_USER_NAME: $smtp_user_name/w $changelog" $config_file
470 if [ -s $changelog ]
471 then
472 rm $changelog
473 else
474 echo "DISCOURSE_SMTP_USER_NAME change failed."
475 update_ok="n"
476 fi
477
478 sed -i -e "s/^ #\?DISCOURSE_SMTP_PASSWORD:.*/ DISCOURSE_SMTP_PASSWORD: \"${smtp_password/\//\\/}\"/w $changelog" $config_file
479 if [ -s $changelog ]
480 then
481 rm $changelog
482 else
483 echo "DISCOURSE_SMTP_PASSWORD change failed."
484 update_ok="n"
485 fi
486
487 if [ "$letsencrypt_status" = "ENTER to skip" ]
488 then
489 local src='^ #\?- "templates\/web.ssl.template.yml"'
490 local dst=' #\- "templates\/web.ssl.template.yml"'
491 sed -i -e "s/$src/$dst/w $changelog" $config_file
492 if [ ! -s $changelog ]
493 then
494 update_ok="n"
495 echo "web.ssl.template.yml NOT DISABLED--Are you using a non-standard template?"
496 fi
497 local src='^ #\?- "templates\/web.letsencrypt.ssl.template.yml"'
498 local dst=' #- "templates\/web.letsencrypt.ssl.template.yml"'
499
500 sed -i -e "s/$src/$dst/w $changelog" $config_file
501 if [ ! -s $changelog ]
502 then
503 update_ok="n"
504 echo "web.ssl.template.yml NOT DISABLED--Are you using a non-standard template?"
505 fi
506 else # enable let's encrypt
507 echo "Let's Encrypt will be enabled for $letsencrypt_account_email"
508 sed -i -e "s/^ #\?LETSENCRYPT_ACCOUNT_EMAIL:.*/ LETSENCRYPT_ACCOUNT_EMAIL: $letsencrypt_account_email/w $changelog" $config_file
509 if [ -s $changelog ]
510 then
511 rm $changelog
512 else
513 echo "LETSENCRYPT_ACCOUNT_EMAIL change failed."
514 update_ok="n"
515 fi
516 local src='^ #\?- "templates\/web.ssl.template.yml"'
517 local dst=' \- "templates\/web.ssl.template.yml"'
518 sed -i -e "s/$src/$dst/w $changelog" $config_file
519 if [ -s $changelog ]
520 then
521 echo "web.ssl.template.yml enabled"
522 else
523 update_ok="n"
524 echo "web.ssl.template.yml NOT ENABLED--was it on already?"
525 fi
526 local src='^ #\?- "templates\/web.letsencrypt.ssl.template.yml"'
527 local dst=' - "templates\/web.letsencrypt.ssl.template.yml"'
528
529 sed -i -e "s/$src/$dst/w $changelog" $config_file
530 if [ -s $changelog ]
531 then
532 echo "letsencrypt.ssl.template.yml enabled"
533 else
534 update_ok="n"
535 echo "letsencrypt.ssl.template.yml NOT ENABLED -- was it on already?"
536 fi
537 fi
538
539 if [ "$update_ok" == "y" ]
540 then
541 echo -e "\nConfiguration file at $config_file updated successfully!\n"
542 else
543 echo -e "\nUnfortunately, there was an error changing $config_file\n"
544 echo -d "This may happen if you have made unexpected changes."
545 exit 1
546 fi
547 }
548
549 ##
550 ## is our config file valid? Does it have the required fields set?
551 ##
552 validate_config() {
553
554 valid_config="y"
555
556 for x in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
557 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
558 do
559 config_line=`grep "^ $x:" $config_file`
560 local result=$?
561 local default="example.com"
562
563 if (( result == 0 ))
564 then
565 if [[ "$config_line" = *"$default"* ]]
566 then
567 echo "$x left at incorrect default of example.com"
568 valid_config="n"
569 fi
570 config_val=`echo $config_line | awk '{print $2}'`
571 if [ -z $config_val ]
572 then
573 echo "$x was left blank"
574 valid_config="n"
575 fi
576 else
577 echo "$x not present"
578 valid_config="n"
579 fi
580 done
581
582 if [ "$valid_config" != "y" ]; then
583 echo -e "\nSorry, these $config_file settings aren't valid -- can't continue!"
584 echo "If you have unusual requirements, edit $config_file and then: "
585 echo "./launcher bootstrap $app_name"
586 exit 1
587 fi
588 }
589
590
591 ##
592 ## template file names
593 ##
594 app_name=app
595 template_path=samples/standalone.yml
596 config_file=containers/$app_name.yml
597 changelog=/tmp/changelog
598
599 ##
600 ## Check requirements before creating a copy of a config file we won't edit
601 ##
602 check_root
603 check_and_install_docker
604 check_disk_and_memory
605
606 ##
607 ## make a copy of the simple standalone config file
608 ##
609 if [ -a $config_file ]
610 then
611 echo "The configuration file $config_file already exists."
612 echo
613 echo ". . . reconfiguring . . ."
614 echo
615 echo
616 DATE=`date +"%Y-%m-%d-%H%M%S"`
617 BACKUP=$app_name.yml.$DATE.bak
618 echo Saving old file as $BACKUP
619 cp $config_file containers/$BACKUP
620 echo "Stopping existing container in 5 seconds or Control-C to cancel."
621 sleep 5
622 ./launcher stop app
623 echo
624 else
625 check_ports # don't need to check ports if Discourse was already installed
626 cp $template_path $config_file
627 fi
628
629 scale_ram_and_cpu
630 ask_user_for_config
631 validate_config
632
633 ##
634 ## if we reach this point without exiting, OK to proceed
635 ## rebuild won't fail if there's nothing to rebuild and does the restart
636 ##
637 echo "Updates successful. Rebuilding in 5 seconds."
638 sleep 5 # Just a chance to ^C in case they were too fast on the draw
639 time ./launcher rebuild $app_name