#!/usr/bin/env bash
#
# SoftSol Martyria - install and enroll the Wazuh endpoint agent on macOS.
#
# Self-contained installer for internet-connected Mac workstations or servers.
# Downloads the Wazuh pkg (Intel or Apple silicon), enrolls with Baruch, and
# applies the workstations group config.
#
# Canonical file (update here when changing the public installer):
#   midas/static/downloads/SoftSol-Martyria-mac.sh
#
# Public download URL:
#   https://softsol.co.za/downloads/SoftSol-Martyria-mac.sh
#
# Run as root, or approve the sudo prompt when the script re-launches itself.

set -euo pipefail

# =============================================================================
# Configuration - edit these values when updating the public installer
# =============================================================================
MANAGER_PUBLIC='martyria.softsol.co.za'
REGISTRATION_PORT=1515
EVENT_PORT=1514
GROUP='workstations'
CLIENT_GROUP=''
VERSION='4.14.6'
REGISTRATION_PASSWORD='MartyriaWitness2026!'
AGENT_NAME_SUFFIX='martyria'
STAGE_DIR='/Library/Application Support/Martyria/wazuh-install'
PRODUCT_NAME='SoftSol Martyria'
OSSEC_CONF='/Library/Ossec/etc/ossec.conf'
CLIENT_KEYS='/Library/Ossec/etc/client.keys'
AGENT_AUTH='/Library/Ossec/bin/agent-auth'
OSSEC_LOG='/Library/Ossec/logs/ossec.log'
LAUNCHD_PLIST='/Library/LaunchDaemons/com.wazuh.agent.plist'

log() {
  printf '%s\n' "$*"
}

die() {
  log "ERROR: $*" >&2
  exit 1
}

require_root() {
  if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
    log '==> Root privileges required - re-running with sudo...'
    exec sudo -E bash "$0" "$@"
  fi
}

require_darwin() {
  [[ "$(uname -s)" == Darwin ]] || die 'This installer is for macOS only. Use SoftSol-Martyria.ps1 (Windows) or SoftSol-Martyria.sh (Linux).'
}

resolve_martyria_endpoint() {
  # Always use the public FQDN: multiple PCs share one WireGuard IP per site,
  # and roaming laptops must reach Baruch from the internet as well as on-site.
  # Enrollment is keyed by agent name (use_source_ip=no on the manager).
  # Never use SoftSol VPN/LAN manager IP — computers move off VPN.
  log "==> Using public manager ${MANAGER_PUBLIC} (on-site and roaming)"
  MANAGER="$MANAGER_PUBLIC"
  AGENT_GROUP="$GROUP"
}

default_agent_name() {
  local name
  name="$(scutil --get ComputerName 2>/dev/null || hostname -s 2>/dev/null || hostname)-${AGENT_NAME_SUFFIX}"
  name="${name//[^a-zA-Z0-9._-]/-}"
  name="${name#-}"
  name="${name%-}"
  [[ ${#name} -gt 128 ]] && name="${name:0:128}"
  printf '%s' "$name"
}

detect_pkg_arch() {
  case "$(uname -m)" in
    arm64) printf '%s' 'arm64' ;;
    x86_64) printf '%s' 'intel64' ;;
    *) die "Unsupported Mac architecture: $(uname -m)" ;;
  esac
}

set_agent_remote_config() {
  local address="$1" port="${2:-1514}"
  [[ -f "$OSSEC_CONF" ]] || return 0
  python3 - "$address" "$port" "$OSSEC_CONF" <<'PY'
import re
import sys

address, port, path = sys.argv[1:4]

with open(path, 'r', encoding='utf-8', errors='replace') as f:
    content = f.read()

match = re.search(r'(<client>)(.*?)(</client>)', content, re.DOTALL)
if not match:
    sys.exit(0)

block = match.group(2)

def set_tag(block, tag, value):
    pattern = rf'(<{tag}>)[^<]*(</{tag}>)'
    if re.search(pattern, block):
        return re.sub(pattern, rf'\g<1>{value}\g<2>', block, count=1)
    return block

if re.search(r'<server>', block):
    if re.search(r'<address>', block):
        block = set_tag(block, 'address', address)
    else:
        block = re.sub(r'(<server>)', rf'\g<1>\n      <address>{address}</address>', block, count=1)
    if re.search(r'<port>', block):
        block = set_tag(block, 'port', str(port))
    else:
        block = re.sub(r'(</server>)', f'      <port>{port}</port>\n    \\g<1>', block, count=1)
    if re.search(r'<protocol>', block):
        block = set_tag(block, 'protocol', 'tcp')
    else:
        block = re.sub(r'(</server>)', '      <protocol>tcp</protocol>\n    \\g<1>', block, count=1)
else:
    block = (
        block.rstrip()
        + f'\n    <server>\n      <address>{address}</address>\n'
        + f'      <port>{port}</port>\n      <protocol>tcp</protocol>\n    </server>\n'
    )

if re.search(r'<crypto_method>', block):
    block = set_tag(block, 'crypto_method', 'aes')
else:
    block = block.rstrip() + '\n    <crypto_method>aes</crypto_method>\n'

content = content[:match.start(2)] + block + content[match.end(2):]

with open(path, 'w', encoding='utf-8') as f:
    f.write(content)
PY
  log "==> Agent configured for ${address}:${port} (crypto_method=aes)"
}

agent_enrolled() {
  [[ -f "$CLIENT_KEYS" ]] && grep -q '[[:graph:]]' "$CLIENT_KEYS"
}

agent_connected() {
  [[ -f "$OSSEC_LOG" ]] || return 1
  tail -n 40 "$OSSEC_LOG" | grep -qE 'Connected to the server|Connected to .*1514' \
    && ! tail -n 40 "$OSSEC_LOG" | grep -qE 'Invalid password|Unable to connect to any server'
}

agent_installed() {
  [[ -x /Library/Ossec/bin/wazuh-control ]] || [[ -f "$LAUNCHD_PLIST" ]]
}

stop_agent() {
  launchctl bootout system/com.wazuh.agent 2>/dev/null || true
  launchctl bootout system "$LAUNCHD_PLIST" 2>/dev/null || true
  /Library/Ossec/bin/wazuh-control stop 2>/dev/null || true
}

start_agent() {
  if launchctl print system/com.wazuh.agent &>/dev/null; then
    launchctl kickstart -k system/com.wazuh.agent 2>/dev/null || \
      /Library/Ossec/bin/wazuh-control restart 2>/dev/null || true
  elif [[ -f "$LAUNCHD_PLIST" ]]; then
    launchctl bootstrap system "$LAUNCHD_PLIST"
  else
    /Library/Ossec/bin/wazuh-control start 2>/dev/null || true
  fi
}

write_wazuh_envs() {
  local manager="$1" agent_name="$2" agent_group="$3" reg_pass="$4"
  {
    printf "WAZUH_MANAGER='%s' && \\\n" "$manager"
    printf "WAZUH_REGISTRATION_SERVER='%s' && \\\n" "$manager"
    printf "WAZUH_REGISTRATION_PORT='%s' && \\\n" "$REGISTRATION_PORT"
    printf "WAZUH_REGISTRATION_PASSWORD='%s' && \\\n" "$reg_pass"
    printf "WAZUH_AGENT_NAME='%s' && \\\n" "$agent_name"
    printf "WAZUH_AGENT_GROUP='%s'\n" "$agent_group"
  } > /tmp/wazuh_envs
}

download_wazuh_pkg() {
  local pkg_arch="$1" dest="$2"
  local pkg_name="wazuh-agent-${VERSION}-1.${pkg_arch}.pkg"
  local url="https://packages.wazuh.com/4.x/macos/${pkg_name}"
  log "==> Downloading ${url}"
  curl -fsSL "$url" -o "$dest"
}

invoke_agent_enrollment() {
  local manager="$1" agent_name="$2" agent_group="$3" reg_pass="$4"

  [[ -x "$AGENT_AUTH" ]] || die 'agent-auth not found'

  if [[ -f "$CLIENT_KEYS" ]]; then
    rm -f "$CLIENT_KEYS"
  fi

  log "==> Enrolling agent: ${agent_name} -> ${manager}:${REGISTRATION_PORT}"
  if ! "$AGENT_AUTH" \
      -m "$manager" \
      -p "$REGISTRATION_PORT" \
      -A "$agent_name" \
      -G "$agent_group" \
      -P "$reg_pass"; then
    die "Enrollment failed. From this Mac, TCP to ${manager}:${REGISTRATION_PORT} and :${EVENT_PORT} must succeed. If the log shows 'Duplicate name', this host name is already enrolled — contact SoftSol IT."
  fi

  agent_enrolled || die 'agent-auth exited 0 but client.keys was not created - contact SoftSol IT.'
}

install_softsol_martyria_agent() {
  local agent_name manager reg_pass needs_enrollment=1 log_path pkg_arch pkg_path

  require_darwin
  mkdir -p "$STAGE_DIR"
  log_path="${STAGE_DIR}/install.log"
  exec > >(tee -a "$log_path") 2>&1

  log ''
  log "  ${PRODUCT_NAME} - Wazuh endpoint agent installer (macOS)"
  resolve_martyria_endpoint
  manager="$MANAGER"
  agent_name="$(default_agent_name)"
  reg_pass="${REGISTRATION_PASSWORD#"${REGISTRATION_PASSWORD%%[![:space:]]*}"}"
  reg_pass="${reg_pass%"${reg_pass##*[![:space:]]}"}"
  [[ -n "$reg_pass" ]] || die 'Registration password is not configured in this script.'
  pkg_arch="$(detect_pkg_arch)"

  log "  Manager: ${manager}"
  log "  Architecture: ${pkg_arch}"
  log ''

  if agent_installed; then
    log '==> Wazuh agent already installed - skipping package install (avoids wiping enrollment keys)'
    stop_agent

    if agent_enrolled; then
      log '==> Existing enrollment keys found - keeping keys (will update config and restart)'
      needs_enrollment=0
    fi
  else
    pkg_path="${STAGE_DIR}/wazuh-agent-${VERSION}-1.${pkg_arch}.pkg"
    if [[ ! -f "$pkg_path" ]]; then
      download_wazuh_pkg "$pkg_arch" "$pkg_path"
    fi

    log '==> Installing Wazuh agent package'
    write_wazuh_envs "$manager" "$agent_name" "$AGENT_GROUP" "$reg_pass"
    installer -pkg "$pkg_path" -target /
    if agent_enrolled; then
      needs_enrollment=0
    fi
  fi

  if [[ "$needs_enrollment" -eq 1 ]]; then
    invoke_agent_enrollment "$manager" "$agent_name" "$AGENT_GROUP" "$reg_pass"
  fi

  set_agent_remote_config "$manager" "$EVENT_PORT"

  sleep 2
  start_agent
  sleep 15

  log ''
  log "==> ${PRODUCT_NAME} agent installed and started."
  log "    Name:    ${agent_name}"
  log "    Manager: ${manager}:${EVENT_PORT}"
  log "    Group:   ${AGENT_GROUP}"
  log ''
  if agent_connected; then
    log '==> Agent connected to manager.'
  else
    log '==> Agent service started. If it is not Active on the dashboard within 2 minutes, check:'
    log "    sudo tail -50 ${OSSEC_LOG}"
  fi

  if [[ -n "${CLIENT_GROUP}" ]]; then
    if ssh -o BatchMode=yes -o ConnectTimeout=10 root@172.17.12.34 \
      "/opt/martyria/baruch/scripts/assign-agent-group.sh '${agent_name}' '${AGENT_GROUP}' '${CLIENT_GROUP}'" 2>/dev/null; then
      log "==> Also assigned to client group: ${CLIENT_GROUP}"
    else
      log "==> Note: could not assign client group ${CLIENT_GROUP} (SSH to Baruch required)"
    fi
  fi
  log ''
  log 'Note: macOS may prompt for Full Disk Access / privacy permissions for some'
  log '      inventory features. That is normal and does not block basic monitoring.'
  log "Install log: ${log_path}"
}

require_root "$@"
install_softsol_martyria_agent
