4 echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
6 echo " start: Start/initialize a container"
7 echo " stop: Stop a running container"
8 echo " restart: Restart a container"
9 echo " destroy: Stop and remove a container"
10 echo " enter: Use nsenter to enter a container"
11 echo " logs: Docker logs for container"
12 echo " bootstrap: Bootstrap a container for the config based on a template"
13 echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
14 echo " cleanup: Remove all containers that have stopped for > 24 hours"
17 echo " --skip-prereqs Don't check prerequisites or resource requirements"
18 echo " --docker-args Extra arguments to pass when running docker"
26 # Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
27 re
='[A-Z/ !@#$%^&*()+~`=]'
28 if [[ $config =~
$re ]];
31 echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
38 docker_min_version
='1.6.0'
39 docker_rec_version
='1.6.0'
41 config_file
=containers
/"$config".yml
42 cidbootstrap
=cids
/"$config"_bootstrap.cid
43 local_discourse
=local_discourse
44 image
=discourse
/discourse
:1.0.17
45 docker_path
=`which docker.io || which docker`
46 template_path
=samples
/standalone.yml
47 changelog
=/tmp
/changelog
# used to test whether sed did anything
49 if [ "${SUPERVISED}" = "true" ]; then
50 restart_policy
="--restart=no"
52 attach_on_run
="-a stdout -a stderr"
57 if [ -n "$DOCKER_HOST" ]; then
58 docker_ip
=`sed -e 's/^tcp:\/\/\(.*\):.*$/\1/' <<< "$DOCKER_HOST"`
59 elif [ -x "$(which ip 2>/dev/null)" ]; then
60 docker_ip
=`ip addr show docker0 | \
62 awk '{ split($2,a,"/"); print a[1] }';`
64 docker_ip
=`ifconfig | \
65 grep -B1 "inet addr" | \
66 awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \
68 awk -F: '{ print $3 }';`
74 IFS
=.
read -a ver_a
<<< "$1"
75 IFS
=.
read -a ver_b
<<< "$2"
77 while [[ -n $ver_a ]]; do
78 if (( ver_a
> ver_b
)); then
80 elif (( ver_b
> ver_a
)); then
89 return 1 # They are equal
95 echo "Docker is not installed, you will need to install Docker in order to run Discourse"
96 echo "Please visit https://docs.docker.com/installation/ for instructions on how to do this for your system"
98 echo "If you are running a recent Ubuntu Server, try the following:"
99 echo "sudo apt-get install docker-engine"
106 if [ -z $docker_path ]; then
110 # 1. docker daemon running?
111 # we send stderr to /dev/null cause we don't care about warnings,
112 # it usually complains about swap which does not matter
113 test=`$docker_path info 2> /dev/null`
114 if [[ $?
-ne 0 ]] ; then
115 echo "Cannot connect to the docker daemon - verify it is running and you have access"
119 # 2. running aufs or btrfs
120 test=`$docker_path info 2> /dev/null | grep 'Driver: '`
121 if [[ "$test" =~
[aufs|btrfs|zfs|overlay
] ]] ; then : ; else
122 echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
123 echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
124 echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
126 echo "If you wish to continue anyway using your existing unsupported filesystem"
127 echo "read the source code of launcher and figure out how to bypass this."
131 # 3. running recommended docker version
132 test=($
($docker_path --version)) # Get docker version string
133 test=${test[2]//,/} # Get version alone and strip comma if exists
135 # At least minimum version
136 if compare_version
"${docker_min_version}" "${test}"; then
137 echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
141 # Recommend best version
142 if compare_version
"${docker_rec_version}" "${test}"; then
143 echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
146 # 4. discourse docker image is downloaded
147 test=`$docker_path images | awk '{print $1 ":" $2 }' | grep "$image"`
149 if [ -z "$test" ]; then
151 echo "WARNING: We are about to start downloading the Discourse base image"
152 echo "This process may take anywhere between a few minutes to an hour, depending on your network speed"
154 echo "Please be patient"
159 # 5. able to attach stderr / out / tty
160 test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
161 if [[ "$test" =~
"working" ]] ; then : ; else
162 echo "Your Docker installation is not working correctly"
164 echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
173 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
174 if [ "$avail_mem" -lt 900 ]; then
175 resources
="insufficient"
176 echo "WARNING: You do not appear to have sufficient memory to run Discourse."
178 echo "Your system may not work properly, or future upgrades of Discourse may"
179 echo "not complete successfully."
181 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#create-new-cloud-server"
182 elif [ "$avail_mem" -lt 1800 ]; then
183 total_swap
="$(LANG=C free -m | grep ^Swap: | awk '{print $2}')"
184 if [ "$total_swap" -lt 1000 ]; then
185 resources
="insufficient"
186 echo "WARNING: You must have at least 1GB of swap when running with less"
187 echo "than 2GB of RAM."
189 echo "Your system may not work properly, or future upgrades of Discourse may"
190 echo "not complete successfully."
192 echo "See https://github.com/discourse/discourse/blob/master/docs/INSTALL-cloud.md#set-up-swap-if-needed"
197 free_disk
="$(df /var | tail -n 1 | awk '{print $4}')"
198 if [ "$free_disk" -lt 5000 ]; then
199 resources
="insufficient"
200 echo "WARNING: You must have at least 5GB of *free* disk space to run Discourse."
202 echo "Insufficient disk space may result in problems running your site, and may"
203 echo "not even allow Discourse installation to complete successfully."
205 echo "Please free up some space, or expand your disk, before continuing."
209 if [ -t 0 ] && [ "$resources" != "ok" ]; then
211 read -p "Press ENTER to continue, or Ctrl-C to exit and give your system more resources"
216 local valid
=$
(netstat
-tln |
awk '{print $4}' |
grep ":${1}\$")
218 if [ -n "$valid" ]; then
219 echo "Launcher has detected that port ${1} is in use."
221 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."
222 echo "See https://meta.discourse.org/t/17247 for help."
223 echo "To continue anyway, re-run Launcher with --skip-prereqs"
228 if [ "$opt" != "--skip-prereqs" ] ; then
232 if [ "$opt" == "--docker-args" ] ; then
239 read -r -d '' env_ruby
<< 'RUBY'
242 input
= STDIN.readlines.
join
243 yaml
= YAML.load
(input
)
245 if host_run
= yaml
['host_run']
246 params
= yaml
['params'] ||
{}
247 host_run.each
do |run|
249 run
= run.gsub
("$#{k}", v
)
251 STDOUT.
write "#{run}--SEP--"
256 host_run
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e "$env_ruby"`
258 while [ "$host_run" ] ; do
259 iter
=${host_run%%--SEP--*}
261 echo "Host run: $iter"
264 host_run
="${host_run#*--SEP--}"
270 volumes
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
271 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"`
275 links
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
276 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['links'].map{|l| '--link ' << l['link']['name'] << ':' << l['link']['alias'] << ' '}.join"`
279 set_template_info
() {
281 templates
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
282 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
284 arrTemplates
=(${templates// / })
285 config_data
=$
(cat $config_file)
289 for template
in "${arrTemplates[@]}"
291 [ ! -z $template ] && {
292 input
="$input _FILE_SEPERATOR_ $(cat $template)"
296 # we always want our config file last so it takes priority
297 input
="$input _FILE_SEPERATOR_ $config_data"
299 read -r -d '' env_ruby
<< 'RUBY'
302 input
=STDIN.readlines.
join
303 # default to UTF-8 for the dbs sake
304 env
= {'LANG' => 'en_US.UTF-8'}
305 input.
split('_FILE_SEPERATOR_').each
do |yml|
308 env.merge
!(YAML.load
(yml
)['env'] ||
{})
309 rescue Psych
::SyntaxError
=> e
317 puts env.map
{|k
,v|
"-e\n#{k}=#{v}" }.
join("\n")
320 raw
=`exec echo "$input" | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"`
325 if [ "$i" == "*ERROR." ]; then
327 elif [ -n "$i" ]; then
332 if [ "$ok" -ne 1 ]; then
334 echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
339 if [ -z $docker_path ]; then
343 [ "$command" == "cleanup" ] && {
345 echo "The following command will"
346 echo "- Delete all docker images for old containers"
347 echo "- Delete all stopped and orphan containers"
349 read -p "Are you sure (Y/n): " -n 1 -r && echo
350 if [[ $REPLY =~ ^
[Yy
]$ ||
! $REPLY ]]
352 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
353 echo "Starting Cleanup (bytes free $space)"
355 STATE_DIR
=.
/.gc-state
scripts
/docker-gc
357 space
=$
(df
/var
/lib
/docker |
awk '{ print $4 }' |
grep -v Available
)
358 echo "Finished Cleanup (bytes free $space)"
370 if [[ ! -e $config_file && $command -ne "memconfig" ]]
372 echo "Config file was not found, ensure $config_file exists"
374 echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
378 docker_version
=($
($docker_path --version))
379 docker_version
=${test[2]//,/}
380 restart_policy
=${restart_policy:---restart=always}
382 set_existing_container
(){
383 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
388 set_existing_container
390 if [ ! -z $existing ]
394 $docker_path stop
-t 10 $config
397 echo "$config was not started !"
403 run_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
404 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
406 if [ -z "$run_image" ]; then
407 run_image
="$local_discourse/$config"
412 boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
413 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
415 if [ -z "$boot_command" ]; then
417 no_boot_command
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
418 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
420 if [ -z "$no_boot_command" ]; then
421 boot_command
="/sbin/boot"
426 scale_ram_and_cpu
() {
429 avail_mem
="$(LANG=C free -m | grep '^Mem:' | awk '{print $2}')"
430 avail_gb
=`expr $(($avail_mem / 950))`
431 avail_cores
=`grep -c processor /proc/cpuinfo`
432 echo "Found ${avail_gb}GB of memory and $avail_cores CPU cores"
434 # set db_shared_buffers: "128MB" (1GB) or 256MB * GB
435 if [ "$avail_gb" -eq "1" ]
437 db_shared_buffers
="128"
439 db_shared_buffers
=`expr $avail_gb \* 256`
441 sed -i -e "s/^ #db_shared_buffers:.*/ db_shared_buffers: \"${db_shared_buffers}MB\"/w $changelog" $config_file
444 echo "setting db_shared_buffers = ${db_shared_buffers}MB based on detected CPU/RAM"
449 # set UNICORN_WORKERS: 2*GB or 2*cores (the same on DO)
450 if [ "$avail_gb" -le "2" ]
452 unicorn_workers
=`expr $avail_gb \* 2`
454 unicorn_workers
=`expr \$avail_cores \* 2`
456 sed -i -e "s/^ #UNICORN_WORKERS:.*/ UNICORN_WORKERS: ${unicorn_workers}/w $changelog" $config_file
459 echo "setting UNICORN_WORKERS = ${unicorn_workers} based on detected CPU/RAM"
467 existing
=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
469 if [ ! -z $existing ]
471 echo "Nothing to do, your container has already started!"
475 existing
=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
476 if [ ! -z $existing ]
478 echo "starting up existing container"
481 $docker_path start
$config
487 ports
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
488 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
490 IFS
='-p ' read -a array
<<< "$ports"
491 for element
in "${array[@]}"
493 IFS
=':' read -a args
<<< "$element"
494 if [ "${#args[@]}" == "2" ]; then
495 check_ports
"${args[0]}"
496 elif [ "${#args[@]}" == "3" ]; then
497 check_ports
"${args[1]}"
501 docker_args
=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
502 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
510 # get hostname and settings from container configuration
511 for envar
in "${env[@]}"
513 if [[ $envar == DOCKER_USE_HOSTNAME
* ]] ||
[[ $envar == DISCOURSE_HOSTNAME
* ]]
515 # use as environment variable
521 hostname
=`hostname -s`
523 if [ "$DOCKER_USE_HOSTNAME" = "true" ]
525 hostname
=$DISCOURSE_HOSTNAME
527 hostname
=$hostname-$config
530 # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
531 # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
532 # docker added more hostname rules
533 hostname
=${hostname/_/-}
536 $docker_path run
$user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
537 -e DOCKER_HOST_IP
=$docker_ip --name $config -t $ports $volumes $docker_args $run_image $boot_command
544 valid_config_check
() {
547 for x
in DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD \
548 DISCOURSE_DEVELOPER_EMAILS DISCOURSE_HOSTNAME
550 mail_var
=`grep "^ $x:" $config_file`
554 if [[ $mail_var = *"example.com"* ]]
556 echo "Warning: $x left at incorrect default of example.com"
560 echo "Warning: $x not configured."
564 if [ -t 0 ] && [ "$valid_config" != "y" ]; then
566 echo "Please edit $config_file to add missing settings."
573 # Does your system meet the minimum requirements?
574 if [ "$opt" != "--skip-prereqs" ] ; then
578 # is our configuration file valid?
581 # make minor scaling adjustments for RAM and CPU
586 # Is the image available?
587 # If not, pull it here so the user is aware what's happening.
588 $docker_path history $image >/dev
/null
2>&1 ||
$docker_path pull
$image
592 base_image
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
593 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"`
595 update_pups
=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
596 "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"`
598 if [[ ! X
"" = X
"$base_image" ]]; then
607 run_command
="cd /pups &&"
608 if [[ ! "false" = $update_pups ]]; then
609 run_command
="$run_command git pull &&"
611 run_command
="$run_command /pups/bin/pups --stdin"
615 (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 \
616 /bin
/bash
-c "$run_command") \
617 ||
($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
619 [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
623 $docker_path commit
`cat $cidbootstrap` $local_discourse/$config ||
echo 'FAILED TO COMMIT'
624 $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
632 echo "Successfully bootstrapped, to startup use ./launcher start $config"
637 exec $docker_path exec -it $config /bin
/bash
--login
647 $docker_path logs
$config
663 if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
664 echo "Ensuring discourse docker is up to date"
668 LOCAL
=$
(git rev-parse @
)
669 REMOTE
=$
(git rev-parse @
{u
})
670 BASE
=$
(git merge-base @ @
{u
})
672 if [ $LOCAL = $REMOTE ]; then
673 echo "Discourse Docker is up-to-date"
675 elif [ $LOCAL = $BASE ]; then
676 echo "Updating Discourse Docker"
677 git pull ||
(echo 'failed to update' && exit 1)
680 elif [ $REMOTE = $BASE ]; then
681 echo "Your version of Discourse Docker is ahead of origin"
684 echo "Discourse Docker has diverged source, this is only expected in Dev mode"
689 set_existing_container
691 if [ ! -z $existing ]
693 echo "Stopping old container"
696 $docker_path stop
-t 10 $config
702 if [ ! -z $existing ]
704 echo "Removing old container"
707 $docker_path rm $config
717 (set -x; $docker_path stop
-t 10 $config && $docker_path rm $config) ||
(echo "$config was not found" && exit 0)