Update cleanup_tokens.sh
Browse files- cleanup_tokens.sh +98 -77
cleanup_tokens.sh
CHANGED
|
@@ -3,9 +3,9 @@
|
|
| 3 |
# cleanup_tokens.sh - Remove invalid/unavailable auth tokens via CLIProxyAPI Management API
|
| 4 |
#
|
| 5 |
# Environment variables:
|
| 6 |
-
# MANAGEMENT_PASSWORD
|
| 7 |
-
# FEISHU_WEBHOOK_URL
|
| 8 |
-
#
|
| 9 |
#
|
| 10 |
|
| 11 |
API_BASE="http://localhost:7860/v0/management"
|
|
@@ -13,50 +13,55 @@ TIMESTAMP="$(date '+%Y-%m-%d %H:%M:%S')"
|
|
| 13 |
LOG_PREFIX="[cleanup_tokens] ${TIMESTAMP}"
|
| 14 |
TMP_FILE="/tmp/auth_files_$$.json"
|
| 15 |
TMP_NAMES="/tmp/auth_names_$$.txt"
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
cleanup_tmp() {
|
| 18 |
rm -f "$TMP_FILE" "$TMP_NAMES"
|
|
|
|
| 19 |
}
|
| 20 |
trap cleanup_tmp EXIT
|
| 21 |
|
| 22 |
# ---------------------------------------------------------------------------
|
| 23 |
-
# notify_feishu TITLE
|
| 24 |
-
# Sends a rich-text "post" message to the Feishu webhook.
|
| 25 |
-
# Uses the same structured layout as the reference AnyRouter check-in style.
|
| 26 |
# ---------------------------------------------------------------------------
|
| 27 |
notify_feishu() {
|
| 28 |
[ -z "$FEISHU_WEBHOOK_URL" ] && return 0
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
-
|
| 38 |
-
'{
|
| 39 |
-
msg_type: "post",
|
| 40 |
-
content: {
|
| 41 |
-
post: {
|
| 42 |
-
zh_cn: {
|
| 43 |
-
title: $title,
|
| 44 |
-
content: [
|
| 45 |
-
[{"tag": "text", "text": $body}]
|
| 46 |
-
]
|
| 47 |
-
}
|
| 48 |
-
}
|
| 49 |
-
}
|
| 50 |
-
}')
|
| 51 |
-
|
| 52 |
-
curl -sf -X POST \
|
| 53 |
-
-H "Content-Type: application/json" \
|
| 54 |
-
-d "$PAYLOAD" \
|
| 55 |
-
"$FEISHU_WEBHOOK_URL" > /dev/null 2>&1 \
|
| 56 |
&& echo "${LOG_PREFIX} Feishu notification sent." \
|
| 57 |
|| echo "${LOG_PREFIX} WARNING: Failed to send Feishu notification."
|
| 58 |
}
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
# ---------------------------------------------------------------------------
|
| 61 |
# Main
|
| 62 |
# ---------------------------------------------------------------------------
|
|
@@ -66,7 +71,7 @@ if [ -z "$MANAGEMENT_PASSWORD" ]; then
|
|
| 66 |
exit 1
|
| 67 |
fi
|
| 68 |
|
| 69 |
-
echo "${LOG_PREFIX} Starting invalid token cleanup..."
|
| 70 |
|
| 71 |
# --- Fetch auth file list ---------------------------------------------------
|
| 72 |
HTTP_STATUS=$(curl -s -o "$TMP_FILE" -w "%{http_code}" \
|
|
@@ -76,8 +81,7 @@ HTTP_STATUS=$(curl -s -o "$TMP_FILE" -w "%{http_code}" \
|
|
| 76 |
if [ "$HTTP_STATUS" != "200" ]; then
|
| 77 |
MSG="API 返回 HTTP ${HTTP_STATUS},服务可能尚未就绪。"
|
| 78 |
echo "${LOG_PREFIX} ERROR: ${MSG}"
|
| 79 |
-
notify_feishu "❌ Token 清理失败"
|
| 80 |
-
"[TIME] ${TIMESTAMP}
|
| 81 |
|
| 82 |
[ERROR] ${MSG}"
|
| 83 |
exit 1
|
|
@@ -85,8 +89,7 @@ fi
|
|
| 85 |
|
| 86 |
if ! jq empty "$TMP_FILE" 2>/dev/null; then
|
| 87 |
echo "${LOG_PREFIX} ERROR: Invalid JSON response"
|
| 88 |
-
notify_feishu "❌ Token 清理失败"
|
| 89 |
-
"[TIME] ${TIMESTAMP}
|
| 90 |
|
| 91 |
[ERROR] API 返回了无效的 JSON 响应"
|
| 92 |
exit 1
|
|
@@ -95,15 +98,9 @@ fi
|
|
| 95 |
TOTAL=$(jq '.files | length' "$TMP_FILE")
|
| 96 |
echo "${LOG_PREFIX} Total auth files: ${TOTAL}"
|
| 97 |
|
| 98 |
-
|
| 99 |
-
echo "${LOG_PREFIX} No auth files found."
|
| 100 |
-
exit 0
|
| 101 |
-
fi
|
| 102 |
|
| 103 |
# --- Identify tokens to delete ---------------------------------------------
|
| 104 |
-
# Criteria (file-based tokens only, runtime_only are skipped):
|
| 105 |
-
# unavailable == true
|
| 106 |
-
# OR status in: error | expired | invalid | failed | unauthorized | quota_exceeded
|
| 107 |
jq -r '
|
| 108 |
.files[] |
|
| 109 |
select(.runtime_only != true) |
|
|
@@ -118,13 +115,12 @@ jq -r '
|
|
| 118 |
.name
|
| 119 |
' "$TMP_FILE" > "$TMP_NAMES"
|
| 120 |
|
| 121 |
-
TO_DELETE=$(
|
| 122 |
echo "${LOG_PREFIX} Tokens to delete: ${TO_DELETE}"
|
| 123 |
|
| 124 |
if [ "$TO_DELETE" -eq 0 ]; then
|
| 125 |
echo "${LOG_PREFIX} All tokens healthy, nothing to clean up."
|
| 126 |
-
notify_feishu "✅ Token 状态检查"
|
| 127 |
-
"[TIME] ${TIMESTAMP}
|
| 128 |
|
| 129 |
[STATS] 共 ${TOTAL} 个 token,全部健康
|
| 130 |
|
|
@@ -132,49 +128,75 @@ if [ "$TO_DELETE" -eq 0 ]; then
|
|
| 132 |
exit 0
|
| 133 |
fi
|
| 134 |
|
| 135 |
-
#
|
| 136 |
-
DETAIL_LINES=""
|
| 137 |
while IFS= read -r NAME; do
|
| 138 |
-
STATUS=$(jq -r --arg n "$NAME"
|
| 139 |
-
'.files[] | select(.name == $n) | .status // "unknown"' "$TMP_FILE")
|
| 140 |
-
DETAIL_LINES="${DETAIL_LINES} • ${NAME} [${STATUS}]
|
| 141 |
-
"
|
| 142 |
echo "${LOG_PREFIX} - ${NAME} (${STATUS})"
|
| 143 |
done < "$TMP_NAMES"
|
| 144 |
|
| 145 |
-
# ---
|
| 146 |
-
|
| 147 |
-
ERRORS=0
|
| 148 |
-
ERROR_LINES=""
|
| 149 |
|
|
|
|
| 150 |
while IFS= read -r NAME; do
|
| 151 |
[ -z "$NAME" ] && continue
|
| 152 |
-
ENCODED=$(printf '%s' "$NAME" | sed 's/@/%40/g; s/ /%20/g')
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
"${API_BASE}/auth-files?name=${ENCODED}" 2>/dev/null)
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
ERR=$(cat /tmp/del_resp_$$.json 2>/dev/null)
|
| 164 |
-
echo "${LOG_PREFIX} ERROR deleting ${NAME} (HTTP ${DEL_STATUS}): ${ERR}"
|
| 165 |
-
ERRORS=$((ERRORS + 1))
|
| 166 |
-
ERROR_LINES="${ERROR_LINES} • ${NAME} (HTTP ${DEL_STATUS})
|
| 167 |
-
"
|
| 168 |
fi
|
| 169 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
done < "$TMP_NAMES"
|
| 171 |
|
| 172 |
echo "${LOG_PREFIX} Done. Deleted: ${DELETED}, Errors: ${ERRORS}"
|
| 173 |
|
| 174 |
# --- Feishu notification ---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
if [ "$ERRORS" -gt 0 ]; then
|
| 176 |
-
notify_feishu "⚠️ Token 清理完成(有错误)"
|
| 177 |
-
"[TIME] ${TIMESTAMP}
|
| 178 |
|
| 179 |
[STATS] 共 ${TOTAL} 个 token,发现 ${TO_DELETE} 个失效
|
| 180 |
|
|
@@ -184,8 +206,7 @@ ${DETAIL_LINES}
|
|
| 184 |
[FAIL] 删除失败 ${ERRORS} 个:
|
| 185 |
${ERROR_LINES}────────────────────"
|
| 186 |
else
|
| 187 |
-
notify_feishu "🧹 Token 清理完成"
|
| 188 |
-
"[TIME] ${TIMESTAMP}
|
| 189 |
|
| 190 |
[STATS] 共 ${TOTAL} 个 token,清理 ${DELETED} 个失效
|
| 191 |
|
|
|
|
| 3 |
# cleanup_tokens.sh - Remove invalid/unavailable auth tokens via CLIProxyAPI Management API
|
| 4 |
#
|
| 5 |
# Environment variables:
|
| 6 |
+
# MANAGEMENT_PASSWORD CLIProxyAPI management API password (required)
|
| 7 |
+
# FEISHU_WEBHOOK_URL Feishu bot webhook URL for notifications (optional)
|
| 8 |
+
# CLEANUP_CONCURRENCY Max parallel DELETE requests (default: 20)
|
| 9 |
#
|
| 10 |
|
| 11 |
API_BASE="http://localhost:7860/v0/management"
|
|
|
|
| 13 |
LOG_PREFIX="[cleanup_tokens] ${TIMESTAMP}"
|
| 14 |
TMP_FILE="/tmp/auth_files_$$.json"
|
| 15 |
TMP_NAMES="/tmp/auth_names_$$.txt"
|
| 16 |
+
TMP_DIR="/tmp/cleanup_$$"
|
| 17 |
+
|
| 18 |
+
CONCURRENCY="${CLEANUP_CONCURRENCY:-20}"
|
| 19 |
|
| 20 |
cleanup_tmp() {
|
| 21 |
rm -f "$TMP_FILE" "$TMP_NAMES"
|
| 22 |
+
rm -rf "$TMP_DIR"
|
| 23 |
}
|
| 24 |
trap cleanup_tmp EXIT
|
| 25 |
|
| 26 |
# ---------------------------------------------------------------------------
|
| 27 |
+
# notify_feishu TITLE BODY
|
|
|
|
|
|
|
| 28 |
# ---------------------------------------------------------------------------
|
| 29 |
notify_feishu() {
|
| 30 |
[ -z "$FEISHU_WEBHOOK_URL" ] && return 0
|
| 31 |
+
PAYLOAD=$(jq -n --arg title "$1" --arg body "$2" '{
|
| 32 |
+
msg_type: "post",
|
| 33 |
+
content: { post: { zh_cn: {
|
| 34 |
+
title: $title,
|
| 35 |
+
content: [[{"tag": "text", "text": $body}]]
|
| 36 |
+
}}}
|
| 37 |
+
}')
|
| 38 |
+
curl -sf -X POST -H "Content-Type: application/json" \
|
| 39 |
+
-d "$PAYLOAD" "$FEISHU_WEBHOOK_URL" > /dev/null 2>&1 \
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
&& echo "${LOG_PREFIX} Feishu notification sent." \
|
| 41 |
|| echo "${LOG_PREFIX} WARNING: Failed to send Feishu notification."
|
| 42 |
}
|
| 43 |
|
| 44 |
+
# ---------------------------------------------------------------------------
|
| 45 |
+
# delete_one NAME — writes result to $TMP_DIR/<name>.result
|
| 46 |
+
# Format: "OK <name>" or "ERR <name> <http_status>"
|
| 47 |
+
# ---------------------------------------------------------------------------
|
| 48 |
+
delete_one() {
|
| 49 |
+
NAME="$1"
|
| 50 |
+
ENCODED=$(printf '%s' "$NAME" | sed 's/@/%40/g; s/ /%20/g')
|
| 51 |
+
RESULT_FILE="${TMP_DIR}/$(printf '%s' "$NAME" | md5sum | cut -d' ' -f1).result"
|
| 52 |
+
|
| 53 |
+
HTTP_ST=$(curl -s -o /dev/null -w "%{http_code}" \
|
| 54 |
+
-X DELETE \
|
| 55 |
+
-H "Authorization: Bearer ${MANAGEMENT_PASSWORD}" \
|
| 56 |
+
"${API_BASE}/auth-files?name=${ENCODED}" 2>/dev/null)
|
| 57 |
+
|
| 58 |
+
if [ "$HTTP_ST" = "200" ]; then
|
| 59 |
+
printf 'OK %s\n' "$NAME" > "$RESULT_FILE"
|
| 60 |
+
else
|
| 61 |
+
printf 'ERR %s %s\n' "$NAME" "$HTTP_ST" > "$RESULT_FILE"
|
| 62 |
+
fi
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
# ---------------------------------------------------------------------------
|
| 66 |
# Main
|
| 67 |
# ---------------------------------------------------------------------------
|
|
|
|
| 71 |
exit 1
|
| 72 |
fi
|
| 73 |
|
| 74 |
+
echo "${LOG_PREFIX} Starting invalid token cleanup... (concurrency=${CONCURRENCY})"
|
| 75 |
|
| 76 |
# --- Fetch auth file list ---------------------------------------------------
|
| 77 |
HTTP_STATUS=$(curl -s -o "$TMP_FILE" -w "%{http_code}" \
|
|
|
|
| 81 |
if [ "$HTTP_STATUS" != "200" ]; then
|
| 82 |
MSG="API 返回 HTTP ${HTTP_STATUS},服务可能尚未就绪。"
|
| 83 |
echo "${LOG_PREFIX} ERROR: ${MSG}"
|
| 84 |
+
notify_feishu "❌ Token 清理失败" "[TIME] ${TIMESTAMP}
|
|
|
|
| 85 |
|
| 86 |
[ERROR] ${MSG}"
|
| 87 |
exit 1
|
|
|
|
| 89 |
|
| 90 |
if ! jq empty "$TMP_FILE" 2>/dev/null; then
|
| 91 |
echo "${LOG_PREFIX} ERROR: Invalid JSON response"
|
| 92 |
+
notify_feishu "❌ Token 清理失败" "[TIME] ${TIMESTAMP}
|
|
|
|
| 93 |
|
| 94 |
[ERROR] API 返回了无效的 JSON 响应"
|
| 95 |
exit 1
|
|
|
|
| 98 |
TOTAL=$(jq '.files | length' "$TMP_FILE")
|
| 99 |
echo "${LOG_PREFIX} Total auth files: ${TOTAL}"
|
| 100 |
|
| 101 |
+
[ "$TOTAL" -eq 0 ] && { echo "${LOG_PREFIX} No auth files found."; exit 0; }
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
# --- Identify tokens to delete ---------------------------------------------
|
|
|
|
|
|
|
|
|
|
| 104 |
jq -r '
|
| 105 |
.files[] |
|
| 106 |
select(.runtime_only != true) |
|
|
|
|
| 115 |
.name
|
| 116 |
' "$TMP_FILE" > "$TMP_NAMES"
|
| 117 |
|
| 118 |
+
TO_DELETE=$(grep -c '' "$TMP_NAMES" 2>/dev/null || echo 0)
|
| 119 |
echo "${LOG_PREFIX} Tokens to delete: ${TO_DELETE}"
|
| 120 |
|
| 121 |
if [ "$TO_DELETE" -eq 0 ]; then
|
| 122 |
echo "${LOG_PREFIX} All tokens healthy, nothing to clean up."
|
| 123 |
+
notify_feishu "✅ Token 状态检查" "[TIME] ${TIMESTAMP}
|
|
|
|
| 124 |
|
| 125 |
[STATS] 共 ${TOTAL} 个 token,全部健康
|
| 126 |
|
|
|
|
| 128 |
exit 0
|
| 129 |
fi
|
| 130 |
|
| 131 |
+
# Log the plan
|
|
|
|
| 132 |
while IFS= read -r NAME; do
|
| 133 |
+
STATUS=$(jq -r --arg n "$NAME" '.files[] | select(.name==$n) | .status // "unknown"' "$TMP_FILE")
|
|
|
|
|
|
|
|
|
|
| 134 |
echo "${LOG_PREFIX} - ${NAME} (${STATUS})"
|
| 135 |
done < "$TMP_NAMES"
|
| 136 |
|
| 137 |
+
# --- Parallel DELETE with concurrency control ------------------------------
|
| 138 |
+
mkdir -p "$TMP_DIR"
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
ACTIVE=0
|
| 141 |
while IFS= read -r NAME; do
|
| 142 |
[ -z "$NAME" ] && continue
|
|
|
|
| 143 |
|
| 144 |
+
# Launch delete in background
|
| 145 |
+
delete_one "$NAME" &
|
| 146 |
+
ACTIVE=$((ACTIVE + 1))
|
|
|
|
| 147 |
|
| 148 |
+
# When we hit the concurrency limit, wait for all current batch to finish
|
| 149 |
+
if [ "$ACTIVE" -ge "$CONCURRENCY" ]; then
|
| 150 |
+
wait
|
| 151 |
+
ACTIVE=0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
fi
|
| 153 |
+
done < "$TMP_NAMES"
|
| 154 |
+
|
| 155 |
+
# Wait for any remaining background jobs
|
| 156 |
+
wait
|
| 157 |
+
|
| 158 |
+
# --- Collect results -------------------------------------------------------
|
| 159 |
+
DELETED=0
|
| 160 |
+
ERRORS=0
|
| 161 |
+
DETAIL_LINES=""
|
| 162 |
+
ERROR_LINES=""
|
| 163 |
+
|
| 164 |
+
# Re-read the names file to preserve status info for the notification
|
| 165 |
+
while IFS= read -r NAME; do
|
| 166 |
+
[ -z "$NAME" ] && continue
|
| 167 |
+
RESULT_FILE="${TMP_DIR}/$(printf '%s' "$NAME" | md5sum | cut -d' ' -f1).result"
|
| 168 |
+
RESULT=$(cat "$RESULT_FILE" 2>/dev/null)
|
| 169 |
+
|
| 170 |
+
case "$RESULT" in
|
| 171 |
+
OK*)
|
| 172 |
+
STATUS=$(jq -r --arg n "$NAME" '.files[] | select(.name==$n) | .status // "unknown"' "$TMP_FILE")
|
| 173 |
+
echo "${LOG_PREFIX} DELETED: ${NAME}"
|
| 174 |
+
DELETED=$((DELETED + 1))
|
| 175 |
+
DETAIL_LINES="${DETAIL_LINES} • ${NAME} [${STATUS}]\n"
|
| 176 |
+
;;
|
| 177 |
+
ERR*)
|
| 178 |
+
HTTP_ST=$(echo "$RESULT" | awk '{print $3}')
|
| 179 |
+
echo "${LOG_PREFIX} ERROR deleting ${NAME} (HTTP ${HTTP_ST})"
|
| 180 |
+
ERRORS=$((ERRORS + 1))
|
| 181 |
+
ERROR_LINES="${ERROR_LINES} • ${NAME} (HTTP ${HTTP_ST})\n"
|
| 182 |
+
;;
|
| 183 |
+
*)
|
| 184 |
+
echo "${LOG_PREFIX} WARNING: No result for ${NAME}"
|
| 185 |
+
ERRORS=$((ERRORS + 1))
|
| 186 |
+
ERROR_LINES="${ERROR_LINES} • ${NAME} (no result)\n"
|
| 187 |
+
;;
|
| 188 |
+
esac
|
| 189 |
done < "$TMP_NAMES"
|
| 190 |
|
| 191 |
echo "${LOG_PREFIX} Done. Deleted: ${DELETED}, Errors: ${ERRORS}"
|
| 192 |
|
| 193 |
# --- Feishu notification ---------------------------------------------------
|
| 194 |
+
# printf to interpret \n in the accumulated lines
|
| 195 |
+
DETAIL_LINES=$(printf '%b' "$DETAIL_LINES")
|
| 196 |
+
ERROR_LINES=$(printf '%b' "$ERROR_LINES")
|
| 197 |
+
|
| 198 |
if [ "$ERRORS" -gt 0 ]; then
|
| 199 |
+
notify_feishu "⚠️ Token 清理完成(有错误)" "[TIME] ${TIMESTAMP}
|
|
|
|
| 200 |
|
| 201 |
[STATS] 共 ${TOTAL} 个 token,发现 ${TO_DELETE} 个失效
|
| 202 |
|
|
|
|
| 206 |
[FAIL] 删除失败 ${ERRORS} 个:
|
| 207 |
${ERROR_LINES}────────────────────"
|
| 208 |
else
|
| 209 |
+
notify_feishu "🧹 Token 清理完成" "[TIME] ${TIMESTAMP}
|
|
|
|
| 210 |
|
| 211 |
[STATS] 共 ${TOTAL} 个 token,清理 ${DELETED} 个失效
|
| 212 |
|