📜 hermes-cron-master.sh

#!/usr/bin/env bash
# 🤖 Hermes Cron Master — 紧凑版报告
set -o pipefail

# ── 路径推导 ──
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
TASKS_DIR="${SCRIPT_DIR}/cron-tasks"
REPORT_DIR="${HOME}/.cron-reports"
REPORT_DATE=$(date +%Y-%m-%d)
REPORT_FILE="${REPORT_DIR}/cron-${REPORT_DATE}.md"

# Fix HOME for cron context
export HOME="/opt/data/home"
export PATH="/opt/data/home/bin:${HOME}/bin:/usr/local/bin:/usr/bin:/bin"
export GBRAIN_EMBEDDING_MODEL="v100:embed"
export GBRAIN_EMBEDDING_DIMENSIONS="1024"

mkdir -p "${TASKS_DIR}" "${REPORT_DIR}"

# ── 任务定义 ──
TASK_ORDER=(sitemap)

declare -A TASK_TIMEOUT
TASK_TIMEOUT[sitemap]=110

declare -A TASK_LABEL
TASK_LABEL[sitemap]="🌐 Trilium Sitemap 更新"

# ── 单任务模式 ──
RUN_SINGLE=""
for arg in "$@"; do
  case "$arg" in
    --task=*) RUN_SINGLE="${arg#*=}" ;;
    --test)   RUN_SINGLE="sitemap" ;;
  esac
done

# ── 指标提取函数 ──
extract_sitemap_compact() {
  local err="$1"
  local count size http removed metrics
  count=$(echo "$err" | grep -oP '提取到\s+\K\d+' || echo "?")
  removed=$(echo "$err" | grep -oP '已移除\s+\K\d+' || echo "0")
  size=$(echo "$err" | grep -oP 'sitemap\.xml:\s+\K[\d,]+(?=\s+bytes)' || echo "")
  http=$(echo "$err" | grep -oP 'HTTP\s+\K\d+' || echo "")
  metrics="🔗 ${count} 链接"
  [ "$removed" != "0" ] && metrics+=" (🔒已移除 ${removed})"
  [ -n "$size" ] && metrics+=" | 📦 ${size}B"
  [ -n "$http" ] && metrics+=" | 📡 ${http}"
  echo "$metrics"
}

# ── 运行单个任务 ──
run_task() {
  local name="$1"
  local task_script="${TASKS_DIR}/${name}.sh"
  if [ ! -f "$task_script" ]; then
    task_script="${TASKS_DIR}/${name}.py"
  fi
  if [ ! -f "$task_script" ]; then
    LAST_EXIT_CODE=1; LAST_DURATION=0; LAST_STDOUT=""; LAST_STDERR="脚本不存在"
    return 1
  fi
  local start_ts=$(date +%s)
  local stdout_file=$(mktemp) stderr_file=$(mktemp)
  case "$task_script" in
    *.py)  interpreter="python3" ;;
    *)     interpreter="bash" ;;
  esac
  timeout "${TASK_TIMEOUT[$name]:-120}" "$interpreter" "$task_script" > "$stdout_file" 2> "$stderr_file"
  LAST_EXIT_CODE=$?
  LAST_DURATION=$(( $(date +%s) - start_ts ))
  LAST_STDOUT=$(cat "$stdout_file")
  LAST_STDERR=$(cat "$stderr_file")
  rm -f "$stdout_file" "$stderr_file"
  return $LAST_EXIT_CODE
}

# ── 主循环 ──
TASK_RESULTS=(); TASK_NAMES=(); TASK_EXIT_CODES=(); TASK_DURATIONS=()
TASK_STDOUTS=(); TASK_STDERRS=()
START_TS=$(date +%s)

if [ -n "$RUN_SINGLE" ]; then
  echo "🔧 单任务模式: ${RUN_SINGLE}"
  echo ""
  run_task "$RUN_SINGLE"
  echo "exit=${LAST_EXIT_CODE}, duration=${LAST_DURATION}s"
  exit $LAST_EXIT_CODE
fi

OVERALL_SUCCESS=0; OVERALL_FAIL=0; OVERALL_SKIP=0; OVERALL_TIME=0
FAILED_TASKS=""
idx=0

for task_name in "${TASK_ORDER[@]}"; do
  TASK_NAMES+=("$task_name")
  run_task "$task_name"
  TASK_EXIT_CODES+=($LAST_EXIT_CODE); TASK_DURATIONS+=($LAST_DURATION)
  TASK_STDOUTS+=("$LAST_STDOUT"); TASK_STDERRS+=("$LAST_STDERR")
  if [ $LAST_EXIT_CODE -eq 124 ]; then
    TASK_RESULTS+=("time"); OVERALL_TIME=$((OVERALL_TIME+1))
    FAILED_TASKS="$FAILED_TASKS $task_name"
  elif [ $LAST_EXIT_CODE -ne 0 ]; then
    TASK_RESULTS+=("fail"); OVERALL_FAIL=$((OVERALL_FAIL+1))
    FAILED_TASKS="$FAILED_TASKS $task_name"
  else
    TASK_RESULTS+=("ok"); OVERALL_SUCCESS=$((OVERALL_SUCCESS+1))
  fi
  idx=$((idx+1))
done

TOTAL_DURATION=$(( $(date +%s) - START_TS ))
REPORT=""
report() { REPORT+="$1"$'\n'; }
SUMMARY="✅${OVERALL_SUCCESS}"
[ "$OVERALL_FAIL" -gt 0 ] && SUMMARY+=" ❌${OVERALL_FAIL}"
[ "$OVERALL_TIME" -gt 0 ] && SUMMARY+=" ⏰${OVERALL_TIME}"
[ "$OVERALL_SKIP" -gt 0 ] && SUMMARY+=" ⏭️${OVERALL_SKIP}"
report "🤖 HerMes Cron 日报 — ${REPORT_DATE} | ${SUMMARY} | ⏱️${TOTAL_DURATION}s"

for i in "${!TASK_ORDER[@]}"; do
  name="${TASK_ORDER[$i]}"
  result="${TASK_RESULTS[$i]}"
  exit_code="${TASK_EXIT_CODES[$i]}"
  duration="${TASK_DURATIONS[$i]}"
  stdout="${TASK_STDOUTS[$i]}"
  stderr="${TASK_STDERRS[$i]}"
  case "$result" in
    ok)   emoji="✅" ;;
    fail) emoji="❌" ;;
    skip) emoji="⏭️" ;;
    time) emoji="⏰" ;;
    *)    emoji="❓" ;;
  esac
  if [ "$result" = "ok" ]; then
    metrics=""
    case "$name" in
      sitemap) metrics=$(extract_sitemap_compact "$stderr") ;;
    esac
    report "${emoji} ${TASK_LABEL[$name]} — ${metrics} (${duration}s)"
  else
    err=$(echo "$stderr" | head -3 | tr '\n' ' ' | sed 's/  */ /g' | cut -c1-80)
    [ -z "$err" ] && err=$(echo "$stdout" | head -3 | tr '\n' ' ' | sed 's/  */ /g' | cut -c1-80)
    report "${emoji} ${TASK_LABEL[$name]} — ${err} (${duration}s)"
  fi
done

echo "$REPORT"
echo "$REPORT" > "$REPORT_FILE"