ohmyapi commited on
Commit
9727ff0
ยท
verified ยท
1 Parent(s): 3c7791a

feat: add automatic token cleanup with crond + fixed /tmp/crontabs for HF read-only fs

Browse files
Files changed (4) hide show
  1. Dockerfile +6 -3
  2. cleanup_tokens.sh +217 -0
  3. config.yaml +1 -1
  4. entrypoint.sh +54 -0
Dockerfile CHANGED
@@ -2,17 +2,20 @@ FROM eceasy/cli-proxy-api:latest
2
 
3
  USER root
4
 
5
- RUN apk add --no-cache bash libc6-compat gcompat
6
 
7
  WORKDIR /app
8
  RUN cp /CLIProxyAPI/CLIProxyAPI ./cli-proxy-api && chmod +x ./cli-proxy-api
9
  RUN mkdir -p /tmp/.cli-proxy-api /tmp/logs /tmp/pg_cache/pgstore && chmod -R 777 /tmp
10
 
11
  COPY config.yaml /app/config.yaml
12
- # ไฟฎๅค config.example.yaml ็ผบๅคฑ
 
 
13
  RUN cp /app/config.yaml /app/config.example.yaml
 
14
 
15
  ENV TZ=Asia/Shanghai
16
  EXPOSE 7860
17
 
18
- CMD ["./cli-proxy-api", "--config", "/app/config.yaml"]
 
2
 
3
  USER root
4
 
5
+ RUN apk add --no-cache bash libc6-compat gcompat jq
6
 
7
  WORKDIR /app
8
  RUN cp /CLIProxyAPI/CLIProxyAPI ./cli-proxy-api && chmod +x ./cli-proxy-api
9
  RUN mkdir -p /tmp/.cli-proxy-api /tmp/logs /tmp/pg_cache/pgstore && chmod -R 777 /tmp
10
 
11
  COPY config.yaml /app/config.yaml
12
+ COPY cleanup_tokens.sh /app/cleanup_tokens.sh
13
+ COPY entrypoint.sh /app/entrypoint.sh
14
+
15
  RUN cp /app/config.yaml /app/config.example.yaml
16
+ RUN chmod +x /app/cleanup_tokens.sh /app/entrypoint.sh
17
 
18
  ENV TZ=Asia/Shanghai
19
  EXPOSE 7860
20
 
21
+ CMD ["/app/entrypoint.sh"]
cleanup_tokens.sh ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
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"
12
+ 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
+ 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
+ # ---------------------------------------------------------------------------
68
+
69
+ if [ -z "$MANAGEMENT_PASSWORD" ]; then
70
+ echo "${LOG_PREFIX} ERROR: MANAGEMENT_PASSWORD not set, skipping cleanup"
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}" \
78
+ -H "Authorization: Bearer ${MANAGEMENT_PASSWORD}" \
79
+ "${API_BASE}/auth-files" 2>/dev/null)
80
+
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
88
+ fi
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
96
+ fi
97
+
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) |
107
+ select(
108
+ .unavailable == true or
109
+ (.status != null and (
110
+ .status == "error" or .status == "expired" or
111
+ .status == "invalid" or .status == "failed" or
112
+ .status == "unauthorized" or .status == "quota_exceeded"
113
+ ))
114
+ ) |
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
+
127
+ [RESULT] ๆ— ้œ€ๆธ…็†"
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
+
203
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
204
+ [SUCCESS] ๅทฒๅˆ ้™ค ${DELETED} ไธช๏ผš
205
+ ${DETAIL_LINES}
206
+ [FAIL] ๅˆ ้™คๅคฑ่ดฅ ${ERRORS} ไธช๏ผš
207
+ ${ERROR_LINES}โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€"
208
+ else
209
+ notify_feishu "๐Ÿงน Token ๆธ…็†ๅฎŒๆˆ" "[TIME] ${TIMESTAMP}
210
+
211
+ [STATS] ๅ…ฑ ${TOTAL} ไธช token๏ผŒๆธ…็† ${DELETED} ไธชๅคฑๆ•ˆ
212
+
213
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
214
+ [REMOVED] ๅทฒๅˆ ้™ค๏ผš
215
+ ${DETAIL_LINES}โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
216
+ [RESULT] Success: ${DELETED}/${TO_DELETE} | Failed: 0/${TO_DELETE}"
217
+ fi
config.yaml CHANGED
@@ -12,4 +12,4 @@ remote-management:
12
  secret-key: "${MANAGEMENT_PASSWORD}"
13
 
14
  commercial-mode: true
15
- debug: false
 
12
  secret-key: "${MANAGEMENT_PASSWORD}"
13
 
14
  commercial-mode: true
15
+ debug: false
entrypoint.sh ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ #
3
+ # entrypoint.sh - Start crond for scheduled cleanup, then launch CLIProxyAPI
4
+ #
5
+ # Environment variables:
6
+ # CLEANUP_CRON_SCHEDULE - Cron schedule for token cleanup (default: "0 */6 * * *", every 6h)
7
+ # CLEANUP_STARTUP_DELAY - Seconds to wait after service is ready before first cleanup (default: 10)
8
+ #
9
+
10
+ echo "[entrypoint] Starting up at $(date)"
11
+
12
+ CLEANUP_CRON_SCHEDULE="${CLEANUP_CRON_SCHEDULE:-0 */6 * * *}"
13
+ CLEANUP_STARTUP_DELAY="${CLEANUP_STARTUP_DELAY:-10}"
14
+ CRON_OK=0
15
+
16
+ mkdir -p /tmp/logs /tmp/crontabs
17
+
18
+ # Write crontab to /tmp (HuggingFace root filesystem is read-only)
19
+ cat > /tmp/crontabs/root << EOF
20
+ ${CLEANUP_CRON_SCHEDULE} /app/cleanup_tokens.sh >> /tmp/logs/cleanup.log 2>&1
21
+ EOF
22
+
23
+ echo "[entrypoint] Cleanup schedule: ${CLEANUP_CRON_SCHEDULE}"
24
+
25
+ # Try crond; if it fails, fall back to a sleep loop
26
+ if crond -c /tmp/crontabs -l 8 -L /tmp/logs/crond.log 2>/dev/null; then
27
+ echo "[entrypoint] crond started"
28
+ CRON_OK=1
29
+ else
30
+ echo "[entrypoint] WARNING: crond failed, will use sleep-loop fallback"
31
+ fi
32
+
33
+ # Background: wait for API ready, run initial cleanup, then optionally loop
34
+ (
35
+ MAX_WAIT=120
36
+ WAITED=0
37
+ while ! wget -q -O /dev/null http://localhost:7860/ 2>/dev/null; do
38
+ sleep 2
39
+ WAITED=$((WAITED + 2))
40
+ [ "$WAITED" -ge "$MAX_WAIT" ] && break
41
+ done
42
+ sleep "${CLEANUP_STARTUP_DELAY}"
43
+ /app/cleanup_tokens.sh >> /tmp/logs/cleanup.log 2>&1
44
+
45
+ if [ "$CRON_OK" = "0" ]; then
46
+ while true; do
47
+ sleep 21600
48
+ /app/cleanup_tokens.sh >> /tmp/logs/cleanup.log 2>&1
49
+ done
50
+ fi
51
+ ) &
52
+
53
+ echo "[entrypoint] Launching cli-proxy-api..."
54
+ exec /app/cli-proxy-api --config /app/config.yaml "$@"