| #!/bin/sh |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| API_BASE="http://localhost:7860/v0/management" |
| TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S')" |
| LOG_PREFIX="[cleanup_tokens] ${TIMESTAMP}" |
| TMP_FILE="/tmp/auth_files_$$.json" |
| TMP_NAMES="/tmp/auth_names_$$.txt" |
| TMP_DIR="/tmp/cleanup_$$" |
|
|
| CONCURRENCY="${CLEANUP_CONCURRENCY:-20}" |
|
|
| cleanup_tmp() { |
| rm -f "$TMP_FILE" "$TMP_NAMES" |
| rm -rf "$TMP_DIR" |
| } |
| trap cleanup_tmp EXIT |
|
|
| |
| |
| |
| notify_feishu() { |
| [ -z "$FEISHU_WEBHOOK_URL" ] && return 0 |
| PAYLOAD=$(jq -n --arg title "$1" --arg body "$2" '{ |
| msg_type: "post", |
| content: { post: { zh_cn: { |
| title: $title, |
| content: [[{"tag": "text", "text": $body}]] |
| }}} |
| }') |
| curl -sf -X POST -H "Content-Type: application/json" \ |
| -d "$PAYLOAD" "$FEISHU_WEBHOOK_URL" > /dev/null 2>&1 \ |
| && echo "${LOG_PREFIX} Feishu notification sent." \ |
| || echo "${LOG_PREFIX} WARNING: Failed to send Feishu notification." |
| } |
|
|
| |
| |
| |
| |
| delete_one() { |
| NAME="$1" |
| ENCODED=$(printf '%s' "$NAME" | sed 's/@/%40/g; s/ /%20/g') |
| RESULT_FILE="${TMP_DIR}/$(printf '%s' "$NAME" | md5sum | cut -d' ' -f1).result" |
|
|
| HTTP_ST=$(curl -s -o /dev/null -w "%{http_code}" \ |
| -X DELETE \ |
| -H "Authorization: Bearer ${MANAGEMENT_PASSWORD}" \ |
| "${API_BASE}/auth-files?name=${ENCODED}" 2>/dev/null) |
|
|
| if [ "$HTTP_ST" = "200" ]; then |
| printf 'OK %s\n' "$NAME" > "$RESULT_FILE" |
| else |
| printf 'ERR %s %s\n' "$NAME" "$HTTP_ST" > "$RESULT_FILE" |
| fi |
| } |
|
|
| |
| |
| |
|
|
| if [ -z "$MANAGEMENT_PASSWORD" ]; then |
| echo "${LOG_PREFIX} ERROR: MANAGEMENT_PASSWORD not set, skipping cleanup" |
| exit 1 |
| fi |
|
|
| echo "${LOG_PREFIX} Starting invalid token cleanup... (concurrency=${CONCURRENCY})" |
|
|
| |
| HTTP_STATUS=$(curl -s -o "$TMP_FILE" -w "%{http_code}" \ |
| -H "Authorization: Bearer ${MANAGEMENT_PASSWORD}" \ |
| "${API_BASE}/auth-files" 2>/dev/null) |
|
|
| if [ "$HTTP_STATUS" != "200" ]; then |
| MSG="API 返回 HTTP ${HTTP_STATUS},服务可能尚未就绪。" |
| echo "${LOG_PREFIX} ERROR: ${MSG}" |
| notify_feishu "❌ Token 清理失败" "[TIME] ${TIMESTAMP} |
| |
| [ERROR] ${MSG}" |
| exit 1 |
| fi |
|
|
| if ! jq empty "$TMP_FILE" 2>/dev/null; then |
| echo "${LOG_PREFIX} ERROR: Invalid JSON response" |
| notify_feishu "❌ Token 清理失败" "[TIME] ${TIMESTAMP} |
| |
| [ERROR] API 返回了无效的 JSON 响应" |
| exit 1 |
| fi |
|
|
| TOTAL=$(jq '.files | length' "$TMP_FILE") |
| echo "${LOG_PREFIX} Total auth files: ${TOTAL}" |
|
|
| [ "$TOTAL" -eq 0 ] && { echo "${LOG_PREFIX} No auth files found."; exit 0; } |
|
|
| |
| jq -r ' |
| .files[] | |
| select(.runtime_only != true) | |
| select( |
| .unavailable == true or |
| (.status != null and ( |
| .status == "error" or .status == "expired" or |
| .status == "invalid" or .status == "failed" or |
| .status == "unauthorized" or .status == "quota_exceeded" |
| )) |
| ) | |
| .name |
| ' "$TMP_FILE" > "$TMP_NAMES" |
|
|
| TO_DELETE=$(grep -c '' "$TMP_NAMES" 2>/dev/null || echo 0) |
| echo "${LOG_PREFIX} Tokens to delete: ${TO_DELETE}" |
|
|
| if [ "$TO_DELETE" -eq 0 ]; then |
| echo "${LOG_PREFIX} All tokens healthy, nothing to clean up." |
| notify_feishu "✅ Token 状态检查" "[TIME] ${TIMESTAMP} |
| |
| [STATS] 共 ${TOTAL} 个 token,全部健康 |
| |
| [RESULT] 无需清理" |
| exit 0 |
| fi |
|
|
| |
| while IFS= read -r NAME; do |
| STATUS=$(jq -r --arg n "$NAME" '.files[] | select(.name==$n) | .status // "unknown"' "$TMP_FILE") |
| echo "${LOG_PREFIX} - ${NAME} (${STATUS})" |
| done < "$TMP_NAMES" |
|
|
| |
| mkdir -p "$TMP_DIR" |
|
|
| ACTIVE=0 |
| while IFS= read -r NAME; do |
| [ -z "$NAME" ] && continue |
|
|
| |
| delete_one "$NAME" & |
| ACTIVE=$((ACTIVE + 1)) |
|
|
| |
| if [ "$ACTIVE" -ge "$CONCURRENCY" ]; then |
| wait |
| ACTIVE=0 |
| fi |
| done < "$TMP_NAMES" |
|
|
| |
| wait |
|
|
| |
| DELETED=0 |
| ERRORS=0 |
| DETAIL_LINES="" |
| ERROR_LINES="" |
|
|
| |
| while IFS= read -r NAME; do |
| [ -z "$NAME" ] && continue |
| RESULT_FILE="${TMP_DIR}/$(printf '%s' "$NAME" | md5sum | cut -d' ' -f1).result" |
| RESULT=$(cat "$RESULT_FILE" 2>/dev/null) |
|
|
| case "$RESULT" in |
| OK*) |
| STATUS=$(jq -r --arg n "$NAME" '.files[] | select(.name==$n) | .status // "unknown"' "$TMP_FILE") |
| echo "${LOG_PREFIX} DELETED: ${NAME}" |
| DELETED=$((DELETED + 1)) |
| DETAIL_LINES="${DETAIL_LINES} • ${NAME} [${STATUS}]\n" |
| ;; |
| ERR*) |
| HTTP_ST=$(echo "$RESULT" | awk '{print $3}') |
| echo "${LOG_PREFIX} ERROR deleting ${NAME} (HTTP ${HTTP_ST})" |
| ERRORS=$((ERRORS + 1)) |
| ERROR_LINES="${ERROR_LINES} • ${NAME} (HTTP ${HTTP_ST})\n" |
| ;; |
| *) |
| echo "${LOG_PREFIX} WARNING: No result for ${NAME}" |
| ERRORS=$((ERRORS + 1)) |
| ERROR_LINES="${ERROR_LINES} • ${NAME} (no result)\n" |
| ;; |
| esac |
| done < "$TMP_NAMES" |
|
|
| echo "${LOG_PREFIX} Done. Deleted: ${DELETED}, Errors: ${ERRORS}" |
|
|
| |
| |
| DETAIL_LINES=$(printf '%b' "$DETAIL_LINES") |
| ERROR_LINES=$(printf '%b' "$ERROR_LINES") |
|
|
| if [ "$ERRORS" -gt 0 ]; then |
| notify_feishu "⚠️ Token 清理完成(有错误)" "[TIME] ${TIMESTAMP} |
| |
| [STATS] 共 ${TOTAL} 个 token,发现 ${TO_DELETE} 个失效 |
| |
| ──────────────────── |
| [SUCCESS] 已删除 ${DELETED} 个: |
| ${DETAIL_LINES} |
| [FAIL] 删除失败 ${ERRORS} 个: |
| ${ERROR_LINES}────────────────────" |
| else |
| notify_feishu "🧹 Token 清理完成" "[TIME] ${TIMESTAMP} |
| |
| [STATS] 共 ${TOTAL} 个 token,清理 ${DELETED} 个失效 |
| |
| ──────────────────── |
| [REMOVED] 已删除: |
| ${DETAIL_LINES}──────────────────── |
| [RESULT] Success: ${DELETED}/${TO_DELETE} | Failed: 0/${TO_DELETE}" |
| fi |
|
|