#!/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 < 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 "$@"