#!/bin/bash
set -eu

DEFAULT_POSTSTART="exec sleep 1"

usage() {
    echo "Usage: os-svc-daemon [ -ph ] [ -s POSTSTART ] [ -e ENV ] -n SERVICENAME -u RUNAS [ -c RUNCMD -- [arg [arg...]]]"
    echo ""
    echo "SERVICENAME, RUNAS, RUNCMD, and POSTSTART can be set via the"
    echo "environment as well. Command line arguments will override"
    echo "environment variables. By default this will create a python logging"
    echo "configuration file in /etc/os-logging/servicename.conf"
    echo ""
    echo "  -a             Use alternate svc-map instead of map-services"
    echo "  -h             Show help and exit"
    echo "  -p             Print the job file instead of writing to disk"
    echo "  -l             Create neither a python logging.conf nor pass --log-config-append argument to command."
    echo "  -d [NAME]      Specify the name of the runtime directory, which will be"
    echo "                 /var/run/[NAME]"
    echo "  -s POSTSTART   post_start will be added to the upstart job. Ignored with systemd."
    echo "                 default: $DEFAULT_POSTSTART"
    echo "  -e ENV         Environment name=value entries to set in the service/job"
    echo "  -n SERVICENAME Name of job/service file."
    echo "  -i INSTALLDIR  Optional: virtualenv installation directory. Defaults to: /opt/stack/venvs/<SERVICENAME>"
    echo "  -u RUNAS       User to run main executable as."
    echo "  -c RUNCMD      Command to execute. Must stay in foreground."
    echo "     arg...      Arguments will be passed to COMMAND"
    echo ""
}

# Can be set in environment now
SERVICENAME=${SERVICENAME:-""}
INSTALLDIR=
RUNAS=${RUNAS:-""}
RUNCMD=${RUNCMD:-""}
ENV=${ENV:-""}
DISABLE_LOGGING_CONF=
CREATE_DIR_NAME=${CREATE_DIR_NAME:-""}
# The default helps avoid race with daemon listening. http://pad.lv/1179766
POSTSTART=${POSTSTART:-$DEFAULT_POSTSTART}
MAPPING_COMMAND=map-services

print_only() {
    cat
}

print_to_file() {
    cat > $1
}

append_to_file() {
    cat >> $1
}

OUTPUT=print_to_file
APPEND=append_to_file

nshift=0
while getopts "aplhd:s:n:i:u:c:e:" opt; do
    case "$opt" in
        n) SERVICENAME=$OPTARG;;
        i) INSTALLDIR=$OPTARG;;
        u) RUNAS=$OPTARG;;
        c) RUNCMD=$OPTARG;;
        s) POSTSTART=$OPTARG;;
        e) ENV=$OPTARG;;
        a) MAPPING_COMMAND=svc-map;;
        p) OUTPUT=print_only; APPEND=print_only;;
        l) DISABLE_LOGGING_CONF="1";;
        d) CREATE_DIR_NAME=$OPTARG;;
        h) usage; exit 0;;
        \?) usage; exit 1;;
        :) usage; exit 1;;
    esac
done

shift $(($OPTIND-1))
if [ -z "$SERVICENAME" ] || [ -z "$RUNAS" ] ; then
    if [ $# -lt 3 ] ; then
        usage
        exit 1
    fi
fi

function deprecated_posarg_warning {
    echo "WARNING: Setting $1 via positional argument is deprecated and will be removed in a future release."
}

# Compatibility with old style passing w/o switches
if [ -z "$SERVICENAME" ]; then
    SERVICENAME=$1
    shift
    deprecated_posarg_warning "SERVICENAME"
fi
if [ -z "$RUNAS" ]; then
    RUNAS=$1
    shift
    deprecated_posarg_warning "RUNAS"
fi
if [ -z "$RUNCMD" ]; then
    CHECK=${1:-""}
    if [ -n "$CHECK" ]; then
        RUNCMD=$1
        shift
        deprecated_posarg_warning "CHECK"
    fi
fi

# if INSTALLDIR isn't set use /opt/stack/venvs/RUNAS
# NOTE: this was our default before adding the -i option
if [ -z "$INSTALLDIR" ]; then
    INSTALLDIR="/opt/stack/venvs/$RUNAS"
fi

if [ -z "$DISABLE_LOGGING_CONF" ]; then
    # Set up service-specific logging config
    LOGGING_CONFIG="/etc/os-logging/$SERVICENAME.config"
    mkdir /etc/os-logging || true
    $OUTPUT $LOGGING_CONFIG <<EOF
[loggers]
keys=root

[handlers]
keys=syslog

[formatters]
keys=normal

[logger_root]
handlers=syslog

[handler_syslog]
class=handlers.SysLogHandler
args=('/dev/log', handlers.SysLogHandler.LOG_USER)
formatter=normal

[formatter_normal]
format=$SERVICENAME: %(asctime)s %(levelname)s %(message)s
EOF
fi

function install_upstart {
    local name=$1
    local install_dir=$2
    local user=$3
    local dirname=${4:-$user}
    local cmd=$5
    shift; shift; shift; shift; shift
    local args=$*

    local env_entries=''
    if [ -n "$ENV" ]; then
        local env_pad=" $ENV"
        env_entries=${env_pad// /
env }
    fi
    local target_file="/etc/init/$name.conf"

    if [ -z "$DISABLE_LOGGING_CONF" ]; then
        args="--log-config-append $LOGGING_CONFIG $args"
    fi

    $OUTPUT $target_file <<EOF
start on runlevel [2345]
stop on runlevel [016]
$env_entries

env OS_SVC_ENABLE_CONTROL=1
export OS_SVC_ENABLE_CONTROL

pre-start script
  mkdir -p /var/run/$dirname
  chown -R $user:$user /var/run/$dirname
end script
EOF

    if [ -n "$cmd" ]; then
        $APPEND $target_file <<EOF

respawn
# the default post-start of 1 second sleep delays respawning enough to
# not hit the default of 10 times in 5 seconds. Make it 2 times in 10s.
respawn limit 2 10

exec start-stop-daemon --start -c $user --exec $install_dir/bin/$cmd -- $args

post-start $POSTSTART
EOF
    fi
}

function install_systemd {
    local name=$1
    local install_dir=$2
    local user=$3
    local cmd=$4
    shift; shift; shift; shift;
    local args=$*

    local env_entries=''
    if [ -n "$ENV" ]; then
        local env_pad=" $ENV"
        env_entries=${env_pad// /
Environment=}
    fi

    if [ -z "$DISABLE_LOGGING_CONF" ]; then
        args="--log-config-append $LOGGING_CONFIG $args"
    fi

    $OUTPUT /lib/systemd/system/$name.service <<EOF
[Unit]
Description=$name Service
After=os-refresh-config.service
Requires=$name-create-dir.service

[Service]
ExecStart=$install_dir/bin/$cmd $args
User=$user
$env_entries

[Install]
WantedBy=multi-user.target
Alias=$name.service
EOF

}

function install_create_dir_systemd {
    local name="$($MAPPING_COMMAND "$1")"
    local user=$2
    local dirname=${3:-$user}

    $OUTPUT /lib/systemd/system/$name-create-dir.service <<EOF
[Unit]
Description=Create /var/run/$dirname

[Service]
ExecStartPre=/bin/mkdir -p /var/run/$dirname
ExecStartPre=/usr/local/bin/restore-selinux-file-context /var/run/$dirname
ExecStart=/bin/chown -R $user:$user /var/run/$dirname

[Install]
RequiredBy=$name.service
EOF

}

# TODO: SysV init fallback support
DIB_INIT_SYSTEM=$(dib-init-system)
if [ "$DIB_INIT_SYSTEM" == "upstart" ]; then
    install_upstart $SERVICENAME $INSTALLDIR $RUNAS "$CREATE_DIR_NAME" "$RUNCMD" $*
elif [ "$DIB_INIT_SYSTEM" == "systemd" ]; then
    if [ "$POSTSTART" != "$DEFAULT_POSTSTART" ] ; then
        echo "WARNING: post start is ignored with systemd." >&2
    fi
    if [ -n "$RUNCMD" ]; then
        install_systemd $SERVICENAME $INSTALLDIR $RUNAS $RUNCMD $*
    fi
    install_create_dir_systemd $SERVICENAME $RUNAS $CREATE_DIR_NAME
fi
