#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

readonly CONFIG_DIR="/etc/argus-agent"
readonly CONFIG_FILE="${CONFIG_DIR}/config.yml"
readonly ENV_FILE="${CONFIG_DIR}/argus-agent.env"
readonly SPOOL_DIR="/var/lib/argus-agent/spool"
readonly BIN_FILE="/usr/local/bin/argus-agent"
readonly LEGACY_CTL_FILE="/usr/local/bin/argus-agentctl"
readonly SERVICE_FILE="/etc/systemd/system/argus-agent.service"
readonly INSTALLER_VERSION="2026-07-14-agent-auto-config-v4"

ARGUS_INSTALL_BASE_URL_EXPLICIT="${ARGUS_INSTALL_BASE_URL+x}"
ARGUS_INSTALL_BASE_URL="${ARGUS_INSTALL_BASE_URL:-http://192.168.9.80}"
ARGUS_SERVER_URL="${ARGUS_SERVER_URL:-http://192.168.9.80:8000}"
ARGUS_AGENT_TOKEN="${ARGUS_AGENT_TOKEN:-}"
ARGUS_PROJECT="${ARGUS_PROJECT:-Default}"
ARGUS_AGENT_SITE_EXPLICIT="${ARGUS_AGENT_SITE+x}"
ARGUS_AGENT_SITE="${ARGUS_AGENT_SITE:-${ARGUS_SITE:-Local}}"
ARGUS_AGENT_ENVIRONMENT="${ARGUS_AGENT_ENVIRONMENT:-production}"
ARGUS_AGENT_GROUP="${ARGUS_AGENT_GROUP:-${ARGUS_GROUP:-linux}}"
ARGUS_AGENT_INTERVAL_SECONDS="${ARGUS_AGENT_INTERVAL_SECONDS:-30}"
ARGUS_AGENT_LOG_LEVEL="${ARGUS_AGENT_LOG_LEVEL:-info}"
ARGUS_FORCE_CONFIG="${ARGUS_FORCE_CONFIG:-false}"
ARGUS_PURGE_SPOOL="${ARGUS_PURGE_SPOOL:-false}"

cleanup_temp_file() {
  if [ -n "${1:-}" ]; then
    rm -f -- "$1"
  fi
}

trim_quotes() {
  local value="$1"
  value="${value%\"}"
  value="${value#\"}"
  value="${value%\'}"
  value="${value#\'}"
  printf '%s' "$value"
}

read_agent_config_value() {
  local key="$1"
  local current_section=""
  local line trimmed section_name value

  [ -f "$CONFIG_FILE" ] || return 1

  while IFS= read -r line; do
    trimmed="${line#"${line%%[![:space:]]*}"}"
    case "$trimmed" in
      "" | \#*)
        continue
        ;;
    esac
    if [ "${line# }" = "$line" ] && [ "${trimmed%:}" != "$trimmed" ]; then
      section_name="${trimmed%:}"
      current_section="$section_name"
      continue
    fi
    if [ "$current_section" = "agent" ] && [ "${trimmed#${key}:}" != "$trimmed" ]; then
      value="${trimmed#${key}:}"
      value="${value#"${value%%[![:space:]]*}"}"
      trim_quotes "$value"
      return 0
    fi
  done <"$CONFIG_FILE"

  return 1
}

preserve_existing_agent_identity() {
  local existing_site

  if [ -n "$ARGUS_AGENT_SITE_EXPLICIT" ]; then
    return
  fi
  if existing_site="$(read_agent_config_value "site")" && [ -n "$existing_site" ]; then
    ARGUS_AGENT_SITE="$existing_site"
    log "preserving existing agent.site=${ARGUS_AGENT_SITE} to keep stable agent_id"
  fi
}

log() {
  printf '[argus-agent] %s\n' "$*"
}

die() {
  printf '[argus-agent] ERROR: %s\n' "$*" >&2
  exit 1
}

usage() {
  cat <<'EOF'
Usage:
  install-argus-agent.sh [install] [--project NAME] [--site NAME] [--environment NAME] [--group NAME] [--server-url URL] [--install-base-url URL] [--token TOKEN] [--force-config]
  install-argus-agent.sh uninstall
  install-argus-agent.sh remove

Environment:
  ARGUS_AGENT_TOKEN             Required for install. Token sent to Argus Server.
  ARGUS_PROJECT                 Project name. Default: Default
  ARGUS_AGENT_SITE              Identity namespace for this host. Default: default
                                 (changing it after install changes the agent_id).
  ARGUS_AGENT_GROUP             Agent group. Default: linux
  ARGUS_SERVER_URL              Default: http://192.168.9.80:8000
  ARGUS_INSTALL_BASE_URL        Default: http://192.168.9.80, or --server-url when passed by CLI.
  ARGUS_FORCE_CONFIG=true       Overwrite /etc/argus-agent/config.yml if it exists.
  ARGUS_PURGE_SPOOL=true        Remove /var/lib/argus-agent on uninstall.
EOF
}

parse_args() {
  COMMAND="install"
  while [ "$#" -gt 0 ]; do
    case "$1" in
      install | uninstall | remove)
        COMMAND="$1"
        shift
        ;;
      --project)
        [ -n "${2:-}" ] || die "--project requires a value"
        ARGUS_PROJECT="${2:-}"
        shift 2
        ;;
      --site)
        [ -n "${2:-}" ] || die "--site requires a value"
        ARGUS_AGENT_SITE="${2:-}"
        ARGUS_AGENT_SITE_EXPLICIT=1
        shift 2
        ;;
      --environment)
        [ -n "${2:-}" ] || die "--environment requires a value"
        ARGUS_AGENT_ENVIRONMENT="${2:-}"
        shift 2
        ;;
      --group)
        [ -n "${2:-}" ] || die "--group requires a value"
        ARGUS_AGENT_GROUP="${2:-}"
        shift 2
        ;;
      --server-url)
        [ -n "${2:-}" ] || die "--server-url requires a value"
        ARGUS_SERVER_URL="${2:-}"
        if [ -z "$ARGUS_INSTALL_BASE_URL_EXPLICIT" ]; then
          ARGUS_INSTALL_BASE_URL="$ARGUS_SERVER_URL"
        fi
        shift 2
        ;;
      --install-base-url)
        [ -n "${2:-}" ] || die "--install-base-url requires a value"
        ARGUS_INSTALL_BASE_URL="${2:-}"
        ARGUS_INSTALL_BASE_URL_EXPLICIT=1
        shift 2
        ;;
      --token)
        [ -n "${2:-}" ] || die "--token requires a value"
        ARGUS_AGENT_TOKEN="${2:-}"
        shift 2
        ;;
      --force-config)
        ARGUS_FORCE_CONFIG=true
        shift
        ;;
      --purge-spool)
        ARGUS_PURGE_SPOOL=true
        shift
        ;;
      -h | --help | help)
        COMMAND="help"
        shift
        ;;
      *)
        usage
        die "unknown argument: $1"
        ;;
    esac
  done
}

require_root() {
  if [ "$(id -u)" -ne 0 ]; then
    die "run as root or with sudo"
  fi
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

arch_suffix() {
  case "$(uname -m)" in
    x86_64 | amd64)
      printf 'amd64'
      ;;
    aarch64 | arm64)
      printf 'arm64'
      ;;
    *)
      die "unsupported architecture: $(uname -m)"
      ;;
  esac
}

download_file() {
  local url="$1"
  local output="$2"

  if command -v curl >/dev/null 2>&1; then
    curl -fsSL "$url" -o "$output"
  elif command -v wget >/dev/null 2>&1; then
    wget -qO "$output" "$url"
  else
    die "curl or wget is required to download $url"
  fi
}

sha256_file() {
  local file="$1"

  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$file" | awk '{print $1}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$file" | awk '{print $1}'
  else
    die "sha256sum or shasum is required to validate downloads"
  fi
}

validate_checksum() {
  local binary_path="$1"
  local binary_name="$2"
  local checksums_file="$3"
  local expected actual

  [ -s "$checksums_file" ] || die "checksum file is empty"
  expected="$(awk -v name="$binary_name" '$2 == name {print $1}' "$checksums_file" | head -n 1)"
  [ -n "$expected" ] || die "checksum for $binary_name not found"
  actual="$(sha256_file "$binary_path")"
  if [ "$actual" != "$expected" ]; then
    die "checksum mismatch for $binary_name"
  fi
}

write_service() {
  cat >"$SERVICE_FILE" <<'EOF'
[Unit]
Description=Argus Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=-/etc/argus-agent/argus-agent.env
ExecStart=/usr/local/bin/argus-agent --config /etc/argus-agent/config.yml
Restart=always
RestartSec=10
User=root

[Install]
WantedBy=multi-user.target
EOF
  chmod 0644 "$SERVICE_FILE"
}

write_env() {
  umask 077
  cat >"$ENV_FILE" <<EOF
ARGUS_AGENT_TOKEN=${ARGUS_AGENT_TOKEN}
ARGUS_SERVER_URL=${ARGUS_SERVER_URL}
EOF
  chmod 0600 "$ENV_FILE"
}

write_config() {
  local target_config="$CONFIG_FILE"

  if [ -f "$CONFIG_FILE" ] && [ "$ARGUS_FORCE_CONFIG" != "true" ]; then
    target_config="${CONFIG_FILE}.new"
    log "config already exists, keeping ${CONFIG_FILE} and writing ${target_config}"
  elif [ -f "$CONFIG_FILE" ]; then
    cp -p "$CONFIG_FILE" "${CONFIG_FILE}.bak.$(date +%Y%m%d%H%M%S)"
  fi

  cat >"$target_config" <<EOF
# Argus Agent default configuration.
#
# Secure by default: only host, CPU, memory, filesystem and systemd health
# collectors are enabled. Service-specific collectors stay disabled until an
# operator enables only what belongs to this host.

agent:
  name: ""
  project: ${ARGUS_PROJECT}
  site: ${ARGUS_AGENT_SITE}
  environment: ${ARGUS_AGENT_ENVIRONMENT}
  agent_group: ${ARGUS_AGENT_GROUP}
  interval_seconds: ${ARGUS_AGENT_INTERVAL_SECONDS}
  spool_dir: /var/lib/argus-agent/spool
  log_level: ${ARGUS_AGENT_LOG_LEVEL}

server:
  url: ${ARGUS_SERVER_URL}
  token_env: ARGUS_AGENT_TOKEN
  timeout_seconds: 10

remote_policy:
  enabled: true
  refresh_interval_seconds: 60
  cache_file: /var/lib/argus-agent/policy-cache.json

discovery:
  enabled: true
  mode: safe
  scan_interval_seconds: 300
  deep_scan_interval_seconds: 1800
  local_only: true
  network_scan_enabled: false
  max_ports_to_probe: 50
  auto_enable_collectors: true
  auto_enable_safe_collectors: true
  auto_enable_requires_credentials: false
  send_discovered_services: true
  send_discovery_evidence: true
  ignore_services: []
  include_services: []

identity:
  hostname_override: ""
  ip_detection: auto
  ip_override: ""
  ip_detection_interface: ""
  prefer_ipv4: true
  ignore_interfaces:
    - lo
    - docker0
    - br-*
    - veth*
    - cni*
    - flannel*
    - cali*
  tags: []

collectors:
  # Basic OS identity, uptime and load/kernel metadata.
  host:
    enabled: true
    collect_uptime: true
    collect_load_average: true
    collect_os_release: true
    collect_kernel: true

  # CPU usage check and metric.
  cpu:
    enabled: true
    warning_percent: 80
    critical_percent: 90
    sample_seconds: 2

  # Memory usage check and metric.
  memory:
    enabled: true
    warning_percent: 80
    critical_percent: 90

  # Filesystem usage check and metric. Keep pseudo filesystems excluded.
  filesystem:
    enabled: true
    warning_percent: 85
    critical_percent: 90
    include_mountpoints:
      - /
      - /var
      - /opt
      - /home
      - /docker
      - /var/lib/docker
    exclude_filesystem_types:
      - tmpfs
      - devtmpfs
      - overlay
      - squashfs
      - proc
      - sysfs
      - cgroup2
    exclude_mountpoints:
      - /proc
      - /sys
      - /dev
      - /run
      - /snap

  # systemd services. The agent detects the distro (/etc/os-release) and
  # picks the correct default service list automatically (e.g. cron on
  # Debian/Ubuntu vs crond on Oracle Linux/RHEL-like) — you don't need to
  # list both. Use extra_services for anything specific to this host.
  systemd:
    enabled: true
    service_profile: auto
    use_os_defaults: true
    extra_services: []
    ignored_services: []
    alert_on_missing_extra_services: false
    missing_default_service_state: skipped
    inactive_is_critical: false
    failed_is_critical: true
    required_services_are_critical: true

  # Systemd degraded/failed units. reset-failed is safe here because it only
  # clears accumulated failed state and never restarts a service.
  systemd_health:
    enabled: true
    check_degraded: true
    collect_failed_units: true
    max_failed_units: 20
    auto_reset_failed: true
    reset_failed_after_notify: true
    reset_failed_delay_seconds: 30
    revalidate_after_seconds: 10
    reset_failed_cooldown_seconds: 3600
    commands_timeout_seconds: 10
    ignored_units:
      - session-*.scope
      - user@*.service

  # Top processes by CPU/memory and optional systemd unit aggregation.
  resource_top:
    enabled: true
    interval_seconds: 30
    top_n: 10
    collect_processes: true
    collect_systemd_units: true
    collect_docker_containers: true
    collect_cpu: true
    collect_memory: true
    collect_io: true
    sample_seconds: 2
    min_cpu_percent: 1
    min_memory_percent: 1
    include_cmdline: true
    cmdline_max_length: 180
    aggregate_by_systemd_unit: true
    aggregate_by_container: true
    aggregate_by_user: true
    ignore_kernel_threads: true
    ignore_processes:
      - kthreadd
      - kworker/*
      - ksoftirqd/*
      - rcu_*
    alert_cpu_process_percent: 80
    alert_memory_process_percent: 50
    send_even_without_alert: true

  # Top directories by size. Runs only when filesystem usage is high.
  disk_top:
    enabled: true
    only_when_filesystem_usage_gte: 80
    top_n: 5
    max_depth: 2
    timeout_seconds: 20
    min_interval_seconds: 900
    same_filesystem_only: true
    exclude_paths:
      - /proc
      - /sys
      - /dev
      - /run
      - /tmp/systemd-private*
      - /var/lib/kubelet/pods
      - /var/lib/docker/overlay2/*/merged

  # Optional collectors. Enable only on hosts that actually run these services.
  docker:
    enabled: auto
    socket: /var/run/docker.sock
    timeout_seconds: 10
    collect_engine: true
    collect_df: true
    collect_containers: true
    collect_container_stats: true
    collect_images: true
    collect_volumes: true
    image_usage_warning_gb: 10
    image_usage_critical_gb: 20
    volume_usage_warning_gb: 20
    volume_usage_critical_gb: 50
    unused_images:
      enabled: true
      older_than_hours: 168
      include_dangling: true
    unused_volumes:
      enabled: true
      protect_named_volumes: true
      require_label_for_auto_cleanup: true
      allowed_cleanup_label: argus.cleanup=true
    cleanup:
      enabled: false
      dry_run: true
      notify_before_cleanup: true
      cleanup_delay_seconds: 30
      cooldown_seconds: 86400
      images:
        enabled: false
        remove_unused: true
        remove_dangling: true
        older_than_hours: 168
        max_reclaim_gb: 10
      volumes:
        enabled: false
        remove_unused: false
        require_label: true
        allowed_label: argus.cleanup=true
        max_reclaim_gb: 10

  docker_swarm:
    enabled: auto
    socket: /var/run/docker.sock
    timeout_seconds: 10
    manager_only_checks: true
    collect_nodes: true
    collect_services: true
    collect_tasks: true
    collect_networks: true
    collect_overlay_networks: true
    collect_manager_quorum: true
    api_ping_interval_seconds: 30
    swarm_check_interval_seconds: 60
    expected_managers_min: 3
    manager_quorum_required: true
    alert_on_node_down: true
    alert_on_node_availability_drain: true
    alert_on_node_availability_pause: true
    alert_on_service_replica_mismatch: true
    alert_on_task_failed: true
    alert_on_overlay_network_issue: true
    ignored_nodes: []
    ignored_services: []
    ignored_networks: []

  haproxy:
    enabled: auto
    stats_url: http://127.0.0.1:8404/metrics
    stats_socket_paths:
      - /run/haproxy/admin.sock
      - /var/lib/haproxy/stats
      - /var/run/haproxy.sock
    collect_backends: true
    collect_frontends: true

  rke2_server:
    enabled: false
    services:
      - rke2-server
    kubeconfig: /etc/rancher/rke2/rke2.yaml

  rke2_agent:
    enabled: false
    services:
      - rke2-agent
    kubeconfig: /etc/rancher/rke2/rke2.yaml

  traefik:
    enabled: false
    metrics_url: http://127.0.0.1:8080/metrics

  nginx:
    enabled: auto
    status_url: http://127.0.0.1/nginx_status
    endpoints:
      - http://127.0.0.1/nginx_status

  apache:
    enabled: auto
    endpoints:
      - http://127.0.0.1/server-status?auto

  postfix:
    enabled: auto
    queue_command: postqueue -p

  mysql:
    enabled: auto
    require_credentials: true
    dsn_env: ARGUS_MYSQL_DSN
    service_names:
      - mysql
      - mysqld
    check_port: 3306

  mariadb:
    enabled: false
    service_names:
      - mariadb
      - mysql
    check_port: 3306

  postgresql:
    enabled: auto
    require_credentials: true
    dsn_env: ARGUS_POSTGRES_DSN
    service_names:
      - postgresql
    check_port: 5432

  redis:
    enabled: auto
    service_names:
      - redis
      - redis-server
    check_port: 6379

  kubernetes:
    enabled: auto
    mode: discovery_only
EOF
  chmod 0644 "$target_config"
}

install_agent() {
  require_root
  require_cmd uname
  require_cmd systemctl

  if [ -z "$ARGUS_AGENT_TOKEN" ]; then
    die "ARGUS_AGENT_TOKEN is required"
  fi
  if [ -z "$ARGUS_SERVER_URL" ]; then
    die "ARGUS_SERVER_URL is required"
  fi

  preserve_existing_agent_identity

  local suffix binary_name binary_url checksum_url temp_file checksums_file
  suffix="$(arch_suffix)"
  binary_name="argus-agent-linux-${suffix}"
  binary_url="${ARGUS_INSTALL_BASE_URL%/}/${binary_name}"
  checksum_url="${ARGUS_INSTALL_BASE_URL%/}/SHA256SUMS"
  temp_file="$(mktemp)"
  checksums_file="$(mktemp)"
  trap "cleanup_temp_file '$temp_file'; cleanup_temp_file '$checksums_file'" EXIT

  log "downloading ${binary_url}"
  log "installer version ${INSTALLER_VERSION}"
  download_file "$binary_url" "$temp_file"
  log "downloading ${checksum_url}"
  download_file "$checksum_url" "$checksums_file"
  validate_checksum "$temp_file" "$binary_name" "$checksums_file"
  chmod 0755 "$temp_file"

  install -d "$CONFIG_DIR" "$SPOOL_DIR"
  install -m 0755 "$temp_file" "$BIN_FILE"
  ln -sfn "$BIN_FILE" "$LEGACY_CTL_FILE"
  cleanup_temp_file "$temp_file"
  cleanup_temp_file "$checksums_file"
  trap - EXIT
  write_service
  write_env
  write_config

  systemctl daemon-reload
  systemctl enable argus-agent >/dev/null
  systemctl restart argus-agent

  log "installed and started argus-agent"
  log "config: ${CONFIG_FILE}"
}

uninstall_agent() {
  require_root
  require_cmd systemctl

  systemctl disable --now argus-agent >/dev/null 2>&1 || true
  rm -f "$SERVICE_FILE"
  rm -f "$BIN_FILE"
  rm -f "$LEGACY_CTL_FILE"
  rm -f "$CONFIG_FILE"
  rm -f "$ENV_FILE"
  rmdir "$CONFIG_DIR" >/dev/null 2>&1 || true

  if [ "$ARGUS_PURGE_SPOOL" = "true" ]; then
    rm -rf /var/lib/argus-agent
  fi

  systemctl daemon-reload
  log "removed argus-agent service, binary, env file and config file"
}

main() {
  parse_args "$@"
  case "$COMMAND" in
    install)
      install_agent
      ;;
    uninstall | remove)
      uninstall_agent
      ;;
    -h | --help | help)
      usage
      ;;
    *)
      usage
      die "unknown command: $COMMAND"
      ;;
  esac
}

main "$@"
