ohmyapi commited on
Commit
bb4eb6e
·
verified ·
1 Parent(s): e000b8b

Update cleanup_tokens.sh

Browse files
Files changed (1) hide show
  1. 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 - CLIProxyAPI management API password (required)
7
- # FEISHU_WEBHOOK_URL - Feishu bot webhook URL for notifications (optional)
8
- # If not set, no notifications are sent.
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 BODY_TEXT
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
- TITLE="$1"
31
- BODY="$2"
32
-
33
- # Build content as a single text paragraph (preserves newlines via \n in JSON)
34
- # jq handles all escaping automatically
35
- PAYLOAD=$(jq -n \
36
- --arg title "$TITLE" \
37
- --arg body "$BODY" \
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
- if [ "$TOTAL" -eq 0 ]; then
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=$(wc -l < "$TMP_NAMES" | tr -d ' ')
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
- # --- Print deletion plan ---------------------------------------------------
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
- # --- Delete invalid tokens -------------------------------------------------
146
- DELETED=0
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
- DEL_STATUS=$(curl -s -o /tmp/del_resp_$$.json -w "%{http_code}" \
155
- -X DELETE \
156
- -H "Authorization: Bearer ${MANAGEMENT_PASSWORD}" \
157
- "${API_BASE}/auth-files?name=${ENCODED}" 2>/dev/null)
158
 
159
- if [ "$DEL_STATUS" = "200" ]; then
160
- echo "${LOG_PREFIX} DELETED: ${NAME}"
161
- DELETED=$((DELETED + 1))
162
- else
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
- rm -f /tmp/del_resp_$$.json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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