#!/usr/bin/env bash
###################
# This is property of eXtremeSHOK.com
# You are free to use, modify and distribute, however you may not remove this notice.
# Copyright (c) Adrian Jon Kriel :: admin@extremeshok.com
# License: BSD (Berkeley Software Distribution)
##################
# Container entrypoint for clamav-unofficial-sigs
#
# Modes (CUS_MODE):
#   all-in-one : (default) run the official clamav init (clamd + freshclam)
#                plus a periodic unofficial signature update loop
#   updater    : signature update loop only, for use as a sidecar that
#                shares /var/lib/clamav with an external clamd
#
# Environment:
#   CUS_MODE=all-in-one|updater
#   CUS_UPDATE_HOURS=2      loop interval in hours
#   CUS_USER_CONF           extra user.conf lines (only used when no
#                           user.conf is bind-mounted)
#
# Any arguments are executed instead of the loop, eg. one-shot usage:
#   docker run --rm -v clamdb:/var/lib/clamav <image> clamav-unofficial-sigs.sh --force
##################

set -u

date +%s > /tmp/cus-started

CUS_MODE="${CUS_MODE:-all-in-one}"
CUS_UPDATE_HOURS="${CUS_UPDATE_HOURS:-2}"
CONFIG_DIR="/etc/clamav-unofficial-sigs"
SCRIPT="/usr/local/sbin/clamav-unofficial-sigs.sh"

# One-shot / custom command passthrough
if [ "$#" -gt 0 ] ; then
    exec "$@"
fi

# Generate a minimal user.conf unless the user bind-mounted their own
if [ ! -f "${CONFIG_DIR}/user.conf" ] ; then
    {
        echo '# Generated by the container entrypoint, bind-mount your own user.conf to override'
        echo 'user_configuration_complete="yes"'
        # The random pause exists to spread cron load, pointless in a container loop
        echo 'enable_random="no"'
        if [ "$CUS_MODE" == "all-in-one" ] ; then
            echo 'reload_dbs="yes"'
        fi
        if [ -n "${CUS_USER_CONF:-}" ] ; then
            printf '%s\n' "$CUS_USER_CONF"
        fi
    } > "${CONFIG_DIR}/user.conf"
fi

init_pid=""
sleep_pid=""
script_pid=""
term_handler() {
    [ -n "$sleep_pid" ] && kill "$sleep_pid" 2>/dev/null
    [ -n "$script_pid" ] && kill -TERM "$script_pid" 2>/dev/null
    [ -n "$init_pid" ] && kill -TERM "$init_pid" 2>/dev/null
    [ -n "$init_pid" ] && wait "$init_pid" 2>/dev/null
    exit 0
}
trap term_handler TERM INT

if [ "$CUS_MODE" == "all-in-one" ] ; then
    echo "Starting clamd and freshclam (official clamav init)"
    /init &
    init_pid=$!
    # Wait for clamd to come up before the first update run
    waited=0
    while [ ! -S /tmp/clamd.sock ] && [ "$waited" -lt 300 ] ; do
        if ! kill -0 "$init_pid" 2>/dev/null ; then
            echo "ERROR: clamav init exited before clamd started"
            exit 1
        fi
        sleep 5
        waited=$((waited + 5))
    done
fi

echo "Starting clamav-unofficial-sigs update loop, interval: ${CUS_UPDATE_HOURS}h (mode: ${CUS_MODE})"
while true ; do
    # Run as a background child so the TERM trap fires immediately
    bash "$SCRIPT" --verbose &
    script_pid=$!
    wait "$script_pid"
    script_rc=$?
    script_pid=""
    if [ "$script_rc" -ne 0 ] ; then
        echo "WARNING: clamav-unofficial-sigs update run failed (exit ${script_rc})"
    fi
    # Loop status consumed by the container healthcheck
    echo "$(date +%s) ${script_rc}" > /tmp/cus-status
    if [ -n "$init_pid" ] && ! kill -0 "$init_pid" 2>/dev/null ; then
        echo "ERROR: clamav init exited, stopping container"
        exit 1
    fi
    # Interruptible sleep so SIGTERM stops the container immediately
    sleep "$((CUS_UPDATE_HOURS * 3600))" &
    sleep_pid=$!
    wait "$sleep_pid"
    sleep_pid=""
done
