X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=launcher;h=bc7f2041f4fca25a758317bf8bb8010ba34c0295;hb=be55cb66c609b460249142aa7bda23e5de8c2d4b;hp=1031f14b2cb533564a068b55ad517bac3088f2c1;hpb=03f50dca16c357467e473ffc5f1b3b06dd1853e9;p=discourse_docker.git diff --git a/launcher b/launcher index 1031f14..bc7f204 100755 --- a/launcher +++ b/launcher @@ -2,26 +2,138 @@ command=$1 config=$2 -config_file="$config".yml +opt=$3 + +cd "$(dirname "$0")" + +docker_min_version='1.2.0' +docker_rec_version='1.2.0' + +config_file=containers/"$config".yml cidfile=cids/"$config".cid cidbootstrap=cids/"$config"_boostrap.cid local_discourse=local_discourse -image=samsaffron/discourse -docker_path=`which docker` +image=samsaffron/discourse:1.0.3 +docker_path=`which docker.io || which docker` + +docker_ip=`/sbin/ifconfig | \ + grep -B1 "inet addr" | \ + awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' | \ + grep docker0 | \ + awk -F: '{ print $3 }';` + usage () { - echo "Usage: launcher COMMAND CONFIG" + 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 base on an image" + echo " mailtest: Test the mail settings in 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 + echo "Options:" + echo " --skip-prereqs Don't check prerequisites" exit 1 } +compare_version() { + declare -a ver_a + declare -a ver_b + IFS=. read -a ver_a <<< "$1" + IFS=. read -a ver_b <<< "$2" + + while [[ -n $ver_a ]]; do + if (( ver_a > ver_b )); then + return 0 + elif (( ver_b > ver_a )); then + return 1 + else + unset ver_a[0] + ver_a=("${ver_a[@]}") + unset ver_b[0] + ver_b=("${ver_b[@]}") + fi + done + return 1 # They are equal +} + +prereqs() { + + # 1. docker daemon running? + test=`$docker_path info >/dev/null` + + if [[ $? -ne 0 ]] ; then + echo "Cannot connect to the docker daemon - verify it is running and you have access" + exit 1 + fi + + # 2. running aufs + test=`$docker_path info 2> /dev/null | grep 'Driver: aufs'` + if [[ "$test" =~ "aufs" ]] ; then : ; else + echo "Your Docker installation is not using aufs, in the past we have had issues with it" + echo "If you are unable to bootstrap your image (or stop it) please report the issue at:" + echo "https://meta.discourse.org/t/discourse-docker-installation-without-aufs/15639" + fi + + # 3. running recommended docker version + 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 + 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 + if compare_version "${docker_rec_version}" "${test}"; then + echo "WARNING: Docker version ${test} deprecated, recommend upgrade to ${docker_rec_version} or newer." + fi + + # 4. able to attach stderr / out / tty + test=`$docker_path run -i --rm -a stdout -a stderr $image echo working` + if [[ "$test" =~ "working" ]] ; then : ; else + echo "Your Docker installation is not working correctly" + echo + 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 + +get_ssh_pub_key() { + if tty -s ; then + if [[ ! -e ~/.ssh/id_rsa.pub && ! -e ~/.ssh/id_dsa.pub ]] ; then + echo "This user has no SSH key, but a SSH key is required to access the Discourse Docker container." + read -p "Generate a SSH key? (Y/n) " -n 1 -r + if [[ $REPLY =~ ^[Nn]$ ]] ; then + echo + echo WARNING: You may not be able to log in to your container. + echo + else + echo + echo Generating SSH key + mkdir -p ~/.ssh && ssh-keygen -f ~/.ssh/id_rsa -t rsa -N '' + echo + fi + fi + fi + + ssh_pub_key="$(cat ~/.ssh/id_rsa.pub 2>/dev/null || cat ~/.ssh/id_dsa.pub)" +} + + install_docker() { echo "Docker is not installed, make sure you are running on the 3.8 kernel" @@ -41,85 +153,272 @@ install_docker() { } set_volumes() { - volumes=`cat $config_file | docker run -rm -i -a stdout -a stdin $image ruby -e \ + volumes=`cat $config_file | $docker_path run --rm -i -a stdout -a stdin $image ruby -e \ "require 'yaml'; puts YAML.load(STDIN.readlines.join)['volumes'].map{|v| '-v ' << v['volume']['host'] << ':' << v['volume']['guest'] << ' '}.join"` } +set_template_info() { + + templates=`cat $config_file | $docker_path run --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 ] && { + input="$input _FILE_SEPERATOR_ $(cat $template)" + } + done + + # we always want our config file last so it takes priority + input="$input _FILE_SEPERATOR_ $config_data" + + read -r -d '' env_ruby << 'RUBY' + require 'yaml' + + input=STDIN.readlines.join + # default to UTF-8 for the dbs sake + env = {'LANG' => 'en_US.UTF-8'} + input.split('_FILE_SEPERATOR_').each do |yml| + yml.strip! + begin + env.merge!(YAML.load(yml)['env'] || {}) + rescue Psych::SyntaxError => e + puts e + puts "*ERROR." + rescue => e + puts yml + p e + end + end + puts env.map{|k,v| "-e\n#{k}=#{v}" }.join("\n") +RUBY + + raw=`exec echo "$input" | $docker_path run --rm -i -a stdin -a stdout $image ruby -e "$env_ruby"` + + env=() + ok=1 + while read i; do + if [ "$i" == "*ERROR." ]; then + ok=0 + elif [ -n "$i" ]; then + env[${#env[@]}]=$i + fi + done <<< "$raw" + + if [ "$ok" -ne 1 ]; then + echo "${env[@]}" + echo "YAML syntax error. Please check your /var/docker/containers/*.yml config files." + exit 1 + fi + echo "Calculated ENV: ${env[@]}" +} + [ -z $docker_path ] && { install_docker } -[ $# -ne 2 ] && { +[ $# -lt 2 ] && { usage } if [ ! -e $config_file ] then echo "Config file was not found, ensure $config_file exists" + echo "" + echo "Available configs ( `cd containers && ls -dm *.yml | tr -s '\n' ' ' | awk '{ gsub(/\.yml/, ""); print }'`)" exit 1 fi -case "$command" in - bootstrap) - templates=`cat $config_file | docker run -rm -i -a stdin -a stdout $image ruby -e \ - "require 'yaml'; puts YAML.load(STDIN.readlines.join)['templates']"` +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=always" +fi + + +run_mailtest(){ + if [ ! -e $config_file ]; then + echo "Config does not exist: $config_file" >&2 + exit 1 + fi + exec scripts/mailtest $config_file +} + +run_stop(){ + if [ ! -e $cidfile ] + then + echo "No cid found" + exit 1 + else + $docker_path stop -t 10 `cat $cidfile` + fi +} + +run_start(){ + + if [ ! -e $cidfile ] + then + echo "No cid found, creating a new container" + ports=`cat $config_file | $docker_path run --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"` + + set_template_info + set_volumes + + existing=`$docker_path ps -a | awk '{ print $1, $(NF) }' | grep "$config$" | awk '{ print $1 }'` + if [ ! -z $existing ] + then + echo "Found an existing container by its name, recovering cidfile, please rerun" + echo $existing > $cidfile + exit 1 + fi + + $docker_path run $restart_policy "${env[@]}" -h "`hostname`-$config" -e DOCKER_HOST_IP=$docker_ip --name $config -t --cidfile $cidfile $ports \ + -d $volumes $local_discourse/$config /sbin/runit + + exit 0 + else + cid=`cat $cidfile` + + if [ -z $cid ] + then + echo "Detected empty cid file, deleting, please re-run" + rm $cidfile + exit 1 + fi + + found=`$docker_path ps -q -a --no-trunc | grep $cid` + if [ -z $found ] + then + echo "Invalid cid file, deleting, please re-run" + rm $cidfile + exit 1 + fi + + echo "cid found, ensuring container is started" + $docker_path start `cat $cidfile` + exit 0 + fi + +} + +run_bootstrap(){ + get_ssh_pub_key + + # Is the image available? + # If not, pull it here so the user is aware what's happening. + $docker_path history $image >/dev/null 2>&1 || $docker_path pull $image + + set_template_info + + base_image=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \ + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['base_image']"` + + update_pups=`cat $config_file | $docker_path run --rm -i -a stdin -a stdout $image ruby -e \ + "require 'yaml'; puts YAML.load(STDIN.readlines.join)['update_pups']"` - arrTemplates=(${templates// / }) - input=$(cat $config_file) + if [[ ! X"" = X"$base_image" ]]; then + image=$base_image + fi + set_volumes - for template in "${arrTemplates[@]}" - do - [ ! -z $template ] && { - input="$input _FILE_SEPERATOR_ $(cat $template)" - } - done + rm -f $cidbootstrap - set_volumes + run_command="cd /pups &&" + if [[ ! "false" = $update_pups ]]; then + run_command="$run_command git pull &&" + fi + run_command="$run_command /pups/bin/pups --stdin" - rm -f $cidbootstrap + echo $run_command - (exec echo "$input" | docker run -cidfile $cidbootstrap -i -a stdin -a stdout -a stderr $volumes $image \ - /bin/bash -c 'cd /pups && git pull && /pups/bin/pups --stdin') \ - || (docker rm `cat $cidbootstrap` && rm $cidbootstrap) + env=("${env[@]}" "-e" "SSH_PUB_KEY=$ssh_pub_key") - [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1 + (exec echo "$input" | $docker_path run "${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) - sleep 5 + [ ! -e $cidbootstrap ] && echo "FAILED TO BOOTSTRAP" && exit 1 - docker commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT' - docker rm `cat $cidbootstrap` && rm $cidbootstrap + sleep 5 - echo "Successfully bootstrappd, to starup use ./launcher start $config" + $docker_path commit `cat $cidbootstrap` $local_discourse/$config || echo 'FAILED TO COMMIT' + $docker_path rm `cat $cidbootstrap` && rm $cidbootstrap +} + +case "$command" in + bootstrap) + run_bootstrap + echo "Successfully bootstrapped, to startup use ./launcher start $config" exit 0 ;; - ssh) + mailtest) + run_mailtest + exit 0 + ;; + + enter) + if [ ! -e $cidfile ] then echo "No cid found" exit 1 - else - cid="`cat $cidfile`" - address="`docker port $cid 22`" - split=(${address//:/ }) - exec ssh root@${split[0]} -p ${split[1]} fi + + if [ ! $UID -eq 0 ] ; + then + echo "enter command must run as root, will attempt to sudo" + echo + fi + + if [ ! -e bin/nsenter ] + then + echo "Downloading nsenter" + $docker_path pull samsaffron/nsenter + ($docker_path run --rm samsaffron/nsenter cat /nsenter > bin/nsenter1) || exit 1 + cp bin/nsenter1 bin/nsenter + chmod +x bin/nsenter + fi + + PID=$($docker_path inspect --format {{.State.Pid}} `cat $cidfile`) + SHELL=/bin/bash sudo -E bin/nsenter --target $PID --mount --uts --ipc --net --pid + + exit 0; ;; - stop) + ssh) if [ ! -e $cidfile ] then echo "No cid found" exit 1 else - docker stop -t 10 `cat $cidfile` - exit 0 + cid="`cat $cidfile`" + address="`$docker_path port $cid 22`" + split=(${address//:/ }) + exec ssh -o StrictHostKeyChecking=no root@${split[0]} -p ${split[1]} fi ;; + stop) + run_stop + exit 0 + ;; + logs) if [ ! -e $cidfile ] @@ -127,28 +426,42 @@ case "$command" in echo "No cid found" exit 1 else - docker logs `cat $cidfile` + $docker_path logs `cat $cidfile` exit 0 fi ;; + restart) + run_stop + run_start + exit 0 + ;; + start) + run_start + exit 0 + ;; - if [ ! -e $cidfile ] - then - echo "No cid found, creating a new container" - ports=`cat $config_file | docker run -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"` + rebuild) + if [ "$(git symbolic-ref --short HEAD)" == "master" ]; then + echo "Updating discourse docker" + git pull || (echo 'failed to update' && exit 1) + fi + if [ -e $cidfile ] + then + echo "Stopping old container" + $docker_path stop -t 10 `cat $cidfile` + fi - set_volumes + run_bootstrap - docker run -name $config -cidfile $cidfile $ports -d $volumes $local_discourse/$config /usr/bin/runsvdir -P /etc/service - exit 0 - else - echo "cid found, ensuring container is started" - docker start `cat $cidfile` - exit 0 + if [ -e $cidfile ] + then + $docker_path rm `cat $cidfile` && rm $cidfile fi + + run_start + exit 0 ;; @@ -156,8 +469,8 @@ case "$command" in if [ -e $cidfile ] then echo "destroying container $cidfile" - docker stop -t 10 `cat $cidfile` - docker rm `cat $cidfile` && rm $cidfile + $docker_path stop -t 10 `cat $cidfile` + $docker_path rm `cat $cidfile` && rm $cidfile exit 0 else echo "nothing to destroy cidfile does not exist"