#!/bin/bash
+usage () {
+ echo "Usage: launcher COMMAND CONFIG [--skip-prereqs] [--docker-args STRING]"
+ echo "Commands:"
+ echo " start: Start/initialize a container"
+ echo " stop: Stop a running container"
+ echo " restart: Restart a container"
+ echo " destroy: Stop and remove a container"
+ echo " enter: Use nsenter to get a shell into a container"
+ echo " logs: View the Docker logs for a container"
+ echo " bootstrap: Bootstrap a container for the config based on a template"
+ echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
+ echo " cleanup: Remove all containers that have stopped for > 24 hours"
+ echo
+ echo "Options:"
+ echo " --skip-prereqs Don't check launcher prerequisites"
+ echo " --docker-args Extra arguments to pass when running docker"
+ exit 1
+}
+
command=$1
config=$2
-opt=$3
+user_args=""
+
+while [ ${#} -gt 0 ]; do
+ case "${1}" in
+ --debug)
+ DEBUG="1"
+ ;;
+ --skip-prereqs)
+ SKIP_PREREQS="1"
+ ;;
+ --docker-args)
+ user_args="$2"
+ shift
+ ;;
+ esac
+
+ shift 1
+done
# Docker doesn't like uppercase characters, spaces or special characters, catch it now before we build everything out and then find out
re='[A-Z/ !@#$%^&*()+~`=]'
if [[ $config =~ $re ]];
then
- echo
+ echo
echo "ERROR: Config name must not contain upper case characters, spaces or special characters. Correct config name and rerun $0."
echo
exit 1
cd "$(dirname "$0")"
-docker_min_version='1.2.0'
-docker_rec_version='1.2.0'
+docker_min_version='1.8.0'
+docker_rec_version='1.8.0'
+git_min_version='1.8.0'
+git_rec_version='1.8.0'
config_file=containers/"$config".yml
cidbootstrap=cids/"$config"_bootstrap.cid
local_discourse=local_discourse
-image=samsaffron/discourse:1.0.12
+image=discourse/discourse:1.3.2
docker_path=`which docker.io || which docker`
+git_path=`which git`
if [ "${SUPERVISED}" = "true" ]; then
restart_policy="--restart=no"
awk -F: '{ print $3 }';`
fi
-
-usage () {
- echo "Usage: launcher COMMAND CONFIG [--skip-prereqs]"
- echo "Commands:"
- echo " start: Start/initialize a container"
- echo " stop: Stop a running container"
- echo " restart: Restart a container"
- echo " destroy: Stop and remove a container"
- echo " enter: Use nsenter to enter a container"
- echo " ssh: Start a bash shell in a running container"
- echo " logs: Docker logs for container"
- echo " bootstrap: Bootstrap a container for the config based on a template"
- echo " rebuild: Rebuild a container (destroy old, bootstrap, start new)"
- echo " cleanup: Remove all containers that have stopped for > 24 hours"
- echo
- echo "Options:"
- echo " --skip-prereqs Don't check prerequisites"
- echo " --docker-args Extra arguments to pass when running docker"
- exit 1
-}
-
compare_version() {
declare -a ver_a
declare -a ver_b
return 1 # They are equal
}
-prereqs() {
+
+install_docker() {
+ echo "Docker is not installed, you will need to install Docker in order to run Launcher"
+ echo "See https://docs.docker.com/installation/"
+ exit 1
+}
+
+check_prereqs() {
+
+ if [ -z $docker_path ]; then
+ install_docker
+ fi
# 1. docker daemon running?
# we send stderr to /dev/null cause we don't care about warnings,
# it usually complains about swap which does not matter
test=`$docker_path info 2> /dev/null`
-
if [[ $? -ne 0 ]] ; then
echo "Cannot connect to the docker daemon - verify it is running and you have access"
exit 1
echo "Your Docker installation is not using a supported filesystem if we were to proceed you may have a broken install."
echo "aufs is the recommended filesystem you should be using (zfs/btrfs and overlay may work as well)"
echo "You can tell what filesystem you are using by running \"docker info\" and looking at the driver"
- echo ""
- echo "If you wish to continue anyway using your existing unsupported filesystem"
+ echo
+ echo "If you wish to continue anyway using your existing unsupported filesystem, "
echo "read the source code of launcher and figure out how to bypass this."
exit 1
fi
test=($($docker_path --version)) # Get docker version string
test=${test[2]//,/} # Get version alone and strip comma if exists
- [[ "$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
-
- # At least minimum version
+ # At least minimum docker version
if compare_version "${docker_min_version}" "${test}"; then
echo "ERROR: Docker version ${test} not supported, please upgrade to at least ${docker_min_version}, or recommended ${docker_rec_version}"
exit 1
fi
- # Recommend best version
+ # Recommend newer docker version
if compare_version "${docker_rec_version}" "${test}"; then
echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer."
fi
echo
echo "Please be patient"
echo
+ fi
+
+ # 5. running recommended git version
+ test=($($git_path --version)) # Get git version string
+ test=${test[2]//,/} # Get version alone and strip comma if exists
+ # At least minimum version
+ if compare_version "${git_min_version}" "${test}"; then
+ echo "ERROR: Git version ${test} not supported, please upgrade to at least ${git_min_version}, or recommended ${git_rec_version}"
+ exit 1
+ fi
+
+ # Recommend best version
+ if compare_version "${git_rec_version}" "${test}"; then
+ echo "WARNING: Git version ${test} deprecated, recommend upgrade to ${git_rec_version} or newer."
fi
- # 5. able to attach stderr / out / tty
+ # 6. able to attach stderr / out / tty
test=`$docker_path run $user_args -i --rm -a stdout -a stderr $image echo working`
if [[ "$test" =~ "working" ]] ; then : ; else
echo "Your Docker installation is not working correctly"
echo "See: https://meta.discourse.org/t/docker-error-on-bootstrap/13657/18?u=sam"
exit 1
fi
-}
-
-if [ "$opt" != "--skip-prereqs" ] ; then
- prereqs
-fi
-if [ "$opt" == "--docker-args" ] ; then
- user_args=$4
-else
- user_args=""
-fi
-
-get_ssh_pub_key() {
- local ${ssh_key_locations}
- ssh_key_locations=(
- ~/.ssh/id_ed25519.pub
- ~/.ssh/id_ecdsa.pub
- ~/.ssh/id_rsa.pub
- ~/.ssh/id_dsa.pub
- ~core/.ssh/authorized_keys
- )
-
- local $keyfile
- for keyfile in "${ssh_key_locations[@]}"; do
- if [[ -e ${keyfile} ]] ; then
- ssh_pub_key="$(cat ${keyfile})"
- return 0
- fi
- done
-
- return 0
}
-install_docker() {
-
- echo "Docker is not installed, make sure you are running on the 3.8 kernel"
- echo "The best supported Docker release is Ubuntu 12.04.03 for it run the following"
- echo
- echo "sudo apt-get update"
- echo "sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring"
- echo "sudo reboot"
- echo
-
- echo "sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D"
- echo "sudo sh -c \"echo deb https://apt.dockerproject.org/repo ubuntu-precise main > /etc/apt/sources.list.d/docker.list\""
- echo "sudo apt-get update"
- echo "sudo apt-get install docker-engine"
-
- exit 1
-}
+if [ -z "$SKIP_PREREQS" ] ; then
+ check_prereqs
+fi
host_run() {
read -r -d '' env_ruby << 'RUBY'
templates=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
"require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"`
-
arrTemplates=(${templates// / })
config_data=$(cat $config_file)
input="hack: true"
-
for template in "${arrTemplates[@]}"
do
[ ! -z $template ] && {
if [ "$ok" -ne 1 ]; then
echo "${env[@]}"
- echo "YAML syntax error. Please check your /var/discourse/containers/*.yml config files."
+ echo "YAML syntax error. Please check your containers/*.yml config files."
exit 1
fi
}
-[ -z $docker_path ] && {
+if [ -z $docker_path ]; then
install_docker
-}
+fi
[ "$command" == "cleanup" ] && {
echo
if [[ $REPLY =~ ^[Yy]$ || ! $REPLY ]]
then
space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
+ echo "Starting Cleanup (bytes free $space)"
- echo "Starting Cleanup"
-
- if [[ ! -z `docker ps -aq` ]]; then
- docker inspect -f '{{.Id}},{{.State.Running}},{{.State.FinishedAt}}' $(docker ps -qa) | \
- awk -F, 'BEGIN { TIME=strftime("%FT%T.000000000Z",systime()-60*60*24); } $2=="false" && $3 < TIME {print $1;}' | \
- xargs --no-run-if-empty docker rm >/dev/null 2>/dev/null
- fi
-
- docker rmi `docker images -a | grep '<none>' | awk '{print $3}'` 2> /dev/null
-
- let freed=$space-$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
-
- echo $space
- echo $(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
+ STATE_DIR=./.gc-state scripts/docker-gc
+ space=$(df /var/lib/docker | awk '{ print $4 }' | grep -v Available)
+ echo "Finished Cleanup (bytes free $space)"
- output="$freed" | awk '{sum=$1;hum[1024**3]="GB"; hum[1024**2]="MB"; hum[1024]="KB"; for (x=1024**3;x>=1024; x/=1024){ if (sum>=x) { printf "%.2f %s\n",sum/x,hum[x];break }}}'
- [ -z "$output" ] && {
- [[ $freed > 0 ]] && { echo "./launcher cleanup cleared up $freed of disk space."; } || { echo "./launcher cleanup has finished, no files were removed."; }
- } || { echo "./launcher cleanup cleared up $freed of disk space."; }
else
exit 1
fi
exit 0
}
-[ $# -lt 2 ] && {
+if [ -z "$command" -a -z "$config" ]; then
usage
-}
+fi
-if [ ! -e $config_file ]
- then
+if [ ! "$command" == "setup" ]; then
+ if [[ ! -e $config_file ]]; then
echo "Config file was not found, ensure $config_file exists"
- echo ""
+ echo
echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)"
exit 1
+ fi
fi
-
docker_version=($($docker_path --version))
docker_version=${test[2]//,/}
-
-if compare_version "1.2.0" "$docker_version"; then
- echo "We recommend you upgrade docker, the version you are running has no restart policies, on reboot your container may not start up"
- restart_policy=""
-else
- restart_policy=${restart_policy:---restart=always}
-fi
+restart_policy=${restart_policy:---restart=always}
set_existing_container(){
existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
}
-run_stop(){
+run_stop() {
set_existing_container
fi
}
-run_start(){
+set_run_image() {
+ run_image=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
+ "require 'yaml'; puts YAML.load(STDIN.readlines.join)['run_image']"`
+
+ if [ -z "$run_image" ]; then
+ run_image="$local_discourse/$config"
+ fi
+}
+
+set_boot_command() {
+ boot_command=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
+ "require 'yaml'; puts YAML.load(STDIN.readlines.join)['boot_command']"`
+
+ if [ -z "$boot_command" ]; then
+
+ no_boot_command=`cat $config_file | $docker_path run $user_args --rm -i -a stdin -a stdout $image ruby -e \
+ "require 'yaml'; puts YAML.load(STDIN.readlines.join)['no_boot_command']"`
+
+ if [ -z "$no_boot_command" ]; then
+ boot_command="/sbin/boot"
+ fi
+ fi
+}
+
+run_start() {
existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
echo $existing
if [ ! -z $existing ]
then
echo "Nothing to do, your container has already started!"
- exit 1
+ exit 0
fi
existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
fi
host_run
+
ports=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
- "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| '-p ' << p.to_s << ' '}.join"`
+ "require 'yaml'; puts YAML.load(STDIN.readlines.join)['expose'].map{|p| \"-p #{p}\"}.join(' ')"`
+
+ docker_args=`cat $config_file | $docker_path run $user_args --rm -i -a stdout -a stdin $image ruby -e \
+ "require 'yaml'; puts YAML.load(STDIN.readlines.join)['docker_args']"`
set_template_info
set_volumes
set_links
+ set_run_image
+ set_boot_command
+
+ # get hostname and settings from container configuration
+ for envar in "${env[@]}"
+ do
+ if [[ $envar == DOCKER_USE_HOSTNAME* ]] || [[ $envar == DISCOURSE_HOSTNAME* ]]
+ then
+ # use as environment variable
+ eval $envar
+ fi
+ done
(
- hostname=`hostname`
+ hostname=`hostname -s`
+ # overwrite hostname
+ if [ "$DOCKER_USE_HOSTNAME" = "true" ]
+ then
+ hostname=$DISCOURSE_HOSTNAME
+ else
+ hostname=$hostname-$config
+ fi
+
+ # we got to normalize so we only have allowed strings, this is more comprehensive but lets see how bash does first
+ # hostname=`$docker_path run $user_args --rm $image ruby -e 'print ARGV[0].gsub(/[^a-zA-Z-]/, "-")' $hostname`
+ # docker added more hostname rules
+ hostname=${hostname//_/-}
+
+ mac_address="--mac-address $($docker_path run $user_args -i --rm -a stdout -a stderr $image /bin/sh -c "echo $hostname | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/'")"
+
set -x
- $docker_path run $user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname-$config" \
- -e DOCKER_HOST_IP=$docker_ip --name $config -t $ports $volumes $local_discourse/$config /sbin/boot
+ $docker_path run $user_args $links $attach_on_run $restart_policy "${env[@]}" -h "$hostname" \
+ -e DOCKER_HOST_IP=$docker_ip --name $config -t $ports $volumes $mac_address $docker_args \
+ $run_image $boot_command
)
exit 0
}
-run_bootstrap(){
- host_run
+run_bootstrap() {
- get_ssh_pub_key
+ # I got no frigging clue what this does, ask Sam Saffron. It RUNS STUFF ON THE HOST I GUESS?
+ host_run
# Is the image available?
# If not, pull it here so the user is aware what's happening.
echo $run_command
- env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key")
-
+ unset FAILED
(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 \
- /bin/bash -c "$run_command") \
- || ($docker_path rm `cat $cidbootstrap` && rm $cidbootstrap)
+ /bin/bash -c "$run_command") || FAILED="TRUE"
+
+ if [[ $FAILED = "TRUE" ]]; then
+ if [[ ! -z "$DEBUG" ]]; then
+ $docker_path commit `cat $cidbootstrap` $local_discourse/$config-debug || echo 'FAILED TO COMMIT'
+ echo "** DEBUG ** Maintaining image for diagnostics $local_discourse/$config-debug"
+ fi
- [ ! -e $cidbootstrap ] && echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one" && exit 1
+ $docker_path rm `cat $cidbootstrap`
+ rm $cidbootstrap
+ echo "** FAILED TO BOOTSTRAP ** please scroll up and look for earlier error messages, there may be more than one"
+ exit 1
+ fi
sleep 5
$docker_path rm `cat $cidbootstrap` && rm $cidbootstrap
}
+
+
case "$command" in
bootstrap)
run_bootstrap
;;
enter)
- exec $docker_path exec -it $config /bin/bash
- ;;
-
- ssh)
- existing=`$docker_path ps | awk '{ print $1, $(NF) }' | grep " $config$" | awk '{ print $1 }'`
-
- if [[ ! -z $existing ]]; then
- address="`$docker_path port $config 22`"
- split=(${address//:/ })
- exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]}
- else
- echo "$config is not running!"
- exit 1
- fi
+ exec $docker_path exec -it $config /bin/bash --login
;;
stop)
rebuild)
if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then
- echo "Ensuring discourse docker is up to date"
+ echo "Ensuring launcher is up to date"
git remote update
BASE=$(git merge-base @ @{u})
if [ $LOCAL = $REMOTE ]; then
- echo "Discourse Docker is up-to-date"
+ echo "Launcher is up-to-date"
elif [ $LOCAL = $BASE ]; then
- echo "Updating Discourse Docker"
+ echo "Updating Launcher"
git pull || (echo 'failed to update' && exit 1)
exec /bin/bash $0 $@
elif [ $REMOTE = $BASE ]; then
- echo "Your version of Discourse Docker is ahead of origin"
+ echo "Your version of Launcher is ahead of origin"
else
- echo "Discourse Docker has diverged source, this is only expected in Dev mode"
+ echo "Launcher has diverged source, this is only expected in Dev mode"
fi
fi