#!/bin/bash
#
# Collect the relevant XiVO logs for a given day, ready to attach to a ticket.
#
# Usage: xivo-collect-logs <YYYYMMDD> [ticket number]
#
# The deployment role is auto-detected from the /etc/docker/* layout:
#   /etc/docker/xivo    -> xivo
#   /etc/docker/compose -> cc
#   /etc/docker/mds     -> xds (media server)
#   xivo + compose      -> ucaddon (collects both xivo and cc logs)
#
# Logs are copied (keeping their absolute path) into a destination directory
# and packed into a tarball. Rotated logs are selected by the day they cover
# (mtime based), so it works whatever the rotation/compression/date-ext scheme.

set -u

readonly progname="${0##*/}"

# Logs that always apply, whatever the role.
readonly SYSTEM_LOGS="/var/log/syslog"

# Text logs collected on a XiVO node (date-matched through rotation).
readonly XIVO_LOGS="/var/log/asterisk/full
/var/log/nginx/xivo.access.log
/var/log/xivo-agentd.log
/var/log/xivo-ctid/xivo-ctid.log
/var/log/xivo-web-interface/xivo.log"

# Text logs collected on an XDS media server.
readonly XDS_LOGS="/var/log/asterisk/full"

# Text logs collected on a CC node.
readonly CC_LOGS="/var/log/xivocc/xuc/xuc.log
/var/log/xivocc/recording-server/recording-server.log"

# Optional CC logs (collected only when present, never reported as missing).
readonly CC_OPTIONAL_LOGS="/var/log/xivocc/xuc/xuc_ami.log"

DATE=""
TICKET=""
DEST=""
T0=0
T1=0
MISSING=()

usage() {
    cat <<EOF
Usage: ${progname} <YYYYMMDD> [ticket number]

Collect the XiVO logs of the given day into a directory (and a tarball).

  YYYYMMDD       day to collect logs for (e.g. 20260610)
  ticket number  optional; logs go to /var/local/issue<ticket number>
                 when omitted a temporary directory is used (printed at the end)
EOF
}

die() {
    echo "${progname}: $*" >&2
    exit 1
}

note_missing() {
    MISSING+=("$1")
}

# Copy a file keeping its absolute path under DEST (e.g. DEST/var/log/...).
copy_file() {
    local f="$1"
    if cp -a --parents "$f" "${DEST}/" 2>/dev/null; then
        echo "  + ${f}"
    else
        note_missing "${f} (copy failed)"
    fi
}

# Collect every rotated file of a log that covers the requested day.
# A file covers (mtime_of_previous_file, its_own_mtime]; the requested day is
# [T0, T1). We keep files whose coverage interval overlaps that day.
collect_rotated() {
    local base="$1"
    local optional="${2:-}"
    local candidates=() prev=0 selected=0

    shopt -s nullglob
    candidates=( "${base}" "${base}".* )
    shopt -u nullglob

    if [ "${#candidates[@]}" -eq 0 ]; then
        [ -n "${optional}" ] || note_missing "${base} (not found)"
        return
    fi

    while IFS=$'\t' read -r mtime path; do
        if [ "${mtime}" -gt "${T0}" ] && [ "${prev}" -lt "${T1}" ]; then
            copy_file "${path}"
            selected=1
        fi
        prev="${mtime}"
    done < <(stat -c '%Y	%n' "${candidates[@]}" 2>/dev/null | sort -n)

    if [ "${selected}" -eq 0 ] && [ -z "${optional}" ]; then
        note_missing "${base} (no rotated file covering ${DATE})"
    fi
}

collect_log_list() {
    local log
    while IFS= read -r log; do
        [ -n "${log}" ] && collect_rotated "${log}"
    done <<< "$1"
}

# atop logs are already stored per-day as /var/log/atop/atop_YYYYMMDD.
collect_atop() {
    local matches=()
    shopt -s nullglob
    matches=( /var/log/atop/*"${DATE}"* )
    shopt -u nullglob

    if [ "${#matches[@]}" -eq 0 ]; then
        note_missing "/var/log/atop/atop_${DATE} (not found)"
        return
    fi
    local f
    for f in "${matches[@]}"; do
        copy_file "${f}"
    done
}

detect_role() {
    local has_xivo=0 has_cc=0 has_mds=0
    [ -d /etc/docker/xivo ] && has_xivo=1
    [ -d /etc/docker/compose ] && has_cc=1
    [ -d /etc/docker/mds ] && has_mds=1

    if [ "${has_mds}" -eq 1 ]; then
        echo "xds"
    elif [ "${has_xivo}" -eq 1 ] && [ "${has_cc}" -eq 1 ]; then
        echo "ucaddon"
    elif [ "${has_xivo}" -eq 1 ]; then
        echo "xivo"
    elif [ "${has_cc}" -eq 1 ]; then
        echo "cc"
    else
        echo "unknown"
    fi
}

collect_for_role() {
    local role="$1"
    case "${role}" in
        xivo)
            collect_log_list "${XIVO_LOGS}"
            collect_atop
            ;;
        xds)
            collect_log_list "${XDS_LOGS}"
            collect_atop
            ;;
        cc)
            collect_log_list "${CC_LOGS}"
            collect_rotated "${CC_OPTIONAL_LOGS}" optional
            ;;
        ucaddon)
            collect_log_list "${XIVO_LOGS}"
            collect_atop
            collect_log_list "${CC_LOGS}"
            collect_rotated "${CC_OPTIONAL_LOGS}" optional
            ;;
    esac
}

parse_date() {
    [[ "${DATE}" =~ ^[0-9]{8}$ ]] || die "invalid date '${DATE}', expected YYYYMMDD"
    local y="${DATE:0:4}" m="${DATE:4:2}" d="${DATE:6:2}"
    T0=$(date -d "${y}-${m}-${d}" +%s 2>/dev/null) \
        || die "invalid date '${DATE}'"
    T1=$((T0 + 86400))
}

prepare_dest() {
    if [ -n "${TICKET}" ]; then
        DEST="/var/local/issue${TICKET}"
        mkdir -p "${DEST}" || die "cannot create ${DEST}"
    else
        DEST=$(mktemp -d "/tmp/xivo-collect-logs-${DATE}.XXXXXX") \
            || die "cannot create a temporary directory"
    fi
}

main() {
    if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
        usage >&2
        exit 1
    fi
    case "$1" in
        -h|--help) usage; exit 0 ;;
    esac

    DATE="$1"
    TICKET="${2:-}"

    [ "$(id -u)" -eq 0 ] || die "must be run as root to read /var/log"

    parse_date
    prepare_dest

    local role
    role=$(detect_role)
    if [ "${role}" = "unknown" ]; then
        die "cannot detect deployment role (no /etc/docker/{xivo,compose,mds})"
    fi

    echo "Detected role : ${role}"
    echo "Collecting day: ${DATE}"
    echo "Destination   : ${DEST}"
    echo

    collect_log_list "${SYSTEM_LOGS}"
    collect_for_role "${role}"

    local archive="${DEST}.tar.gz"
    if tar -czf "${archive}" -C "${DEST}" . 2>/dev/null; then
        echo
        echo "Archive       : ${archive}"
    else
        archive=""
    fi

    echo
    echo "Logs collected in: ${DEST}"
    [ -n "${archive}" ] && echo "Tarball ready    : ${archive}"

    if [ "${#MISSING[@]}" -gt 0 ]; then
        echo
        echo "Could not collect the following (check manually / via scp if on another host):"
        local item
        for item in "${MISSING[@]}"; do
            echo "  - ${item}"
        done
    fi
}

main "$@"
