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
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 "Setting db_shared_buffers to ${db_shared_buffers}GB\c"
460 sed -i -e "s/^ db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}GB\"/w $changelog" $config_file
463 echo " successfully."
466 echo -e ". . . oops!\n---> db_shared_buffers not found in $config_file. Retaining defaults."
469 # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO)
470 if [ "$avail_gb" -le "2" ]
472 unicorn_workers
=`expr $avail_gb \* 2`
474 unicorn_workers
=`expr $avail_cores \* 2`
477 echo -e "Setting UNICORN_WORKERS to $unicorn_workers\c"
478 sed -i -e "s/^ UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
481 echo " successfully."
484 echo -e ". . . oops!\n---> UNICORN_WORKERS not found in $config_file. Retaining defaults.\n"
490 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
492 if [ ! -z $existing ]
494 echo "Nothing to do, your container has already started!"
498 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
499 if [ ! -z $existing ]
501 echo "starting up existing container"
504 $docker_path start
$config
510 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
511 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
513 IFS
='-p ' read -a array
<<< "$ports"
514 for element
in "${array[@]}"
516 IFS
=':' read -a args
<<< "$element"
517 if [ "${#args[@]}" == "2" ]; then
518 check_ports
"${args[0]}"
519 elif [ "${#args[@]}" == "3" ]; then
520 check_ports
"${args[1]}"
524 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
525 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
533 # get hostname and settings from container configuration
534 for envar
in "${env[@]}"
536 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
538 # use as environment variable
544 hostname
=`hostname -s`
546 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
548 hostname
=$DISCOURSE_HOSTNAME
550 hostname
=$hostname-$config
553 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
554 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
555 # docker added more hostname rules
556 hostname
=${hostname/_/-}
559 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
560 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
567 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 $config_file."
595 # Does your system meet the minimum requirements?
596 if [ "$opt" != "--skip-prereqs" ] ; then
600 # is our configuration file valid?
605 # Is the image available?
606 # If not, pull it here so the user is aware what's happening.
607 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
611 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
612 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
614 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
615 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
617 if [[ ! X
"" = X
"$base_image" ]]; then
626 run_command
="cd /pups &&"
627 if [[ ! "false" = $update_pups ]]; then
628 run_command
="$run_command git pull &&"
630 run_command
="$run_command /pups/bin/pups --stdin"
634 (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 \
635 /bin
/bash
-c "$run_command") \
636 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
638 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
642 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
643 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
651 echo "Successfully bootstrapped, to startup use ./launcher start $config"
656 exec $docker_path exec -it $config /bin
/bash
--login
666 $docker_path logs
$config
687 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
688 echo "Ensuring discourse docker is up to date"
692 LOCAL
=$
(git rev-parse @
)
693 REMOTE
=$
(git rev-parse @
{u
})
694 BASE
=$
(git merge-base @ @
{u
})
696 if [ $LOCAL = $REMOTE ]; then
697 echo "Discourse Docker is up-to-date"
699 elif [ $LOCAL = $BASE ]; then
700 echo "Updating Discourse Docker"
701 git pull ||
(echo 'failed to update' && exit 1)
704 elif [ $REMOTE = $BASE ]; then
705 echo "Your version of Discourse Docker is ahead of origin"
708 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
713 set_existing_container
715 if [ ! -z $existing ]
717 echo "Stopping old container"
720 $docker_path stop
-t 10 $config
726 if [ ! -z $existing ]
728 echo "Removing old container"
731 $docker_path rm $config
741 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)