#!/usr/bin/env bash
set -euo pipefail

# Ubuntu Server Service and Port Audit Script
#
# Purpose:
#   - Inventory running systemd services and listening ports
#   - Help identify candidates for review, disablement, or firewall closure
#   - Optionally stop/disable selected services with explicit confirmation
#
# Safe defaults:
#   - Read-only mode by default
#   - No destructive actions unless --apply is used
#   - No packages are removed
#   - No firewall rules are changed automatically
#
# Usage examples:
#   ./ubuntu-service-port-audit.sh
#   ./ubuntu-service-port-audit.sh --export report.txt
#   ./ubuntu-service-port-audit.sh --apply --disable avahi-daemon --stop cups
#
# Notes:
#   - Run on Ubuntu Server with systemd
#   - Some services may be socket-activated; review both service and socket units
#   - Validate remote access, application traffic, and monitoring after changes

usage() {
  cat <<'EOF'
Usage:
  ubuntu-service-port-audit.sh [options]

Options:
  --export FILE          Save the audit report to FILE as well as stdout
  --apply                Allow stop/disable actions for explicitly named units
  --stop UNIT            Stop a systemd service unit (can be repeated)
  --disable UNIT         Disable a systemd service unit (can be repeated)
  --stop-socket UNIT     Stop a socket unit, e.g. name.socket (can be repeated)
  --disable-socket UNIT  Disable a socket unit (can be repeated)
  --help                 Show this help text

Examples:
  ubuntu-service-port-audit.sh
  ubuntu-service-port-audit.sh --export ./service-audit.txt
  sudo ubuntu-service-port-audit.sh --apply --stop avahi-daemon --disable avahi-daemon --disable-socket avahi-daemon.socket
EOF
}

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

require_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    log "ERROR: required command not found: $1"
    exit 1
  fi
}

have_root() {
  [[ ${EUID:-$(id -u)} -eq 0 ]]
}

EXPORT_FILE=""
APPLY=0
STOP_UNITS=()
DISABLE_UNITS=()
STOP_SOCKETS=()
DISABLE_SOCKETS=()

while [[ $# -gt 0 ]]; do
  case "$1" in
    --export)
      [[ $# -ge 2 ]] || { log "ERROR: --export requires a file path"; exit 1; }
      EXPORT_FILE="$2"
      shift 2
      ;;
    --apply)
      APPLY=1
      shift
      ;;
    --stop)
      [[ $# -ge 2 ]] || { log "ERROR: --stop requires a unit name"; exit 1; }
      STOP_UNITS+=("$2")
      shift 2
      ;;
    --disable)
      [[ $# -ge 2 ]] || { log "ERROR: --disable requires a unit name"; exit 1; }
      DISABLE_UNITS+=("$2")
      shift 2
      ;;
    --stop-socket)
      [[ $# -ge 2 ]] || { log "ERROR: --stop-socket requires a unit name"; exit 1; }
      STOP_SOCKETS+=("$2")
      shift 2
      ;;
    --disable-socket)
      [[ $# -ge 2 ]] || { log "ERROR: --disable-socket requires a unit name"; exit 1; }
      DISABLE_SOCKETS+=("$2")
      shift 2
      ;;
    --help|-h)
      usage
      exit 0
      ;;
    *)
      log "ERROR: unknown argument: $1"
      usage
      exit 1
      ;;
  esac
done

require_cmd systemctl
require_cmd ss
require_cmd awk
require_cmd sort
require_cmd sed

REPORT=""
append() {
  REPORT+="$1"$'\n'
  printf '%s\n' "$1"
}

run_block() {
  local title="$1"
  shift
  append ""
  append "## ${title}"
  append '```text'
  local output
  if output="$($* 2>&1)"; then
    append "$output"
  else
    append "$output"
    append "[command exited non-zero]"
  fi
  append '```'
}

append "Ubuntu Server Service and Port Audit"
append "Generated: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
append "Host: $(hostname -f 2>/dev/null || hostname)"
append "User: $(id -un)"
append ""
append "### Read this first"
append "- Review the report before making changes."
append "- Confirm application, SSH, backup, and monitoring dependencies."
append "- If a service is socket-activated, review the socket unit too."
append "- This script does not remove packages or alter firewall rules."

run_block "Running systemd services" systemctl list-units --type=service --state=running --no-pager
run_block "Enabled systemd service unit files" systemctl list-unit-files --type=service --state=enabled --no-pager
run_block "Listening TCP/UDP sockets" ss -tulpn

append ""
append "## Candidate review checklist"
append "- Identify services you can map to an owner or application."
append "- Mark legacy, test, discovery, or troubleshooting services for review."
append "- Verify whether any listening socket is local-only, socket-activated, or externally reachable."
append "- Confirm whether UFW or another firewall currently allows the port."
append "- Validate from both localhost and a remote management source after any change."

if [[ ${#STOP_UNITS[@]} -gt 0 || ${#DISABLE_UNITS[@]} -gt 0 || ${#STOP_SOCKETS[@]} -gt 0 || ${#DISABLE_SOCKETS[@]} -gt 0 ]]; then
  if [[ $APPLY -ne 1 ]]; then
    append ""
    append "## Requested changes not applied"
    append "Use --apply to allow the explicitly named stop/disable actions."
  else
    if ! have_root; then
      log "ERROR: stop/disable actions require root privileges. Re-run with sudo."
      exit 1
    fi
    for unit in "${STOP_UNITS[@]}"; do
      append ""
      append "## Stopping service: ${unit}"
      systemctl stop "$unit" || true
      append "Stopped: ${unit}"
    done
    for unit in "${DISABLE_UNITS[@]}"; do
      append ""
      append "## Disabling service: ${unit}"
      systemctl disable "$unit" || true
      append "Disabled: ${unit}"
    done
    for unit in "${STOP_SOCKETS[@]}"; do
      append ""
      append "## Stopping socket: ${unit}"
      systemctl stop "$unit" || true
      append "Stopped: ${unit}"
    done
    for unit in "${DISABLE_SOCKETS[@]}"; do
      append ""
      append "## Disabling socket: ${unit}"
      systemctl disable "$unit" || true
      append "Disabled: ${unit}"
    done
    append ""
    append "## Post-change validation reminders"
    append "- Check that SSH still works."
    append "- Confirm application endpoints are reachable."
    append "- Verify logs, backups, and monitoring still report normally."
    append "- Re-run ss -tulpn and your firewall review to confirm exposure changed as expected."
  fi
fi

if [[ -n "$EXPORT_FILE" ]]; then
  printf '%s\n' "$REPORT" > "$EXPORT_FILE"
  log "Report saved to: $EXPORT_FILE"
fi

exit 0