Ctrl K
ComunidadGuías…

scripts/butler-gateway-ops.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# Butler WeChat gateway — daily ops (status / restart / logs / preflight / install / upgrade).
set -euo pipefail
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}"

ROOT="$(cd "$(/usr/bin/dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=scripts/lib/butler-gateway-preflight.sh
source "$ROOT/scripts/lib/butler-gateway-preflight.sh"
# shellcheck source=scripts/lib/butler-systemd-install.sh
source "$ROOT/scripts/lib/butler-systemd-install.sh"

BUTLER_PY="$(butler_resolve_python3)"

UNIT=butler-gateway.service
LOG="$ROOT/logs/butler-gateway.log"

_usage() {
  cat <<EOF
Usage: $(basename "$0") <command>

Commands:
  status      systemd + process + last log lines
  restart     systemctl --user restart $UNIT
  logs        tail -f $LOG
  preflight   check .env, deps, WeChat accounts, linger
  install     install/refresh user systemd unit (see install-butler-gateway-service.sh)
  upgrade     git pull + clean .pyc + pip install + restart + verify + memory-reindex
  verify      post-deploy check (service active, commit match, critical imports)
  drift       detect code changes newer than running service
  reindex     rebuild semantic memory index (灵文1号 default)

Examples:
  bash scripts/butler-gateway-ops.sh status
  bash scripts/butler-gateway-ops.sh preflight
  bash scripts/butler-gateway-ops.sh upgrade
  bash scripts/butler-gateway-ops.sh verify
  bash scripts/butler-gateway-ops.sh drift
EOF
}

_cmd_status() {
  systemctl --user --no-pager status "$UNIT" 2>&1 | head -20 || true
  echo "---"
  pgrep -af 'butler.main gateway' || echo "(no butler gateway process)"
  echo "---"
  if [[ -f "$LOG" ]]; then
    echo "Last 5 lines of $LOG:"
    tail -5 "$LOG"
  else
    echo "Log not found: $LOG"
  fi
}

_cmd_restart() {
  for pid in $(pgrep -f 'butler.main gateway' 2>/dev/null || true); do
    echo "Stopping gateway pid=$pid"
    kill "$pid" 2>/dev/null || true
  done
  sleep 1
  systemctl --user restart "$UNIT"
  sleep 1
  systemctl --user --no-pager status "$UNIT" | head -12
}

_cmd_logs() {
  mkdir -p "$ROOT/logs"
  touch "$LOG"
  tail -f "$LOG"
}

_cmd_preflight() {
  ROOT="$ROOT" butler_gateway_preflight
}

_cmd_install() {
  exec bash "$ROOT/scripts/install-butler-gateway-service.sh" "$@"
}

_cmd_reindex() {
  local project="${1:-灵文1号}"
  echo "== memory-reindex ($project) =="
  if [[ -n "$project" ]]; then
    bash "$ROOT/scripts/butler-memory-reindex.sh" --project "$project"
  else
    bash "$ROOT/scripts/butler-memory-reindex.sh"
  fi
}

_cmd_upgrade() {
  local skip_reindex=0
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --skip-reindex) skip_reindex=1 ;;
      *) echo "Unknown upgrade arg: $1" >&2; _usage; exit 1 ;;
    esac
    shift
  done
  cd "$ROOT"
  if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    git pull --ff-only
  else
    echo "Not a git repo; skipping pull" >&2
  fi
  echo "== clean stale __pycache__ =="
  find "$ROOT" -path '*/butler*/__pycache__' -prune -exec rm -rf {} + 2>/dev/null || true
  find "$ROOT" -path '*/tests*/__pycache__' -prune -exec rm -rf {} + 2>/dev/null || true
  echo "== sync dependencies (gateway extras) =="
  if [[ -n "${VIRTUAL_ENV:-}" ]]; then
    "$BUTLER_PY" -m pip install -e "$ROOT[gateway]" --quiet 2>/dev/null || echo "pip install [gateway] skipped (non-fatal)" >&2
  elif [[ -d "$ROOT/.venv/bin" ]]; then
    # shellcheck source=/dev/null
    source "$ROOT/.venv/bin/activate"
    "$BUTLER_PY" -m pip install -e "$ROOT[gateway]" --quiet 2>/dev/null || echo "pip install [gateway] skipped (non-fatal)" >&2
  else
    "$BUTLER_PY" -m pip install -e "$ROOT[gateway]" --quiet 2>/dev/null || echo "pip install [gateway] skipped (non-fatal)" >&2
  fi
  bash "$ROOT/scripts/install-butler-gateway-service.sh"
  echo "== refresh ops timers (runtime all-projects, push-drain) =="
  bash "$ROOT/scripts/install-butler-ops-bundle.sh" --no-enable || true
  echo "== post-deploy verify =="
  sleep 3
  _cmd_verify || echo "WARN: deploy verify failed — check logs" >&2
  if [[ "$skip_reindex" -eq 0 ]]; then
    _cmd_reindex "灵文1号" || echo "memory-reindex failed (non-fatal)" >&2
  fi
}

_cmd_verify() {
  echo "--- deploy verification ---"
  local active
  active=$(systemctl --user is-active "$UNIT" 2>/dev/null || echo "inactive")
  if [[ "$active" != "active" ]]; then
    echo "FAIL: $UNIT is $active (expected active)"
    return 1
  fi
  echo "  [ok] $UNIT is active"

  local expected_sha
  expected_sha=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
  local log_sha
  log_sha=$(grep -oP 'commit=\K[a-f0-9]+' "$LOG" 2>/dev/null | tail -1 || echo "")
  if [[ -n "$log_sha" && "$log_sha" == "$expected_sha" ]]; then
    echo "  [ok] running commit=$log_sha matches HEAD"
  elif [[ -n "$log_sha" ]]; then
    echo "  [WARN] log commit=$log_sha vs HEAD=$expected_sha (may need restart)"
  else
    echo "  [info] no commit stamp in log yet"
  fi

  local fail=0
  PYTHONPATH="$ROOT" "$BUTLER_PY" -c "
from butler.project.lead import gateway_loop_role
from butler.project.manager import get_project_manager
from butler.gateway.message_handler import ButlerMessageHandler
from butler import get_build_identity
info = get_build_identity()
print(f\"  [ok] Butler v{info['version']} (commit={info['git_sha']}, python={info['python']})\")
" 2>&1 || fail=1
  if [[ "$fail" -ne 0 ]]; then
    echo "FAIL: critical import verification failed"
    return 1
  fi

  echo "  -- handler smoke test --"
  PYTHONPATH="$ROOT" "$BUTLER_PY" -c "
from butler.gateway.message_handler import ButlerMessageHandler
h = ButlerMessageHandler(channel='gateway')
r = h.handle_message('/状态', session_key='deploy-verify', platform='cli')
assert r and ('项目' in r or '管家' in r or '状态' in r or 'Butler' in r or '当前' in r), f'状态命令异常: {r!r}'
print('  [ok] handler smoke: /状态 responded')
" 2>&1 || {
    echo "  [WARN] handler smoke test failed (non-fatal)"
  }

  echo "--- deploy verification: OK ---"
  return 0
}

_cmd_drift() {
  cd "$ROOT"
  local active
  active=$(systemctl --user is-active "$UNIT" 2>/dev/null || echo "inactive")
  if [[ "$active" != "active" ]]; then
    echo "Gateway not running"
    return 0
  fi

  local svc_start
  svc_start=$(systemctl --user show "$UNIT" --property=ActiveEnterTimestamp --value 2>/dev/null || echo "")
  local last_commit_epoch
  last_commit_epoch=$(git log -1 --format=%ct 2>/dev/null || echo 0)
  local svc_epoch
  svc_epoch=$(date -d "$svc_start" +%s 2>/dev/null || echo 0)

  if [[ "$last_commit_epoch" -gt "$svc_epoch" && "$svc_epoch" -gt 0 ]]; then
    local age=$(( last_commit_epoch - svc_epoch ))
    echo "DRIFT DETECTED: newest commit is ${age}s newer than gateway start"
    echo "  gateway started: $svc_start"
    echo "  latest commit:   $(git log -1 --format='%ci %h %s')"
    echo "  run: bash scripts/butler-gateway-ops.sh upgrade"
    return 1
  else
    echo "No drift: gateway is up to date"
    return 0
  fi
}

main() {
  local cmd="${1:-}"
  shift || true
  case "$cmd" in
    status) _cmd_status ;;
    restart) _cmd_restart ;;
    logs) _cmd_logs ;;
    preflight) _cmd_preflight ;;
    install) _cmd_install "$@" ;;
    upgrade) _cmd_upgrade "$@" ;;
    verify) _cmd_verify ;;
    drift) _cmd_drift ;;
    reindex) _cmd_reindex "$@" ;;
    -h|--help|help|"") _usage ;;
    *)
      echo "Unknown command: $cmd" >&2
      _usage
      exit 1
      ;;
  esac
}

main "$@"