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.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
97 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
98 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
100 echo "If you are running a recent Ubuntu Server, try the following:"
101 echo "sudo apt-get install docker-engine"
108 if [ -z $docker_path ]; then
112 # 1. docker daemon running?
113 # we send stderr to /dev/null cause we don't care about warnings,
114 # it usually complains about swap which does not matter
115 test=`$docker_path info 2> /dev/null`
116 if [[ $?
-ne 0 ]] ; then
117 echo "Cannot connect to the docker daemon - verify it is running and you have access"
121 # 2. running aufs or btrfs
122 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
123 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
124 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
125 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
126 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
128 echo "If you wish to continue anyway using your existing unsupported filesystem"
129 echo "read the source code of launcher and figure out how to bypass this."
133 # 3. running recommended docker version
134 test=($
($docker_path --version)) # Get docker version string
135 test=${test[2]//,/} # Get version alone and strip comma if exists
137 # At least minimum version
138 if compare_version
"${docker_min_version}" "${test}"; then
139 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
143 # Recommend best version
144 if compare_version
"${docker_rec_version}" "${test}"; then
145 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
148 # 4. discourse docker image is downloaded
149 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
151 if [ -z "$test" ]; then
153 echo "WARNING: We are about to start downloading the Discourse base image"
154 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
156 echo "Please be patient"
161 # 5. able to attach stderr / out / tty
162 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
163 if [[ "$test" =~
"working" ]] ; then : ; else
164 echo "Your Docker installation is not working correctly"
166 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
175 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
176 if [ "$avail_mem" -lt 900 ]; then
177 resources
="insufficient"
178 echo "WARNING: You do not appear to have sufficient memory to run Discourse."
180 echo "Your system may not work properly, or future upgrades of Discourse may"
181 echo "not complete successfully."
183 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
184 elif [ "$avail_mem" -lt 1800 ]; then
185 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
186 if [ "$total_swap" -lt 1000 ]; then
187 resources
="insufficient"
188 echo "WARNING: You must have at least 1GB of swap when running with less"
189 echo "than 2GB of RAM."
191 echo "Your system may not work properly, or future upgrades of Discourse may"
192 echo "not complete successfully."
194 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
199 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
200 if [ "$free_disk" -lt 5000 ]; then
201 resources
="insufficient"
202 echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse."
204 echo "Insufficient disk space may result in problems running your site, and may"
205 echo "not even allow Discourse installation to complete successfully."
207 echo "Please free up some space, or expand your disk, before continuing."
211 if [ -t 0 ] && [ "$resources" != "ok" ]; then
213 read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources"
218 local valid
=$
(netstat
-tln |
awk '{print $4}' |
grep ":${1}\$")
220 if [ -n "$valid" ]; then
221 echo "Launcher has detected that port ${1} is in use."
223 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."
224 echo "See https://meta.discourse.org/t/17247 for help."
225 echo "To continue anyway, re-run Launcher with --skip-prereqs"
230 if [ "$opt" != "--skip-prereqs" ] ; then
234 if [ "$opt" == "--docker-args" ] ; then
241 read -r -d '' env_ruby
<< 'RUBY'
244 input
= STDIN.readlines.
join
245 yaml
= YAML.load
(input
)
247 if host_run
= yaml
['host_run']
248 params
= yaml
['params'] ||
{}
249 host_run.each
do |run|
251 run
= run.gsub
("$#{k}", v
)
253 STDOUT.
write "#{run}--SEP--"
258 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
260 while [ "$host_run" ] ; do
261 iter
=${host_run%%--SEP--*}
263 echo "Host run: $iter"
266 host_run
="${host_run#*--SEP--}"
272 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
273 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
277 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
278 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
281 set_template_info
() {
283 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
284 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
286 arrTemplates
=(${templates// / })
287 config_data
=$
(cat $config_file)
291 for template
in "${arrTemplates[@]}"
293 [ ! -z $template ] && {
294 input
="$input _FILE_SEPERATOR_ $(cat $template)"
298 # we always want our config file last so it takes priority
299 input
="$input _FILE_SEPERATOR_ $config_data"
301 read -r -d '' env_ruby
<< 'RUBY'
304 input
=STDIN.readlines.
join
305 # default to UTF-8 for the dbs sake
306 env
= {'LANG' => 'en_US.UTF-8'}
307 input.
split('_FILE_SEPERATOR_').each
do |yml|
310 env.merge
!(YAML.load
(yml
)['env'] ||
{})
311 rescue Psych
::SyntaxError
=> e
319 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
322 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
327 if [ "$i" == "*ERROR." ]; then
329 elif [ -n "$i" ]; then
334 if [ "$ok" -ne 1 ]; then
336 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
341 if [ -z $docker_path ]; then
345 [ "$command" == "cleanup" ] && {
347 echo "The following command will"
348 echo "- Delete all docker images for old containers"
349 echo "- Delete all stopped and orphan containers"
351 read -p "Are you sure (Y/n): " -n 1 -r && echo
352 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
354 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
355 echo "Starting Cleanup (bytes free $space)"
357 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
359 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
360 echo "Finished Cleanup (bytes free $space)"
372 if [[ ! -e $config_file && $command -ne "memconfig" ]]
374 echo "Config file was not found, ensure $config_file exists"
376 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
380 docker_version
=($
($docker_path --version))
381 docker_version
=${test[2]//,/}
382 restart_policy
=${restart_policy:---restart=always}
384 set_existing_container
(){
385 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
390 set_existing_container
392 if [ ! -z $existing ]
396 $docker_path stop
-t 10 $config
399 echo "$config was not started !"
405 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
406 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
408 if [ -z "$run_image" ]; then
409 run_image
="$local_discourse/$config"
414 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
415 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
417 if [ -z "$boot_command" ]; then
419 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
420 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
422 if [ -z "$no_boot_command" ]; then
423 boot_command
="/sbin/boot"
429 if [ "$opt" != "--skip-prereqs" ] ; then
432 if [ -f $config_file ]
434 cp $config_file $config_file.bak
435 echo "Saving $config_file as $config_file.bak"
437 echo "Creating $config_file from $template_path"
438 if [ ! -f $template_path ]
440 echo "$template_path is missing. Exiting."
443 cp $template_path $config_file
447 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
448 avail_gb
=`expr $(($avail_mem / 950))`
449 avail_cores
=`grep -c processor /proc/cpuinfo`
450 echo "Found $avail_mem (${avail_gb}GB), of memory and $avail_cores cores."
452 # set db_shared_buffers: "128MB" (1GB) or 256MB * GB
453 if [ "$avail_gb" -eq "1" ]
455 db_shared_buffers
="128"
457 db_shared_buffers
=`expr $avail_gb \* 256`
459 echo -e "Recommend setting for db_shared_buffers: ${db_shared_buffers}GB"
460 sed -i -e "s/^ #db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}GB\"/w $changelog" $config_file
463 echo "db_shared_buffers set successfully."
465 else if grep "^ db_shared_buffers:" $config_file
467 echo "db_shared_buffers already set. Unchanged."
469 echo -e ". . . oops!\n---> db_shared_buffers not found in $config_file."
473 # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO)
474 if [ "$avail_gb" -le "2" ]
476 unicorn_workers
=`expr $avail_gb \* 2`
478 unicorn_workers
=`expr $avail_cores \* 2`
481 echo -e "Recommended setting UNICORN_WORKERS: $unicorn_workers"
482 sed -i -e "s/^ #UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
485 echo "UNICORN_WORKERS set successfully."
487 else if grep "^ UNICORN_WORKERS:" $config_file
489 echo "UNICORN_WORKERS already set. Unchanged."
491 echo -e ". . . oops!\n---> UNICORN_WORKERS not found in $config_file.\n"
498 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
500 if [ ! -z $existing ]
502 echo "Nothing to do, your container has already started!"
506 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
507 if [ ! -z $existing ]
509 echo "starting up existing container"
512 $docker_path start
$config
518 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
519 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
521 IFS
='-p ' read -a array
<<< "$ports"
522 for element
in "${array[@]}"
524 IFS
=':' read -a args
<<< "$element"
525 if [ "${#args[@]}" == "2" ]; then
526 check_ports
"${args[0]}"
527 elif [ "${#args[@]}" == "3" ]; then
528 check_ports
"${args[1]}"
532 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
533 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
541 # get hostname and settings from container configuration
542 for envar
in "${env[@]}"
544 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
546 # use as environment variable
552 hostname
=`hostname -s`
554 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
556 hostname
=$DISCOURSE_HOSTNAME
558 hostname
=$hostname-$config
561 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
562 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
563 # docker added more hostname rules
564 hostname
=${hostname/_/-}
567 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
568 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
575 valid_config_check
() {
578 for x
in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
579 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
581 mail_var
=`grep "^ $x:" $config_file`
585 if [[ $mail_var = *"example.com"* ]]
587 echo "Warning: $x left at incorrect default of example.com"
591 echo "Warning: $x not configured."
595 if [ -t 0 ] && [ "$valid_config" != "y" ]; then
597 read -p "Press ENTER to continue, or Ctrl-C to exit and edit $config_file."
603 # Does your system meet the minimum requirements?
604 if [ "$opt" != "--skip-prereqs" ] ; then
608 # is our configuration file valid?
613 # Is the image available?
614 # If not, pull it here so the user is aware what's happening.
615 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
619 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
620 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
622 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
623 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
625 if [[ ! X
"" = X
"$base_image" ]]; then
634 run_command
="cd /pups &&"
635 if [[ ! "false" = $update_pups ]]; then
636 run_command
="$run_command git pull &&"
638 run_command
="$run_command /pups/bin/pups --stdin"
642 (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 \
643 /bin
/bash
-c "$run_command") \
644 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
646 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
650 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
651 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
659 echo "Successfully bootstrapped, to startup use ./launcher start $config"
664 exec $docker_path exec -it $config /bin
/bash
--login
674 $docker_path logs
$config
695 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
696 echo "Ensuring discourse docker is up to date"
700 LOCAL
=$
(git rev-parse @
)
701 REMOTE
=$
(git rev-parse @
{u
})
702 BASE
=$
(git merge-base @ @
{u
})
704 if [ $LOCAL = $REMOTE ]; then
705 echo "Discourse Docker is up-to-date"
707 elif [ $LOCAL = $BASE ]; then
708 echo "Updating Discourse Docker"
709 git pull ||
(echo 'failed to update' && exit 1)
712 elif [ $REMOTE = $BASE ]; then
713 echo "Your version of Discourse Docker is ahead of origin"
716 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
721 set_existing_container
723 if [ ! -z $existing ]
725 echo "Stopping old container"
728 $docker_path stop
-t 10 $config
734 if [ ! -z $existing ]
736 echo "Removing old container"
739 $docker_path rm $config
749 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)