7 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
8 re
='[A-Z/ !@#$%^&*()+~`=]'
9 if [[ $config =~
$re ]];
12 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
19 docker_min_version
='1.6.0'
20 docker_rec_version
='1.6.0'
22 config_file
=containers
/"$config".yml
23 cidbootstrap
=cids
/"$config"_bootstrap.cid
24 local_discourse
=local_discourse
25 image
=discourse
/discourse
:1.0.17
26 docker_path
=`which docker.io || which docker`
27 template_path
=samples
/standalone_template.yml
28 changelog
=/tmp
/changelog
# used to test whether sed did anything
30 if [ "${SUPERVISED}" = "true" ]; then
31 restart_policy
="--restart=no"
33 attach_on_run
="-a stdout -a stderr"
38 if [ -n "$DOCKER_HOST" ]; then
39 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
40 elif [ -x "$(which ip 2>/dev/null)" ]; then
41 docker_ip
=`ip addr show docker0 | \
43 awk '{ split($2,a,"/"); print a[1] }';`
45 docker_ip
=`ifconfig | \
46 grep -B1 "inet addr" | \
47 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
49 awk -F: '{ print $3 }';`
54 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
56 echo " start: Start/initialize a container"
57 echo " stop: Stop a running container"
58 echo " restart: Restart a container"
59 echo " destroy: Stop and remove a container"
60 echo " enter: Use nsenter to enter a container"
61 echo " ssh: Start a bash shell in a running container"
62 echo " logs: Docker logs for container"
63 echo " bootstrap: Bootstrap a container for the config based on a template"
64 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
65 echo " memconfig: Configure defaults based on available RAM"
66 echo " cleanup: Remove all containers that have stopped for > 24 hours"
69 echo " --skip-prereqs Don't check prerequisites or resource requirements"
70 echo " --docker-args Extra arguments to pass when running docker"
77 IFS
=.
read -a ver_a
<<< "$1"
78 IFS
=.
read -a ver_b
<<< "$2"
80 while [[ -n $ver_a ]]; do
81 if (( ver_a
> ver_b
)); then
83 elif (( ver_b
> ver_a
)); then
92 return 1 # They are equal
97 # 1. docker daemon running?
98 # we send stderr to /dev/null cause we don't care about warnings,
99 # it usually complains about swap which does not matter
100 test=`$docker_path info 2> /dev/null`
102 if [[ $?
-ne 0 ]] ; then
103 echo "Cannot connect to the docker daemon - verify it is running and you have access"
107 # 2. running aufs or btrfs
108 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
109 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
110 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
111 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
112 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
114 echo "If you wish to continue anyway using your existing unsupported filesystem"
115 echo "read the source code of launcher and figure out how to bypass this."
119 # 3. running recommended docker version
120 test=($
($docker_path --version)) # Get docker version string
121 test=${test[2]//,/} # Get version alone and strip comma if exists
123 [[ "$test" =~
"0.12.0" ]] && echo "You are running a broken version of Docker, please upgrade ASAP. See: https://meta.discourse.org/t/the-installation-stopped-in-the-middle/16311/ for more details." && exit 1
125 # At least minimum version
126 if compare_version
"${docker_min_version}" "${test}"; then
127 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
131 # Recommend best version
132 if compare_version
"${docker_rec_version}" "${test}"; then
133 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
136 # 4. discourse docker image is downloaded
137 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
139 if [ -z "$test" ]; then
141 echo "WARNING: We are about to start downloading the Discourse base image"
142 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
144 echo "Please be patient"
149 # 5. able to attach stderr / out / tty
150 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
151 if [[ "$test" =~
"working" ]] ; then : ; else
152 echo "Your Docker installation is not working correctly"
154 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
163 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
164 if [ "$avail_mem" -lt 900 ]; then
165 resources
="insufficient"
166 echo "WARNING: You do not appear to have sufficient memory to run Discourse."
168 echo "Your system may not work properly, or future upgrades of Discourse may"
169 echo "not complete successfully."
171 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
172 elif [ "$avail_mem" -lt 1800 ]; then
173 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
174 if [ "$total_swap" -lt 1000 ]; then
175 resources
="insufficient"
176 echo "WARNING: You must have at least 1GB of swap when running with less"
177 echo "than 2GB of RAM."
179 echo "Your system may not work properly, or future upgrades of Discourse may"
180 echo "not complete successfully."
182 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
187 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
188 if [ "$free_disk" -lt 5000 ]; then
189 resources
="insufficient"
190 echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse."
192 echo "Insufficient disk space may result in problems running your site, and may"
193 echo "not even allow Discourse installation to complete successfully."
195 echo "Please free up some space, or expand your disk, before continuing."
199 if [ -t 0 ] && [ "$resources" != "ok" ]; then
201 read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources"
206 local valid
=$
(netstat
-tln |
awk '{print $4}' |
grep ":${1}\$")
208 if [ -n "$valid" ]; then
209 echo "Launcher has detected that port ${1} is in use."
211 echo "If you are trying to run Discourse simultaneously with another web server like Apache or nginx, you will need to bind to a different port."
212 echo "See https://meta.discourse.org/t/17247 for help."
213 echo "To continue anyway, re-run Launcher with --skip-prereqs"
218 if [ "$opt" != "--skip-prereqs" ] ; then
222 if [ "$opt" == "--docker-args" ] ; then
229 local ${ssh_key_locations}
231 ~
/.ssh
/id_ed25519.pub
235 ~core
/.ssh
/authorized_keys
239 for keyfile
in "${ssh_key_locations[@]}"; do
240 if [[ -e ${keyfile} ]] ; then
241 ssh_pub_key
="$(cat ${keyfile})"
252 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
253 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
255 echo "If you are running Ubuntu Trusty or later, you can try the following:"
258 echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
259 echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -sc) main > /etc/apt/sources.list.d/docker.list\""
260 echo "sudo apt-get update"
261 echo "sudo apt-get install docker-engine"
267 read -r -d '' env_ruby
<< 'RUBY'
270 input
= STDIN.readlines.
join
271 yaml
= YAML.load
(input
)
273 if host_run
= yaml
['host_run']
274 params
= yaml
['params'] ||
{}
275 host_run.each
do |run|
277 run
= run.gsub
("$#{k}", v
)
279 STDOUT.
write "#{run}--SEP--"
284 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
286 while [ "$host_run" ] ; do
287 iter
=${host_run%%--SEP--*}
289 echo "Host run: $iter"
292 host_run
="${host_run#*--SEP--}"
298 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
299 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
303 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
304 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
307 set_template_info
() {
309 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
310 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
313 arrTemplates
=(${templates// / })
314 config_data
=$
(cat $config_file)
318 for template
in "${arrTemplates[@]}"
320 [ ! -z $template ] && {
321 input
="$input _FILE_SEPERATOR_ $(cat $template)"
325 # we always want our config file last so it takes priority
326 input
="$input _FILE_SEPERATOR_ $config_data"
328 read -r -d '' env_ruby
<< 'RUBY'
331 input
=STDIN.readlines.
join
332 # default to UTF-8 for the dbs sake
333 env
= {'LANG' => 'en_US.UTF-8'}
334 input.
split('_FILE_SEPERATOR_').each
do |yml|
337 env.merge
!(YAML.load
(yml
)['env'] ||
{})
338 rescue Psych
::SyntaxError
=> e
346 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
349 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
354 if [ "$i" == "*ERROR." ]; then
356 elif [ -n "$i" ]; then
361 if [ "$ok" -ne 1 ]; then
363 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
368 [ -z $docker_path ] && {
372 [ "$command" == "cleanup" ] && {
374 echo "The following command will"
375 echo "- Delete all docker images for old containers"
376 echo "- Delete all stopped and orphan containers"
378 read -p "Are you sure (Y/n): " -n 1 -r && echo
379 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
381 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
382 echo "Starting Cleanup (bytes free $space)"
384 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
386 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
387 echo "Finished Cleanup (bytes free $space)"
399 if [[ ! -e $config_file && $command -ne "memconfig" ]]
401 echo "Config file was not found, ensure $config_file exists"
403 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
407 docker_version
=($
($docker_path --version))
408 docker_version
=${test[2]//,/}
410 if compare_version
"1.2.0" "$docker_version"; then
411 echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
414 restart_policy
=${restart_policy:---restart=always}
417 set_existing_container
(){
418 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
423 set_existing_container
425 if [ ! -z $existing ]
429 $docker_path stop
-t 10 $config
432 echo "$config was not started !"
438 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
439 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
441 if [ -z "$run_image" ]; then
442 run_image
="$local_discourse/$config"
447 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
448 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
450 if [ -z "$boot_command" ]; then
452 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
453 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
455 if [ -z "$no_boot_command" ]; then
456 boot_command
="/sbin/boot"
462 if [ "$opt" != "--skip-prereqs" ] ; then
465 if [ -f $config_file ]
467 cp $config_file $config_file.bak
468 echo "Saving $config_file as $config_file.bak"
470 echo "Creating $config_file from $template_path"
471 if [ ! -f $template_path ]
473 echo "$template_path is missing. Exiting."
476 cp $template_path $config_file
480 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
481 avail_gb
=`expr $(($avail_mem / 950))`
482 avail_cores
=`grep -c processor /proc/cpuinfo`
483 echo "Found $avail_mem (${avail_gb}GB), of memory and $avail_cores cores."
485 # set db_shared_buffers: "128MB" (1GB) or 256MB * GB
486 if [ "$avail_gb" -eq "1" ]
488 db_shared_buffers
="128"
490 db_shared_buffers
=`expr $avail_gb \* 256`
492 echo -e "Setting db_shared_buffers to ${db_shared_buffers}GB\c"
493 sed -i -e "s/^ db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}GB\"/w $changelog" $config_file
496 echo " successfully."
499 echo -e ". . . oops!\n---> db_shared_buffers not found in $config_file. Retaining defaults."
502 # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO)
503 if [ "$avail_gb" -le "2" ]
505 unicorn_workers
=`expr $avail_gb \* 2`
507 unicorn_workers
=`expr $avail_cores \* 2`
510 echo -e "Setting UNICORN_WORKERS to $unicorn_workers\c"
511 sed -i -e "s/^ UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
514 echo " successfully."
517 echo -e ". . . oops!\n---> UNICORN_WORKERS not found in $config_file. Retaining defaults.\n"
523 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
525 if [ ! -z $existing ]
527 echo "Nothing to do, your container has already started!"
531 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
532 if [ ! -z $existing ]
534 echo "starting up existing container"
537 $docker_path start
$config
543 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
544 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
546 IFS
='-p ' read -a array
<<< "$ports"
547 for element
in "${array[@]}"
549 IFS
=':' read -a args
<<< "$element"
550 if [ "${#args[@]}" == "2" ]; then
551 check_ports
"${args[0]}"
552 elif [ "${#args[@]}" == "3" ]; then
553 check_ports
"${args[1]}"
557 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
558 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
566 # get hostname and settings from container configuration
567 for envar
in "${env[@]}"
569 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
571 # use as environment variable
577 hostname
=`hostname -s`
579 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
581 hostname
=$DISCOURSE_HOSTNAME
583 hostname
=$hostname-$config
586 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
587 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
588 # docker added more hostname rules
589 hostname
=${hostname/_/-}
592 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
593 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
601 mail_config_verbose
=0 # 1 prints mail config to stdout
603 for x
in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
604 DISCOURSE_SMTP_PORT DISCOURSE_SMTP_ENABLE_START_TLS
606 mail_var
=`grep "^ $x:" $config_file`
610 if [ "$mail_config_verbose" -eq 1 ]; then
614 echo "Warning: $x not configured."
615 mail_config
="dubious"
618 if [ -t 0 ] && [ "$mail_config" != "ok" ]; then
620 read -p "Press ENTER to continue, or Ctrl-C to exit and fix your mail config."
626 if [ "$opt" != "--skip-prereqs" ] ; then
636 # Is the image available?
637 # If not, pull it here so the user is aware what's happening.
638 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
642 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
643 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
645 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
646 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
648 if [[ ! X
"" = X
"$base_image" ]]; then
657 run_command
="cd /pups &&"
658 if [[ ! "false" = $update_pups ]]; then
659 run_command
="$run_command git pull &&"
661 run_command
="$run_command /pups/bin/pups --stdin"
665 env
=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
667 (exec echo "$input" |
$docker_path run
$user_args $links "${env[@]}" -e DOCKER_HOST_IP
=$docker_ip --cidfile $cidbootstrap -i -a stdin
-a stdout
-a stderr
$volumes $image \
668 /bin
/bash
-c "$run_command") \
669 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
671 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
675 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
676 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
684 echo "Successfully bootstrapped, to startup use ./launcher start $config"
689 exec $docker_path exec -it $config /bin
/bash
--login
693 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
695 if [[ ! -z $existing ]]; then
696 address
="`$docker_path port $config 22`"
697 split=(${address//:/ })
698 exec ssh -o StrictHostKeyChecking
=no root@
${split[0]} -p ${split[1]}
700 echo "$config is not running!"
712 $docker_path logs
$config
733 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
734 echo "Ensuring discourse docker is up to date"
738 LOCAL
=$
(git rev-parse @
)
739 REMOTE
=$
(git rev-parse @
{u
})
740 BASE
=$
(git merge-base @ @
{u
})
742 if [ $LOCAL = $REMOTE ]; then
743 echo "Discourse Docker is up-to-date"
745 elif [ $LOCAL = $BASE ]; then
746 echo "Updating Discourse Docker"
747 git pull ||
(echo 'failed to update' && exit 1)
750 elif [ $REMOTE = $BASE ]; then
751 echo "Your version of Discourse Docker is ahead of origin"
754 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
759 set_existing_container
761 if [ ! -z $existing ]
763 echo "Stopping old container"
766 $docker_path stop
-t 10 $config
772 if [ ! -z $existing ]
774 echo "Removing old container"
777 $docker_path rm $config
787 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)