view docker/libervia_cont.sh @ 129:bcfe5f20d6e8

screenshot (0.7): renamed ticket screenshot after typo
author Goffi <goffi@goffi.org>
date Thu, 05 Jul 2018 14:10:30 +0200
parents 37e100fd30ef
children
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
MAINT_CONT="debian:jessie" # container used for maintenance

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"
PORT_10143_NAME="IMAP server"
PORT_10125_NAME="SMTP server"
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|stats] [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"

eprintf()
{
   >&2 printf "$@"
}

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
                eprintf "\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
}

check_docker_version() {
    # check if current docker version is greater than or equal to the requested one
    wanted_major=$1
    wanted_minor=$2
    wanted_rev=$3
    raw=$(docker --version | grep -o '\([0-9]\+\)\.[0-9]\+\.[0-9]\+')
    docker_major=$(echo "$raw" |  cut -d . -f 1)
    docker_minor=$(echo "$raw" |  cut -d . -f 2)
    docker_rev=$(echo "$raw" |  cut -d . -f 3)
    for name in major minor rev; do
        docker_val=$(eval echo \$docker_$name)
        wanted_val=$(eval echo \$wanted_$name)
        if [ $docker_val -gt $wanted_val ]; then
            return 0
        fi
        if [ $docker_val -lt $wanted_val ]; then
            return 1
        fi
    done

    # wanted version and docker version are the same
    return 0
}

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;;
        stats) CMD=STATS;;
        -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
                eprintf "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";;
                *) eprintf "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
                eprintf "Error, please check container or ask help on XMPP MUC sat@chat.jabberfr.org\nerror message: $docker_id\n"
                eprintf "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  || eprintf "... Error while stopping $CONT\n"
            printf "\rDeleting container $CONT"
            docker rm -v $CONT > /dev/null 2>&1 || eprintf "... 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
                eprintf "\nError while updating $ACCOUNT/$CONT\n"
                errors=1
            fi
        done
        if [ $errors -eq 0 ]; then
            printf "\n\nImages are up-to-date\n"
        else
            eprintf "\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" $MAINT_CONT tar zcvf "/backup/$filename" -C / -h volumes
        if [ $? -eq 0 ]; then
            printf "\nBackup finished and available at ${SAT_CONT_BACKUP_DIR}${filename}\n"
        else
            eprintf "\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
            eprintf "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" $MAINT_CONT tar zxvf "/backup/$HOST_BACKUP_NAME" -C / -h volumes
        if [ $? -eq 0 ]; then
            printf "\nRestore finished\n"
        else
            eprintf "\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/"
        "$EDITOR" "$TMP_DIR/$HOST_CONF_FILE"
        if [ $? -eq 0 -a -s "$TMP_DIR/$HOST_CONF_FILE" ]; then
            printf "updating configuration\n"
            check_docker_version 1 8 0
            if [ $? -eq 0 ]; then
                docker cp "$TMP_DIR/$HOST_CONF_FILE" "$VOLUME_ALIAS:$CONT_CONF_FILE"
            else
                eprintf "Old Docker version detected, using workaround, please update!\n"
                docker run --rm $DK_VOLUME -v "$TMP_DIR:/tmp_config" $MAINT_CONT /bin/cp -f "/tmp_config/$HOST_CONF_FILE" "$CONT_CONF_FILE"
            fi
            # "docker cp" copy file on container as root, if an option is available later to change this behaviour,
            # the following operation could be removed
            printf "ownership fix..."
            docker run --rm $DK_VOLUME $MAINT_CONT /bin/chown 1000:1000 "$CONT_CONF_FILE"
            printf "done\n"
        fi
        rm -rf "$TMP_DIR"
        ;;
    STATS)
        if [ -n "$1" -a "$1" != "--no-stream" ]; then
            printf "usage: $0 stats [--no-stream]\n"
            exit 1
        fi
        docker stats $1 $CONTAINERS
        ;;
    *) eprintf "Error: unknown command !"
       exit 2
esac