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 " logs: Docker logs for container"
62 echo " bootstrap: Bootstrap a container for the config based on a template"
63 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
64 echo " memconfig: Configure defaults based on available RAM"
65 echo " cleanup: Remove all containers that have stopped for > 24 hours"
68 echo " --skip-prereqs Don't check prerequisites or resource requirements"
69 echo " --docker-args Extra arguments to pass when running docker"
76 IFS
=.
read -a ver_a
<<< "$1"
77 IFS
=.
read -a ver_b
<<< "$2"
79 while [[ -n $ver_a ]]; do
80 if (( ver_a
> ver_b
)); then
82 elif (( ver_b
> ver_a
)); then
91 return 1 # They are equal
96 # 1. docker daemon running?
97 # we send stderr to /dev/null cause we don't care about warnings,
98 # it usually complains about swap which does not matter
99 test=`$docker_path info 2> /dev/null`
101 if [[ $?
-ne 0 ]] ; then
102 echo "Cannot connect to the docker daemon - verify it is running and you have access"
106 # 2. running aufs or btrfs
107 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
108 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
109 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
110 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
111 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
113 echo "If you wish to continue anyway using your existing unsupported filesystem"
114 echo "read the source code of launcher and figure out how to bypass this."
118 # 3. running recommended docker version
119 test=($
($docker_path --version)) # Get docker version string
120 test=${test[2]//,/} # Get version alone and strip comma if exists
122 # At least minimum version
123 if compare_version
"${docker_min_version}" "${test}"; then
124 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
128 # Recommend best version
129 if compare_version
"${docker_rec_version}" "${test}"; then
130 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
133 # 4. discourse docker image is downloaded
134 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
136 if [ -z "$test" ]; then
138 echo "WARNING: We are about to start downloading the Discourse base image"
139 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
141 echo "Please be patient"
146 # 5. able to attach stderr / out / tty
147 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
148 if [[ "$test" =~
"working" ]] ; then : ; else
149 echo "Your Docker installation is not working correctly"
151 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
160 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
161 if [ "$avail_mem" -lt 900 ]; then
162 resources
="insufficient"
163 echo "WARNING: You do not appear to have sufficient memory to run Discourse."
165 echo "Your system may not work properly, or future upgrades of Discourse may"
166 echo "not complete successfully."
168 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
169 elif [ "$avail_mem" -lt 1800 ]; then
170 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
171 if [ "$total_swap" -lt 1000 ]; then
172 resources
="insufficient"
173 echo "WARNING: You must have at least 1GB of swap when running with less"
174 echo "than 2GB of RAM."
176 echo "Your system may not work properly, or future upgrades of Discourse may"
177 echo "not complete successfully."
179 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
184 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
185 if [ "$free_disk" -lt 5000 ]; then
186 resources
="insufficient"
187 echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse."
189 echo "Insufficient disk space may result in problems running your site, and may"
190 echo "not even allow Discourse installation to complete successfully."
192 echo "Please free up some space, or expand your disk, before continuing."
196 if [ -t 0 ] && [ "$resources" != "ok" ]; then
198 read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources"
203 local valid
=$
(netstat
-tln |
awk '{print $4}' |
grep ":${1}\$")
205 if [ -n "$valid" ]; then
206 echo "Launcher has detected that port ${1} is in use."
208 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."
209 echo "See https://meta.discourse.org/t/17247 for help."
210 echo "To continue anyway, re-run Launcher with --skip-prereqs"
215 if [ "$opt" != "--skip-prereqs" ] ; then
219 if [ "$opt" == "--docker-args" ] ; then
227 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
228 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
230 echo "If you are running Ubuntu Trusty or later, you can try the following:"
233 echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
234 echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-$(lsb_release -sc) main > /etc/apt/sources.list.d/docker.list\""
235 echo "sudo apt-get update"
236 echo "sudo apt-get install docker-engine"
242 read -r -d '' env_ruby
<< 'RUBY'
245 input
= STDIN.readlines.
join
246 yaml
= YAML.load
(input
)
248 if host_run
= yaml
['host_run']
249 params
= yaml
['params'] ||
{}
250 host_run.each
do |run|
252 run
= run.gsub
("$#{k}", v
)
254 STDOUT.
write "#{run}--SEP--"
259 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
261 while [ "$host_run" ] ; do
262 iter
=${host_run%%--SEP--*}
264 echo "Host run: $iter"
267 host_run
="${host_run#*--SEP--}"
273 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
274 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
278 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
279 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
282 set_template_info
() {
284 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
285 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
287 arrTemplates
=(${templates// / })
288 config_data
=$
(cat $config_file)
292 for template
in "${arrTemplates[@]}"
294 [ ! -z $template ] && {
295 input
="$input _FILE_SEPERATOR_ $(cat $template)"
299 # we always want our config file last so it takes priority
300 input
="$input _FILE_SEPERATOR_ $config_data"
302 read -r -d '' env_ruby
<< 'RUBY'
305 input
=STDIN.readlines.
join
306 # default to UTF-8 for the dbs sake
307 env
= {'LANG' => 'en_US.UTF-8'}
308 input.
split('_FILE_SEPERATOR_').each
do |yml|
311 env.merge
!(YAML.load
(yml
)['env'] ||
{})
312 rescue Psych
::SyntaxError
=> e
320 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
323 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
328 if [ "$i" == "*ERROR." ]; then
330 elif [ -n "$i" ]; then
335 if [ "$ok" -ne 1 ]; then
337 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
342 [ -z $docker_path ] && {
346 [ "$command" == "cleanup" ] && {
348 echo "The following command will"
349 echo "- Delete all docker images for old containers"
350 echo "- Delete all stopped and orphan containers"
352 read -p "Are you sure (Y/n): " -n 1 -r && echo
353 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
355 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
356 echo "Starting Cleanup (bytes free $space)"
358 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
360 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
361 echo "Finished Cleanup (bytes free $space)"
373 if [[ ! -e $config_file && $command -ne "memconfig" ]]
375 echo "Config file was not found, ensure $config_file exists"
377 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
381 docker_version
=($
($docker_path --version))
382 docker_version
=${test[2]//,/}
383 restart_policy
=${restart_policy:---restart=always}
385 set_existing_container
(){
386 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
391 set_existing_container
393 if [ ! -z $existing ]
397 $docker_path stop
-t 10 $config
400 echo "$config was not started !"
406 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
407 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
409 if [ -z "$run_image" ]; then
410 run_image
="$local_discourse/$config"
415 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
416 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
418 if [ -z "$boot_command" ]; then
420 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
421 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
423 if [ -z "$no_boot_command" ]; then
424 boot_command
="/sbin/boot"
430 if [ "$opt" != "--skip-prereqs" ] ; then
433 if [ -f $config_file ]
435 cp $config_file $config_file.bak
436 echo "Saving $config_file as $config_file.bak"
438 echo "Creating $config_file from $template_path"
439 if [ ! -f $template_path ]
441 echo "$template_path is missing. Exiting."
444 cp $template_path $config_file
448 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
449 avail_gb
=`expr $(($avail_mem / 950))`
450 avail_cores
=`grep -c processor /proc/cpuinfo`
451 echo "Found $avail_mem (${avail_gb}GB), of memory and $avail_cores cores."
453 # set db_shared_buffers: "128MB" (1GB) or 256MB * GB
454 if [ "$avail_gb" -eq "1" ]
456 db_shared_buffers
="128"
458 db_shared_buffers
=`expr $avail_gb \* 256`
460 echo -e "Setting db_shared_buffers to ${db_shared_buffers}GB\c"
461 sed -i -e "s/^ db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}GB\"/w $changelog" $config_file
464 echo " successfully."
467 echo -e ". . . oops!\n---> db_shared_buffers not found in $config_file. Retaining defaults."
470 # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO)
471 if [ "$avail_gb" -le "2" ]
473 unicorn_workers
=`expr $avail_gb \* 2`
475 unicorn_workers
=`expr $avail_cores \* 2`
478 echo -e "Setting UNICORN_WORKERS to $unicorn_workers\c"
479 sed -i -e "s/^ UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
482 echo " successfully."
485 echo -e ". . . oops!\n---> UNICORN_WORKERS not found in $config_file. Retaining defaults.\n"
491 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
493 if [ ! -z $existing ]
495 echo "Nothing to do, your container has already started!"
499 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
500 if [ ! -z $existing ]
502 echo "starting up existing container"
505 $docker_path start
$config
511 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
512 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
514 IFS
='-p ' read -a array
<<< "$ports"
515 for element
in "${array[@]}"
517 IFS
=':' read -a args
<<< "$element"
518 if [ "${#args[@]}" == "2" ]; then
519 check_ports
"${args[0]}"
520 elif [ "${#args[@]}" == "3" ]; then
521 check_ports
"${args[1]}"
525 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
526 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
534 # get hostname and settings from container configuration
535 for envar
in "${env[@]}"
537 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
539 # use as environment variable
545 hostname
=`hostname -s`
547 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
549 hostname
=$DISCOURSE_HOSTNAME
551 hostname
=$hostname-$config
554 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
555 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
556 # docker added more hostname rules
557 hostname
=${hostname/_/-}
560 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
561 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
568 valid_config_check
(){
570 for x
in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
571 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
573 mail_var
=`grep "^ $x:" $config_file`
577 if [[ $mail_var = *"example.com"* ]]
579 echo "Warning: $x left at incorrect default of example.com"
583 echo "Warning: $x not configured."
587 if [ -t 0 ] && [ "$valid_config" != "y" ]; then
589 read -p "Press ENTER to continue, or Ctrl-C to exit and edit your config."
595 if [ "$opt" != "--skip-prereqs" ] ; then
599 # is our configuration valid?
604 # Is the image available?
605 # If not, pull it here so the user is aware what's happening.
606 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
610 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
611 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
613 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
614 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
616 if [[ ! X
"" = X
"$base_image" ]]; then
625 run_command
="cd /pups &&"
626 if [[ ! "false" = $update_pups ]]; then
627 run_command
="$run_command git pull &&"
629 run_command
="$run_command /pups/bin/pups --stdin"
633 (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 \
634 /bin
/bash
-c "$run_command") \
635 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
637 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
641 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
642 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
650 echo "Successfully bootstrapped, to startup use ./launcher start $config"
655 exec $docker_path exec -it $config /bin
/bash
--login
665 $docker_path logs
$config
686 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
687 echo "Ensuring discourse docker is up to date"
691 LOCAL
=$
(git rev-parse @
)
692 REMOTE
=$
(git rev-parse @
{u
})
693 BASE
=$
(git merge-base @ @
{u
})
695 if [ $LOCAL = $REMOTE ]; then
696 echo "Discourse Docker is up-to-date"
698 elif [ $LOCAL = $BASE ]; then
699 echo "Updating Discourse Docker"
700 git pull ||
(echo 'failed to update' && exit 1)
703 elif [ $REMOTE = $BASE ]; then
704 echo "Your version of Discourse Docker is ahead of origin"
707 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
712 set_existing_container
714 if [ ! -z $existing ]
716 echo "Stopping old container"
719 $docker_path stop
-t 10 $config
725 if [ ! -z $existing ]
727 echo "Removing old container"
730 $docker_path rm $config
740 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)