#!/usr/bin/env bash
# =============================================================================
# MAX ContainerAudit — Docker/Podman 容器运行状况巡检
# 原创脚本：以容器运行、安全边界、资源、镜像与卷为重点的只读审计。
# 风险等级: 🟢 只读 — 不停止、不重启、不删除容器、镜像、卷或网络。
# 用法:
#   bash <(curl -fsSL https://dl.unvmax.com/linux/scripts/max-tools/max-container-audit.sh)
#   bash max-container-audit.sh --json
# =============================================================================
set -Eeuo pipefail

BC='\033[38;5;220m'; BG='\033[38;5;220m'; Y='\033[1;33m'; R='\033[0;31m'
BW='\033[38;5;255m'; W='\033[38;5;250m'; GR='\033[38;5;242m'; N='\033[0m'
PASS=0 WARN=0 FAIL=0
JSON_MODE=0
[[ "${1:-}" == '--json' ]] && JSON_MODE=1

ok()   { ((JSON_MODE)) || printf '  %b✔%b %s\n' "$BG" "$N" "$*"; PASS=$((PASS + 1)); }
warn() { ((JSON_MODE)) || printf '  %b⚠%b %s\n' "$Y" "$N" "$*"; WARN=$((WARN + 1)); }
bad()  { ((JSON_MODE)) || printf '  %b✘%b %s\n' "$R" "$N" "$*"; FAIL=$((FAIL + 1)); }
info() { ((JSON_MODE)) || printf '  %b%s%b\n' "$W" "$*" "$N"; }
section() { ((JSON_MODE)) || printf '\n  %b── %s ─────────────────────────────────%b\n' "$BC" "$1" "$N"; }

ENGINE=''
if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
  ENGINE='docker'
elif command -v podman >/dev/null 2>&1 && podman info >/dev/null 2>&1; then
  ENGINE='podman'
fi

if [[ -z "$ENGINE" ]]; then
  if ((JSON_MODE)); then
    printf '{"status":"unavailable","reason":"Docker/Podman daemon not available"}\n'
  else
    printf '\n  %b┌──────────────────────────────────────────────────┐%b\n' "$BC" "$N"
    printf '  %b│     MAX 容器运行状况巡检  %b(只读诊断)%b\n' "$BG" "$GR" "$N"
    printf '  %b└──────────────────────────────────────────────────┘%b\n\n' "$BC" "$N"
    warn '未检测到可用的 Docker 或 Podman 服务，未执行任何操作'
  fi
  exit 0
fi

containers="$($ENGINE ps -a --format '{{.ID}}' 2>/dev/null | wc -l | tr -d ' ')"
running="$($ENGINE ps --format '{{.ID}}' 2>/dev/null | wc -l | tr -d ' ')"
images="$($ENGINE images -q 2>/dev/null | sort -u | wc -l | tr -d ' ')"
volumes="$($ENGINE volume ls -q 2>/dev/null | wc -l | tr -d ' ')"

if ((JSON_MODE)); then
  printf '{"status":"ok","engine":"%s","containers":%s,"running":%s,"images":%s,"volumes":%s}\n' \
    "$ENGINE" "$containers" "$running" "$images" "$volumes"
  exit 0
fi

printf '\n  %b┌──────────────────────────────────────────────────┐%b\n' "$BC" "$N"
printf '  %b│     MAX 容器运行状况巡检  %b(只读诊断)%b\n' "$BG" "$GR" "$N"
printf '  %b└──────────────────────────────────────────────────┘%b\n' "$BC" "$N"
printf '  %b引擎: %b%s  %b· 容器: %b%s  %b· 运行中: %b%s%b\n' "$GR" "$BW" "$ENGINE" "$GR" "$BW" "$containers" "$GR" "$BW" "$running" "$N"

section '引擎与资源概况'
version="$($ENGINE version --format '{{.Server.Version}}' 2>/dev/null || true)"
[[ -n "$version" ]] && ok "$ENGINE 服务端版本: $version" || warn '无法读取服务端版本'
info "镜像: $images 个  · 卷: $volumes 个"
if $ENGINE system df >/dev/null 2>&1; then
  $ENGINE system df 2>/dev/null | sed 's/^/    /' | head -8
fi

section '容器运行状态'
if (( containers == 0 )); then
  warn '没有发现容器'
else
  while IFS='|' read -r name status image; do
    [[ -z "$name" ]] && continue
    if [[ "$status" == Up* || "$status" == running* ]]; then
      ok "$name  $status  [$image]"
    elif [[ "$status" == Exited* || "$status" == exited* || "$status" == Created* ]]; then
      warn "$name  $status  [$image]"
    else
      warn "$name  $status  [$image]"
    fi
  done < <($ENGINE ps -a --format '{{.Names}}|{{.Status}}|{{.Image}}' 2>/dev/null)
fi

section '重启策略与健康检查'
while IFS='|' read -r id name restart health; do
  [[ -z "$id" ]] && continue
  if [[ "$restart" == 'no' || -z "$restart" ]]; then
    warn "$name 未设置自动重启策略"
  else
    ok "$name 重启策略: $restart"
  fi
  if [[ "$health" == 'none' || -z "$health" ]]; then
    info "$name 未定义 HEALTHCHECK"
  elif [[ "$health" == healthy ]]; then
    ok "$name 健康检查正常"
  else
    bad "$name 健康检查: $health"
  fi
done < <($ENGINE ps -a --format '{{.ID}}|{{.Names}}|{{.HostConfig.RestartPolicy.Name}}|{{.State.Health.Status}}' 2>/dev/null)

section '高权限与公网暴露提醒'
privileged=0
hostnet=0
while IFS='|' read -r id name priv net ports; do
  [[ -z "$id" ]] && continue
  if [[ "$priv" == true ]]; then
    warn "$name 使用 privileged=true，请确认必要性"
    privileged=$((privileged + 1))
  fi
  if [[ "$net" == host ]]; then
    warn "$name 使用 host 网络，端口隔离失效"
    hostnet=$((hostnet + 1))
  fi
  if [[ "$ports" == *'0.0.0.0:'* || "$ports" == *':::'* ]]; then
    warn "$name 存在公网监听: $ports"
  else
    info "$name 端口: ${ports:-(未发布)}"
  fi
done < <($ENGINE ps --format '{{.ID}}|{{.Names}}|{{.HostConfig.Privileged}}|{{.HostConfig.NetworkMode}}|{{.Ports}}' 2>/dev/null)
(( privileged == 0 )) && ok '运行中容器未发现 privileged=true'
(( hostnet == 0 )) && ok '运行中容器未使用 host 网络'

section '镜像与日志维护提醒'
dangling="$($ENGINE images -f dangling=true -q 2>/dev/null | wc -l | tr -d ' ')"
if (( dangling > 0 )); then
  warn "发现 $dangling 个悬空镜像；如确认无用，可手动执行: $ENGINE image prune"
else
  ok '未发现悬空镜像'
fi
if [[ -d /var/lib/docker/containers ]]; then
  while IFS= read -r line; do
    size="${line%% *}"; file="${line#* }"
    [[ -n "$file" ]] && warn "容器 JSON 日志较大: $size  $file"
  done < <(find /var/lib/docker/containers -name '*-json.log' -type f -size +100M -printf '%s %p\n' 2>/dev/null | sort -nr | head -5 | awk '{printf "%.1fMB %s\n", $1/1048576, $2}')
fi

printf '\n  %b────────────────────────────────────────────────────%b\n' "$GR" "$N"
printf '  %b巡检完成%b  通过: %s  警告: %s  异常: %s\n' "$BG" "$N" "$PASS" "$WARN" "$FAIL"
printf '  %b提示: 本脚本只读；清理镜像、日志、容器前请先确认业务与备份。%b\n\n' "$GR" "$N"
