#!/bin/bash
# Set up ClamAV daemon on the Makenotwork production host (Ubuntu 24.04).
#
# Idempotent: safe to re-run. Installs `clamav-daemon` and `clamav-freshclam`,
# configures clamd to listen on a Unix socket the makenotwork service can
# reach, enables both as systemd services, and waits for the first signature
# update to complete.
#
# After this script finishes successfully, add the following to
# /opt/makenotwork/.env (and restart makenotwork):
#
#     CLAMAV_SOCKET=/var/run/clamav/clamd.ctl
#
# That env var trips the `clamav` layer from Skip to live. The dashboard's
# Pipeline Health card for `clamav` will flip from "down — last clean: never"
# to "ok" after the first upload completes through the worker.
#
# References:
#   - Ubuntu clamav package: https://packages.ubuntu.com/noble/clamav-daemon
#   - INSTREAM protocol docs: clamdscan(1), clamd.conf(5)

set -euo pipefail

SOCKET_PATH="/var/run/clamav/clamd.ctl"
CLAMD_CONF="/etc/clamav/clamd.conf"

require_root() {
    if [ "$(id -u)" -ne 0 ]; then
        echo "[clamav] error: must run as root" >&2
        exit 1
    fi
}

install_packages() {
    if dpkg -l | grep -q "^ii  clamav-daemon "; then
        echo "[clamav] clamav-daemon already installed"
        return
    fi
    echo "[clamav] installing clamav-daemon + clamav-freshclam..."
    DEBIAN_FRONTEND=noninteractive apt-get update -qq
    DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        clamav-daemon clamav-freshclam
}

configure_socket() {
    # Confirm the default LocalSocket directive matches our expectation. If
    # the operator (or a future package update) changes it, surface that
    # rather than silently writing a path that disagrees with the env var.
    local existing
    existing=$(grep -E "^LocalSocket\s" "$CLAMD_CONF" | awk '{print $2}' || true)
    if [ -n "$existing" ] && [ "$existing" != "$SOCKET_PATH" ]; then
        echo "[clamav] error: $CLAMD_CONF has LocalSocket=$existing, expected $SOCKET_PATH" >&2
        echo "[clamav]        edit clamd.conf and re-run, or update SOCKET_PATH in this script." >&2
        exit 1
    fi
    if [ -z "$existing" ]; then
        echo "[clamav] adding LocalSocket directive to $CLAMD_CONF..."
        echo "" >> "$CLAMD_CONF"
        echo "LocalSocket $SOCKET_PATH" >> "$CLAMD_CONF"
    fi
}

wait_for_initial_signatures() {
    # freshclam pulls the initial signature DB on first boot. Until that's
    # done, clamd will refuse to start. Poll up to 5 minutes.
    echo "[clamav] waiting for freshclam to fetch initial signatures..."
    local deadline=$(( $(date +%s) + 300 ))
    while [ $(date +%s) -lt $deadline ]; do
        if [ -f /var/lib/clamav/daily.cvd ] || [ -f /var/lib/clamav/daily.cld ]; then
            echo "[clamav] signatures present"
            return
        fi
        sleep 5
    done
    echo "[clamav] warn: signatures not present after 5 min; check 'journalctl -u clamav-freshclam'" >&2
}

enable_services() {
    systemctl enable --now clamav-freshclam
    # Reload clamd config (idempotent) and start the daemon.
    systemctl restart clamav-daemon
    systemctl enable clamav-daemon
}

verify_socket() {
    echo "[clamav] verifying socket reachability..."
    local deadline=$(( $(date +%s) + 60 ))
    while [ $(date +%s) -lt $deadline ]; do
        if [ -S "$SOCKET_PATH" ]; then
            if echo "PING" | timeout 5 socat - "UNIX-CONNECT:$SOCKET_PATH" 2>/dev/null | grep -q PONG; then
                echo "[clamav] OK: clamd responds at $SOCKET_PATH"
                return
            fi
        fi
        sleep 2
    done
    echo "[clamav] warn: clamd not responding at $SOCKET_PATH within 60s" >&2
    echo "[clamav]        check 'journalctl -u clamav-daemon' and 'systemctl status clamav-daemon'" >&2
    exit 1
}

ensure_socat() {
    if ! command -v socat >/dev/null 2>&1; then
        DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends socat
    fi
}

main() {
    require_root
    install_packages
    configure_socket
    wait_for_initial_signatures
    enable_services
    ensure_socat
    verify_socket

    cat <<EOF

[clamav] setup complete.

Next step — set the env var so makenotwork uses this clamd:

    echo 'CLAMAV_SOCKET=$SOCKET_PATH' >> /opt/makenotwork/.env
    systemctl restart makenotwork

The Pipeline Health card for 'clamav' will flip from down to ok after the
first upload passes through the scan worker.

EOF
}

main "$@"
