view docker/libervia_cont.sh @ 134:4549cf265131

flatpak: install D-Bus .service on each frontend: work around lack of dependency handling in Flatpak by installing D-Bus .service on each frontend. This works because all backend is included in the runtime, but we have to add backend permissions to all frontend, and set --own-name=org.salutatoi.SAT. Furthermore, if one frontend is removed, the symbolic link is removed and the backend will not be launched automatically anymore, even if other frontends are still there. The benefict of this method is that backend has not to be installed manually to use a frontend.
author Goffi <goffi@goffi.org>
date Sun, 15 Jul 2018 16:56:55 +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