New qwen-token-refresh.sh script provides automatic token refresh for OpenClaw, NanoBot, PicoClaw, NanoClaw (ZeroClaw has native support). Features: - Check token status and expiry - Auto-refresh when < 5 min remaining - Background daemon mode (5 min intervals) - Systemd service installation - Updates both oauth_creds.json and .env file Usage: ./scripts/qwen-token-refresh.sh --status # Check status ./scripts/qwen-token-refresh.sh # Refresh if needed ./scripts/qwen-token-refresh.sh --daemon # Background daemon ./scripts/qwen-token-refresh.sh --install # Systemd service Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
336 lines
9.9 KiB
Bash
336 lines
9.9 KiB
Bash
#!/bin/bash
|
|
# qwen-token-refresh.sh - Auto-refresh Qwen OAuth token
|
|
# Usage:
|
|
# ./qwen-token-refresh.sh # Check and refresh if expired
|
|
# ./qwen-token-refresh.sh --daemon # Run as background daemon
|
|
# ./qwen-token-refresh.sh --status # Check token status
|
|
#
|
|
# This enables auto token refresh for ALL platforms:
|
|
# OpenClaw, NanoBot, PicoClaw, NanoClaw
|
|
#
|
|
# For ZeroClaw, this is NOT needed (native support).
|
|
|
|
set -e
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
CREDS_FILE="$HOME/.qwen/oauth_creds.json"
|
|
ENV_FILE="$HOME/.qwen/.env"
|
|
REFRESH_URL="https://chat.qwen.ai/api/v1/oauth2/token"
|
|
API_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
|
|
|
# Token refresh buffer (refresh 5 minutes before expiry)
|
|
REFRESH_BUFFER_SECONDS=300
|
|
|
|
check_dependencies() {
|
|
if ! command -v jq &> /dev/null; then
|
|
echo -e "${RED}Error: jq is required${NC}"
|
|
echo "Install with: sudo apt install jq"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v curl &> /dev/null; then
|
|
echo -e "${RED}Error: curl is required${NC}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
get_token_status() {
|
|
if [ ! -f "$CREDS_FILE" ]; then
|
|
echo "NOT_FOUND"
|
|
return
|
|
fi
|
|
|
|
local expiry_date=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
|
|
local current_time=$(date +%s)000
|
|
|
|
if [ "$expiry_date" -eq 0 ]; then
|
|
echo "UNKNOWN"
|
|
return
|
|
fi
|
|
|
|
local expiry_seconds=$((expiry_date / 1000))
|
|
local current_seconds=$((current_time / 1000))
|
|
local remaining=$((expiry_seconds - current_seconds))
|
|
|
|
if [ "$remaining" -lt 0 ]; then
|
|
echo "EXPIRED"
|
|
elif [ "$remaining" -lt "$REFRESH_BUFFER_SECONDS" ]; then
|
|
echo "EXPIRING_SOON"
|
|
else
|
|
echo "VALID"
|
|
fi
|
|
}
|
|
|
|
format_time_remaining() {
|
|
local expiry_date=$1
|
|
local expiry_seconds=$((expiry_date / 1000))
|
|
local current_seconds=$(date +%s)
|
|
local remaining=$((expiry_seconds - current_seconds))
|
|
|
|
if [ "$remaining" -lt 0 ]; then
|
|
echo "expired"
|
|
elif [ "$remaining" -lt 60 ]; then
|
|
echo "${remaining}s"
|
|
elif [ "$remaining" -lt 3600 ]; then
|
|
echo "$((remaining / 60))m"
|
|
else
|
|
echo "$((remaining / 3600))h $(((remaining % 3600) / 60))m"
|
|
fi
|
|
}
|
|
|
|
refresh_token() {
|
|
local refresh_token=$(cat "$CREDS_FILE" | jq -r '.refresh_token // empty')
|
|
|
|
if [ -z "$refresh_token" ] || [ "$refresh_token" = "null" ]; then
|
|
echo -e "${RED}Error: No refresh_token found in credentials${NC}"
|
|
echo "Please re-authenticate: qwen --auth-type qwen-oauth -p 'test'"
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Refreshing token...${NC}"
|
|
|
|
local response=$(curl -s -X POST "$REFRESH_URL" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "grant_type=refresh_token" \
|
|
-d "refresh_token=$refresh_token" \
|
|
2>/dev/null)
|
|
|
|
if [ -z "$response" ]; then
|
|
echo -e "${RED}Error: Empty response from refresh endpoint${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local new_access_token=$(echo "$response" | jq -r '.access_token // empty')
|
|
local new_refresh_token=$(echo "$response" | jq -r '.refresh_token // empty')
|
|
local expires_in=$(echo "$response" | jq -r '.expires_in // 3600')
|
|
|
|
if [ -z "$new_access_token" ] || [ "$new_access_token" = "null" ]; then
|
|
echo -e "${RED}Error: Failed to get new access_token${NC}"
|
|
echo "Response: $response"
|
|
echo ""
|
|
echo "Please re-authenticate: qwen --auth-type qwen-oauth -p 'test'"
|
|
return 1
|
|
fi
|
|
|
|
# Calculate new expiry date (current time + expires_in, in milliseconds)
|
|
local current_ms=$(date +%s)000
|
|
local expires_in_ms=$((expires_in * 1000))
|
|
local new_expiry=$((current_ms + expires_in_ms))
|
|
|
|
# Update credentials file
|
|
local resource_url=$(cat "$CREDS_FILE" | jq -r '.resource_url // "portal.qwen.ai"')
|
|
|
|
cat > "$CREDS_FILE" << EOF
|
|
{
|
|
"access_token": "$new_access_token",
|
|
"refresh_token": "${new_refresh_token:-$refresh_token}",
|
|
"token_type": "Bearer",
|
|
"resource_url": "$resource_url",
|
|
"expiry_date": $new_expiry
|
|
}
|
|
EOF
|
|
|
|
chmod 600 "$CREDS_FILE"
|
|
|
|
# Update .env file for other platforms
|
|
mkdir -p "$HOME/.qwen"
|
|
cat > "$ENV_FILE" << EOF
|
|
# Qwen OAuth Configuration (auto-refreshed: $(date))
|
|
export OPENAI_API_KEY="$new_access_token"
|
|
export OPENAI_BASE_URL="$API_BASE_URL"
|
|
export OPENAI_MODEL="coder-model"
|
|
EOF
|
|
|
|
chmod 600 "$ENV_FILE"
|
|
|
|
echo -e "${GREEN}✅ Token refreshed successfully!${NC}"
|
|
echo " New token expires in: $(format_time_remaining $new_expiry)"
|
|
echo ""
|
|
echo "Environment updated. Run to apply:"
|
|
echo " source ~/.qwen/.env"
|
|
|
|
return 0
|
|
}
|
|
|
|
show_status() {
|
|
echo "🦞 Qwen OAuth Token Status"
|
|
echo "==========================="
|
|
echo ""
|
|
|
|
if [ ! -f "$CREDS_FILE" ]; then
|
|
echo -e "${RED}❌ Credentials file not found${NC}"
|
|
echo " Expected: $CREDS_FILE"
|
|
echo ""
|
|
echo "To authenticate, run:"
|
|
echo " qwen --auth-type qwen-oauth -p 'test'"
|
|
exit 1
|
|
fi
|
|
|
|
local status=$(get_token_status)
|
|
local expiry_date=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
|
|
local access_token=$(cat "$CREDS_FILE" | jq -r '.access_token // ""')
|
|
|
|
echo "Credentials: $CREDS_FILE"
|
|
echo "Token: ${access_token:0:20}...${access_token: -10}"
|
|
echo ""
|
|
|
|
case "$status" in
|
|
VALID)
|
|
echo -e "Status: ${GREEN}✅ VALID${NC}"
|
|
echo "Expires in: $(format_time_remaining $expiry_date)"
|
|
;;
|
|
EXPIRING_SOON)
|
|
echo -e "Status: ${YELLOW}⚠️ EXPIRING SOON${NC}"
|
|
echo "Expires in: $(format_time_remaining $expiry_date)"
|
|
echo ""
|
|
echo "Run refresh: $0"
|
|
;;
|
|
EXPIRED)
|
|
echo -e "Status: ${RED}❌ EXPIRED${NC}"
|
|
echo ""
|
|
echo "Run refresh: $0"
|
|
;;
|
|
UNKNOWN)
|
|
echo -e "Status: ${YELLOW}⚠️ UNKNOWN${NC}"
|
|
echo "Expiry date not found in credentials"
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "FREE tier: 2,000 requests/day, 60 req/min"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
}
|
|
|
|
run_daemon() {
|
|
echo "🦞 Qwen Token Refresh Daemon"
|
|
echo "============================="
|
|
echo ""
|
|
echo "Starting background refresh service..."
|
|
echo "Check interval: 5 minutes"
|
|
echo "Refresh buffer: ${REFRESH_BUFFER_SECONDS}s before expiry"
|
|
echo ""
|
|
echo "Press Ctrl+C to stop"
|
|
echo ""
|
|
|
|
while true; do
|
|
local status=$(get_token_status)
|
|
|
|
case "$status" in
|
|
EXPIRED|EXPIRING_SOON)
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - Token needs refresh ($status)"
|
|
if refresh_token > /dev/null 2>&1; then
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - ✅ Token refreshed"
|
|
else
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - ❌ Refresh failed"
|
|
fi
|
|
;;
|
|
VALID)
|
|
local expiry=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - Token valid ($(format_time_remaining $expiry) remaining)"
|
|
;;
|
|
*)
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - Token status: $status"
|
|
;;
|
|
esac
|
|
|
|
sleep 300 # Check every 5 minutes
|
|
done
|
|
}
|
|
|
|
install_systemd_service() {
|
|
local service_dir="$HOME/.config/systemd/user"
|
|
local service_file="$service_dir/qwen-token-refresh.service"
|
|
|
|
mkdir -p "$service_dir"
|
|
|
|
cat > "$service_file" << 'EOF'
|
|
[Unit]
|
|
Description=Qwen OAuth Token Auto-Refresh
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=%h/.local/bin/qwen-token-refresh.sh --daemon
|
|
Restart=always
|
|
RestartSec=60
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
|
|
echo -e "${GREEN}✅ Systemd service installed${NC}"
|
|
echo ""
|
|
echo "To enable and start:"
|
|
echo " systemctl --user daemon-reload"
|
|
echo " systemctl --user enable qwen-token-refresh"
|
|
echo " systemctl --user start qwen-token-refresh"
|
|
echo ""
|
|
echo "To check status:"
|
|
echo " systemctl --user status qwen-token-refresh"
|
|
}
|
|
|
|
# Main
|
|
check_dependencies
|
|
|
|
case "${1:-}" in
|
|
--status|-s)
|
|
show_status
|
|
;;
|
|
--daemon|-d)
|
|
run_daemon
|
|
;;
|
|
--install|-i)
|
|
install_systemd_service
|
|
;;
|
|
--help|-h)
|
|
echo "Usage: $0 [OPTION]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " (none) Check and refresh token if expired"
|
|
echo " --status Show token status"
|
|
echo " --daemon Run as background refresh daemon"
|
|
echo " --install Install as systemd user service"
|
|
echo " --help Show this help"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 # Refresh if needed"
|
|
echo " $0 --status # Check status"
|
|
echo " $0 --daemon # Run background daemon"
|
|
echo " $0 --install # Install systemd service"
|
|
;;
|
|
*)
|
|
# Default: check and refresh if needed
|
|
if [ ! -f "$CREDS_FILE" ]; then
|
|
echo -e "${RED}Error: Credentials not found${NC}"
|
|
echo "Run: qwen --auth-type qwen-oauth -p 'test'"
|
|
exit 1
|
|
fi
|
|
|
|
local status=$(get_token_status)
|
|
|
|
case "$status" in
|
|
VALID)
|
|
local expiry=$(cat "$CREDS_FILE" | jq -r '.expiry_date // 0')
|
|
echo -e "${GREEN}✅ Token is valid${NC} ($(format_time_remaining $expiry) remaining)"
|
|
echo ""
|
|
echo "To force refresh anyway, delete ~/.qwen/oauth_creds.json and re-auth"
|
|
;;
|
|
EXPIRED|EXPIRING_SOON)
|
|
echo -e "${YELLOW}Token needs refresh ($status)${NC}"
|
|
refresh_token
|
|
;;
|
|
*)
|
|
echo -e "${YELLOW}Unknown token status, attempting refresh...${NC}"
|
|
refresh_token
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|