view docker/libervia_cont.sh @ 103:e69883c1ec30

docker (libervia_cont): added a "status" command: - if libervia container is not running, it exits with error code 1 - if libervia container is running but no server is launched, it exits with error code 2 - if libervia container is running and server is launcher, it exits with error code 0 (success) server detection is done by doing a simple grep on logs, that's not perfectly reliable (ports can be changed in configuration, even if that doesn't really make sense in Docker context) but should be good enough for this purpose.
author Goffi <goffi@goffi.org>
date Sat, 27 Feb 2016 00:45:40 +0100
parents 61ff3bef94a6
children b59491821a8a
line wrap: on
line source

#!/bin/sh

# Libervia container manager
# Copyright (C) 2014-2016 Jérôme Poisson (goffi@goffi.org)

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

VERSION="0.3.0"
APP_NAME="Libervia"
ACCOUNT="salutatoi"

# environment variables that can be used for configuration:
# SAT_CONT_TLS_DIR for TLS certificates directory
# SAT_CONT_DOMAIN for the host name
# SAT_CONT_BACKUP_DIR is the directory where tar.gz backup will be written
# SAT_CONT_DK_EXTRA is used for extra options (used with all containers but sat_data)
# SAT_CONT_PORT_<port> is used to specify port when -p is used, <port> must be an exposed port

CONTAINERS="prosody sat_pubsub salut sat libervia"
TEST_CONT="libervia" # container used to test status

DK_DETACH="-d"
DK_TERM="-ti"

VOLUME_NAME="data"
VOLUME_CONT="$ACCOUNT/$VOLUME_NAME"
VOLUME_ALIAS="sat_data"
DK_VOLUME="--volumes-from $VOLUME_ALIAS"

PUBLIC=0

PROSODY_PORTS="5222 5269 5280 5281"
PORT_5222_NAME="XMPP client to server"
PORT_5269_NAME="XMPP server to server"
PORT_5280_NAME="HTTP Upload"
PORT_5281_NAME="HTTP Upload (HTTPS)"
SAT_PORTS="10143 10125 28915"
PORT_10143_NAME="IMAP server"
PORT_10125_NAME="SMTP server"
PORT_28915_NAME="XMPP file transfer"
LIBERVIA_PORTS="8080 8443"
PORT_8080_NAME="HTTP"
PORT_8443_NAME="HTTPS"
NO_PORT="No public port"

DOCKER_EXE="docker"

USAGE="Usage: $0 [start|stop|restart|status|update|backup|restore|ports|config] [ARGS...]"

HELP_SITE="https://wiki.goffi.org/wiki/Docker/en"
HELP_MUC="sat@chat.jabberfr.org"

CONT_CERT_DIR="/usr/share/sat/certificates"
DEFAULT_TMP_DIR="/tmp/tmp_sat_docker"

get_dyn_var() {
    # get dynamicly variable based on given name

    name=$1
    var_type=$2
    name_upp=$(echo "$name" | tr '[:lower:]' '[:upper:]')
    case $var_type in
        ports) eval echo "\$${name_upp}_PORTS";;
        port_name) eval echo "\$PORT_${name_upp}_NAME";;
    esac
}

list_ports() {
    # list used ports in currently running containers

    for cont in $CONTAINERS; do
        # we get variable name with uppercase container name
        # some magic to get the ports
        ports=$(get_dyn_var $cont ports)

        [ -n "$ports" ] && printf "== $cont ==\n\n"

        for port in $ports; do
            # some magic to get port human readable name
            port_name=$(get_dyn_var $port port_name)
            real_port=$(docker port $cont $port 2>&1)
            if [ $? -ne 0 ]; then
                real_port=$NO_PORT
            fi

            # we now show the ports with nice alignment
            desc="port $port ($port_name):"
            nb_tabs=$((5-${#desc}/8))
            printf "$desc"
            for i in $(seq $nb_tabs); do
                printf "\t"
            done
            printf "$real_port\n"
        done
        [ -n "$ports" ] && printf '\n'
    done
}

public_ports_arg() {
    # create Docker arg to have public ports corresponding to container ports

    if [ $PUBLIC -ne 1 ]; then
        return
    fi
    cont=$1
    ports=$(get_dyn_var $cont ports)
    ARG=""
    for port in $ports; do
        host_port=$(eval echo "\$SAT_CONT_PORT_${port}")
        if [ -z "$host_port" ]; then
            host_port=$port
        fi
        if [ "$host_port" != 0 ]; then
            ARG="$ARG -p $host_port:$port"
        fi
    done
    echo $ARG
}

download_missing() {
    # download images wich are not present locally
    for cont in $CONTAINERS $VOLUME_NAME; do
        image="$ACCOUNT/$cont"
        docker inspect $image:latest > /dev/null 2>&1
        if [ $? -ne 0 ]; then
            printf "$image is not present locally, downloading it\n"
            docker pull $image:latest
            if [ $? -eq 0 ]; then
                printf "\nDownload of latest $image finished\n\n"
            else
                printf "\nError while downloading $image, please check your connection and logs\n"
                return 1
            fi
        fi
    done
}

check_docker() {
    which $DOCKER_EXE > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        printf "Docker is not installed or not accessible, please install it.\nYou can check $HELP_SITE for instructions\n"
        return 1
    fi
}

parse_run_args() {
    # manage arguments for run command

    while [ $# -gt 0 ]; do
        case "$1" in

            -h|--help)
                cat << OPT_END
options available for the run command:

-h, --help                      display this help message
-p, --public                    publish using true ports
-d DOMAIN, --domain DOMAIN      use DOMAIN as domain name
OPT_END
                exit 0
                ;;

            -d|--domain)
                shift
                if [ $# -eq 0 ]; then
                    printf "no domain given, --domain must be followed by a domain\n"
                    exit 1
                fi
                SAT_CONT_DOMAIN="$1"
                shift
                ;;

            -p|--public)
                shift
                PUBLIC=1
                ;;

            *)  printf "Invalid argument, please check \"$0 run --help\"\n"
                exit 1
                ;;
        esac
    done
}

check_docker || exit 1

if [ $# -ge 1 ];then
    case $1 in
        start) CMD=START;;
        stop) CMD=STOP;;
        restart) CMD=RESTART;;
        status) CMD=STATUS;;
        update) CMD=UPDATE;;
        backup) CMD=BACKUP;;
        restore) CMD=RESTORE;;
        ports) CMD=PORTS;;
        config) CMD=CONFIG;;
        -v|--version) printf "$VERSION\n"; exit 0;;
        -h|--help) printf "$USAGE\n\nYou can check $HELP_SITE for instructions, or go to $HELP_MUC XMPP MUC room for help\n"; exit 0;;
        *) echo $USAGE
           exit 1
    esac
    shift
else
    CMD=START
fi

case $CMD in
    START)
        parse_run_args "$@"
        download_missing || exit 1

        printf "Running data container... "
        # we use -d even if data container doesn't stay in background to get id of the container
        docker_id=$(docker run -d --name $VOLUME_ALIAS $VOLUME_CONT 2>&1)
        if [ $? -eq 0 ]; then
            printf "OK ($docker_id)\n"
        else
            echo $docker_id | grep Conflict > /dev/null 2>&1
            if [ $? -eq 0 ]; then
                printf "A data container already exists ($VOLUME_ALIAS), use \"docker ps -a\" to see it\n"
            else
                printf "Error, please check data volume\nerror message: $docker_id\n"
                exit 1
            fi
        fi
        printf "\nRunning Libervia\n\n"
        # we first check if we need to mount TLS directory
        if [ -n "$SAT_CONT_TLS_DIR" ]; then
            printf "$SAT_CONT_TLS_DIR will be used for TLS certificate\n"
            DK_TLS="--volume=$SAT_CONT_TLS_DIR:$CONT_CERT_DIR"
        fi
        for CONT in $CONTAINERS; do
            case $CONT in
                prosody) OPTS="$DK_DETACH $DK_TERM $DK_VOLUME $DK_TLS $(public_ports_arg $CONT) --name prosody"
                         if [ -n "$SAT_CONT_DOMAIN" ]; then
                             OPTS="-e DOMAIN=$SAT_CONT_DOMAIN $OPTS"
                         fi
                         ;;
                sat_pubsub) OPTS="$DK_DETACH $DK_TERM $DK_VOLUME --name sat_pubsub --link=prosody:prosody";;
                salut) OPTS="$DK_DETACH $DK_TERM $DK_VOLUME --name salut --link=prosody:prosody";;
                sat) OPTS="$DK_DETACH $DK_TERM $DK_VOLUME -P $(public_ports_arg $CONT) --name sat --link=prosody:prosody";;
                libervia) OPTS="$DK_DETACH $DK_TERM $DK_VOLUME --volumes-from sat $DK_TLS -P $(public_ports_arg $CONT) --name libervia --link=sat:sat";;
                *) printf "Unkown container $CONT\n"; exit 1
            esac
            printf "Launching $CONT... "
            docker_id=$(docker run $OPTS $SAT_CONT_DK_EXTRA $ACCOUNT/$CONT 2>&1)
            if [ $? -eq 0 ]; then
                printf "OK ($docker_id)\n"
            else
                printf "Error, please check container or ask help on XMPP MUC sat@chat.jabberfr.org\nerror message: $docker_id\n"
                printf "Abandon\n"
                exit 1
            fi
        done
        printf '\nLibervia is launched and should be reachable in a couple of seconds.\nYou can check logs with "docker logs -f libervia" (or any other container name).\n'
        printf "An \"admin\" account has been created, you can check its password on $VOLUME_ALIAS container, in file /home/sat/ADMIN_PWD. Config can be tuned on this container.\n"
        printf 'Below are the ports used to connect, you can go with your browser to Libervia HTTP(S) port.\n\n'
        list_ports
        ;;
    STOP)
        printf "stopping Libervia\n"
        REVERSED=""
        for CONT in $CONTAINERS; do
            REVERSED="$CONT $REVERSED"
        done;
        for CONT in $REVERSED; do
            printf "\nStopping container $CONT"
            docker stop $CONT > /dev/null 2>&1  || printf "... Error while stopping $CONT\n"
            printf "\rDeleting container $CONT"
            docker rm -v $CONT > /dev/null 2>&1 || printf "... Error while removing $CONT\n"
        done
        printf "\n"
        ;;
    RESTART)
        printf "restarting containers...\n"
        "$0" stop && "$0" start "$@"
        ;;
    STATUS)
        docker inspect $TEST_CONT > /dev/null 2>&1
        if [ $? -eq 0 ]; then
            printf "$APP_NAME is running"
            # we test the presence of "starting on xxxx" (where xxxx is one of the exposed ports)
            # this is not really reliable as ports can be changed in configuration
            # but in most case it should work OK
            PORTS_REGEX=$(get_dyn_var $TEST_CONT ports | sed 's/ /\\|/')    
            docker logs $TEST_CONT | grep "starting on \($PORTS_REGEX\)" > /dev/null 2>&1
            if [ $? -ne 0 ]; then
                printf " but no server is started\n"
                exit 2
            fi
            printf "\n"
            exit 0
        else
            printf "$APP_NAME is not running\n"
            exit 1
        fi
        ;;
    UPDATE)
        printf "updating images...\n"
        errors=0
        for CONT in $CONTAINERS data; do
            printf "\n*** updating $CONT ***\n"
            docker pull $ACCOUNT/$CONT:latest
            if [ $? -ne 0 ]; then
                printf "\nError while updating $ACCOUNT/$CONT\n"
                errors=1
            fi
        done
        if [ $errors -eq 0 ]; then
            printf "\n\nImages are up-to-date\n"
        else
            printf "\n\nSome errors happened while updating images\n"
            exit 1
        fi
        ;;
    BACKUP)
        case $# in
            0) SAT_CONT_BACKUP_DIR="$(pwd)";;
            1) SAT_CONT_BACKUP_DIR="$1";;
            *) printf "syntaxe is $0 backup [backup_dir_absolute_path]\n[backup_dir_absolute_path] default to current working dir\n"
               exit 1
               ;;
        esac
        SAT_CONT_BACKUP_DIR=$(echo $SAT_CONT_BACKUP_DIR | sed 's%^\/*\|\/*$%\/%g') # we want to be sure that path starts and finishes with "/"
        filename="sat_data_backup_$(date '+%Y-%m-%d_%H:%M:%S').tar.gz"
        printf "backing up data container to ${SAT_CONT_BACKUP_DIR}${filename}\n\n"
        docker run --rm $DK_VOLUME -v "$SAT_CONT_BACKUP_DIR:/backup" debian:jessie tar zcvf "/backup/$filename" -C / -h volumes
        if [ $? -eq 0 ]; then
            printf "\nBackup finished and available at ${SAT_CONT_BACKUP_DIR}${filename}\n"
        else
            printf "\nBackup Error !\n"
            exit 1
        fi
        ;;
    RESTORE)
        if [ $# -ne 1 ]; then
            printf "syntaxe is $0 restore <backup_file.tar.gz>\n"
            exit 1
        fi
        docker run --name $VOLUME_ALIAS $VOLUME_CONT > /dev/null 2>&1
        if [ $? -ne 0 ]; then
            printf "Can't create $VOLUME_ALIAS container.\n\
If you have an existing one, please remove it with \"docker rm -v $VOLUME_ALIAS\" (/!\\ it will remove *ALL* your data)\n\n\
Hint: you can also rename your current data container with \"docker rename $VOLUME_ALIAS new_container_name\"\n"
            exit 1
        fi

        printf "restoring $1 to $VOLUME_ALIAS container\n\n"
        HOST_BACKUP_DIR=$(dirname "$1")
        HOST_BACKUP_NAME=$(basename "$1")
        if [ $HOST_BACKUP_DIR = "." ]; then
            # workaround for a Docker bug (container crash if "." is used)
            HOST_BACKUP_DIR=$(pwd)
        fi
        docker run --rm $DK_VOLUME -v "$HOST_BACKUP_DIR:/backup" debian:jessie tar zxvf "/backup/$HOST_BACKUP_NAME" -C / -h volumes
        if [ $? -eq 0 ]; then
            printf "\nRestore finished\n"
        else
            printf "\nRestore Error !\n"
            exit 1
        fi
        ;;
    PORTS)
        list_ports
        ;;
    CONFIG)
        case $# in
            0) CONF="libervia";;
            1) CONF="$1";;
            *) CONF="";;
        esac
        case $CONF in
            libervia)
               CONT_CONF_FILE="/home/sat/.config/sat/sat.conf"
               ;;
            prosody)
               CONT_CONF_FILE="/etc/prosody/prosody_sat_cfg/prosody.cfg.lua"
               ;;
            *) printf "\nPlease enter type of configuration to edit (libervia, prosody)\n"
               exit 1
               ;;
        esac

        HOST_CONF_FILE=$(basename $CONT_CONF_FILE)

        printf "\ngetting configuration for $CONF\n"
        # we copy config file to a temporary dit
        # then edit with $EDITOR and put it back

        TMP_DIR=$(mktemp -d 2>/dev/null)
        if [ $? -ne 0 ]; then
            TMP_DIR="$DEFAULT_TMP_DIR"
            mkdir -p "$TMP_DIR"
        fi

        docker cp "$VOLUME_ALIAS:$CONT_CONF_FILE" "$TMP_DIR/$HOST_CONF_FILE"
        "$EDITOR" "$TMP_DIR/$HOST_CONF_FILE"
        if [ $? -eq 0 -a -s "$TMP_DIR/$HOST_CONF_FILE" ]; then
            printf "updating configuration\n"
            docker cp "$TMP_DIR/$HOST_CONF_FILE" "$VOLUME_ALIAS:$CONT_CONF_FILE"
        fi
        rm -rf "$TMP_DIR"
        ;;

    *) printf "Error: unknown command !"
       exit 2
esac