Last active
April 25, 2026 12:39
-
-
Save NNdroid/c71e4c1c26c475ee17566b41f06ac5e9 to your computer and use it in GitHub Desktop.
yggdrasil installer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ================= 配置區域 ================= | |
| CONFIG_FILE="/etc/yggdrasil/yggdrasil.conf" | |
| BACKUP_FILE="${CONFIG_FILE}.bak" # 備份文件保留在原目錄,不作臨時文件處理 | |
| PROXY="" | |
| TARGET_URL="https://publicpeers.neilalexander.dev/" | |
| # ⚠️ 再次提醒:請確保已更換為新的 Token | |
| TG_BOT_TOKEN="YOUR_NEW_TG_BOT_TOKEN" | |
| TG_CHAT_ID="YOUR_TG_CHAT_ID" | |
| # ============================================ | |
| # 初始化日誌緩衝區 | |
| LOG_BUFFER="" | |
| # 創建臨時工作目錄 | |
| WORK_DIR=$(mktemp -d -t yggdrasil_update_XXXXXX) | |
| if [ ! -d "$WORK_DIR" ]; then | |
| echo "無法創建臨時目錄,腳本退出。" | |
| exit 1 | |
| fi | |
| # 定義臨時文件路徑 | |
| CURRENT_JSON="${WORK_DIR}/current.json" | |
| TMP_FILE="${WORK_DIR}/yggdrasil.conf.tmp" | |
| JQ_ERR_LOG="${WORK_DIR}/jq_error.log" | |
| # 日誌記錄函數 | |
| log() { | |
| local level=$(echo "$1" | tr 'a-z' 'A-Z') | |
| shift | |
| local msg="$*" | |
| local timestamp=$(date +'%Y-%m-%d %H:%M:%S') | |
| local color_reset='\033[0m' | |
| local color_red='\033[0;31m' | |
| local color_green='\033[0;32m' | |
| local color_yellow='\033[0;33m' | |
| local color_cyan='\033[0;36m' | |
| local color_code="" | |
| case "$level" in | |
| INFO) color_code="${color_cyan}" ;; | |
| WARN) color_code="${color_yellow}" ;; | |
| ERROR) color_code="${color_red}" ;; | |
| SUCCESS) color_code="${color_green}" ;; | |
| *) color_code="${color_reset}" ;; | |
| esac | |
| # 1. 屏幕輸出 | |
| echo -e "${color_code}[${timestamp}] [${level}] ${msg}${color_reset}" | |
| # 2. 存入緩衝區 (用於 TG 推送) | |
| LOG_BUFFER="${LOG_BUFFER}[${timestamp}] [${level}] ${msg}"$'\n' | |
| } | |
| # 發送 Telegram 通知 | |
| send_log_to_telegram() { | |
| if [[ -z "$TG_BOT_TOKEN" || "$TG_BOT_TOKEN" == "YOUR_NEW_TG_BOT_TOKEN" || -z "$TG_CHAT_ID" ]]; then | |
| echo "未配置有效的 Telegram Token,跳過發送。" | |
| return 0 | |
| fi | |
| [ -z "$LOG_BUFFER" ] && return 0 | |
| echo -e "\033[0;36m正在發送日誌到 Telegram...\033[0m" | |
| curl --retry 3 --retry-delay 2 -sS -k "${PROXY}https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \ | |
| --data-urlencode "chat_id=${TG_CHAT_ID}" \ | |
| --data-urlencode "text=${LOG_BUFFER}" >/dev/null 2>&1 || true | |
| } | |
| # 腳本退出時的清理函數 | |
| cleanup() { | |
| # 1. 發送 TG 通知 | |
| send_log_to_telegram | |
| # 2. 清理臨時目錄 | |
| if [ -d "$WORK_DIR" ]; then | |
| rm -rf "$WORK_DIR" | |
| echo -e "\033[0;32m[系統] 臨時目錄 $WORK_DIR 已清理。\033[0m" | |
| fi | |
| } | |
| # 綁定 EXIT 信號,無論腳本如何結束都會執行 cleanup | |
| trap cleanup EXIT | |
| # ================= 主程序開始 ================= | |
| log "INFO" "=== $(hostname) ===" | |
| log "INFO" "=== 開始執行 Yggdrasil 節點更新任務 ===" | |
| # 1. 前置檢查 | |
| if [ ! -f "$CONFIG_FILE" ]; then | |
| log "ERROR" "找不到配置文件:$CONFIG_FILE" | |
| exit 1 | |
| fi | |
| if ! command -v jq &> /dev/null; then | |
| log "ERROR" "系統未安裝 jq,請先執行 apt install jq 獲取依賴。" | |
| exit 1 | |
| fi | |
| if ! command -v yggdrasil &> /dev/null; then | |
| log "ERROR" "系統未安裝 Yggdrasil 命令,無法執行格式轉換。" | |
| exit 1 | |
| fi | |
| # 2. 判斷配置格式並統一轉換為 JSON | |
| log "INFO" "正在檢測配置文件格式..." | |
| IS_HJSON=0 | |
| # 如果 jq 解析失敗,說明很可能是 HJSON (包含註解或非標準格式) | |
| if jq -e . "$CONFIG_FILE" >/dev/null 2>&1; then | |
| log "INFO" "檢測結果:標準 JSON 格式。" | |
| cp "$CONFIG_FILE" "$CURRENT_JSON" | |
| else | |
| log "INFO" "檢測結果:HJSON 格式。正在轉換為標準 JSON..." | |
| IS_HJSON=1 | |
| yggdrasil -normaliseconf -useconffile "$CONFIG_FILE" -json > "$CURRENT_JSON" | |
| if ! jq -e . "$CURRENT_JSON" >/dev/null 2>&1; then | |
| log "ERROR" "HJSON 轉換為 JSON 失敗,請手動檢查配置文件語法是否正確!" | |
| exit 1 | |
| fi | |
| fi | |
| # 3. 抓取節點 | |
| log "INFO" "正在獲取在線節點列表..." | |
| PEERS_JSON=$(curl -s "${PROXY}${TARGET_URL}" | perl -0777 -ne 'while (/<tr.*?>.*?<td.*?>(.*?)<\/td>.*?<td.*?>(.*?)<\/td>.*?<\/tr>/gs) { $addr = $1; $status = $2; $addr =~ s/<[^>]+>//g; $status =~ s/<[^>]+>//g; if ($status =~ /online/i && $addr =~ /(tcp|tls|quic|ws|wss):\/\//) { print "$addr\n"; } }' | jq -R . | jq -s .) | |
| # 4. 檢查抓取結果 | |
| if [ -z "$PEERS_JSON" ] || [ "$PEERS_JSON" == "[]" ]; then | |
| log "ERROR" "獲取節點失敗,或當前無在線節點(列表為空)!" | |
| exit 1 | |
| fi | |
| PEER_COUNT=$(echo "$PEERS_JSON" | jq '. | length') | |
| log "INFO" "成功獲取到 $PEER_COUNT 個在線節點。" | |
| # 5. 更新配置到臨時文件 | |
| log "INFO" "正在將新節點寫入臨時配置文件..." | |
| jq --argjson p "$PEERS_JSON" '.Peers = $p' "$CURRENT_JSON" > "$TMP_FILE" 2> "$JQ_ERR_LOG" | |
| # 6. 嚴格驗證臨時文件並替換 | |
| log "INFO" "正在校驗生成的配置文件合法性..." | |
| if [ $? -eq 0 ] && [ -s "$TMP_FILE" ] && jq -e . "$TMP_FILE" >/dev/null 2>&1; then | |
| log "INFO" "✅ 校驗通過!" | |
| # 備份 | |
| cp -p "$CONFIG_FILE" "$BACKUP_FILE" | |
| log "INFO" "原配置文件已備份至 $BACKUP_FILE" | |
| # 根據原格式寫回 | |
| if [ "$IS_HJSON" -eq 1 ]; then | |
| log "INFO" "正在將配置轉換回 HJSON 格式並寫入..." | |
| yggdrasil -normaliseconf -useconffile "$TMP_FILE" > "$CONFIG_FILE" | |
| else | |
| log "INFO" "正在寫入 JSON 格式配置..." | |
| cat "$TMP_FILE" > "$CONFIG_FILE" | |
| fi | |
| log "INFO" "新配置已成功寫入 $CONFIG_FILE" | |
| else | |
| # 驗證失敗 | |
| log "ERROR" "❌ 錯誤:生成的配置文件異常!" | |
| log "ERROR" "詳細報錯信息:$(cat "$JQ_ERR_LOG")" | |
| log "INFO" "已自動終止,原配置文件未受到任何影響。" | |
| exit 1 | |
| fi | |
| # 7. 重啟服務 | |
| log "INFO" "正在重啟 Yggdrasil 服務..." | |
| systemctl restart yggdrasil | |
| if [ $? -eq 0 ]; then | |
| log "SUCCESS" "✅ Yggdrasil 服務重啟成功!更新任務圓滿結束。" | |
| else | |
| log "WARN" "⚠️ 服務重啟命令執行完成,但返回了非 0 狀態,請手動檢查 systemctl status yggdrasil。" | |
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ================= 配置區域 ================= | |
| CONFIG_FILE="/etc/yggdrasil/yggdrasil.conf" | |
| BACKUP_FILE="${CONFIG_FILE}.bak" | |
| PROXY="" | |
| TARGET_URL="https://publicpeers.neilalexander.dev/" | |
| # ⚠️ 請確保已更換為新的 Token | |
| TG_BOT_TOKEN="YOUR_NEW_TG_BOT_TOKEN" | |
| TG_CHAT_ID="YOUR_TG_CHAT_ID" | |
| MAX_PEERS=5 # 最大保留節點數 | |
| TIMEOUT=2 # 每個節點的測速超時時間(秒) | |
| CONCURRENCY=20 # 並發測速線程數 | |
| # ============================================ | |
| # 初始化日誌緩衝區 | |
| LOG_BUFFER="" | |
| # 創建臨時工作目錄 | |
| WORK_DIR=$(mktemp -d -t yggdrasil_update_XXXXXX) | |
| if [ ! -d "$WORK_DIR" ]; then | |
| echo "無法創建臨時目錄,腳本退出。" | |
| exit 1 | |
| fi | |
| CURRENT_JSON="${WORK_DIR}/current.json" | |
| TMP_FILE="${WORK_DIR}/yggdrasil.conf.tmp" | |
| JQ_ERR_LOG="${WORK_DIR}/jq_error.log" | |
| RAW_PEERS_TXT="${WORK_DIR}/raw_peers.txt" | |
| TEST_SCRIPT="${WORK_DIR}/test_peer.sh" | |
| RESULTS_TXT="${WORK_DIR}/results.txt" | |
| # 日誌記錄函數 | |
| log() { | |
| local level=$(echo "$1" | tr 'a-z' 'A-Z') | |
| shift | |
| local msg="$*" | |
| local timestamp=$(date +'%Y-%m-%d %H:%M:%S') | |
| local color_reset='\033[0m' | |
| local color_red='\033[0;31m' | |
| local color_green='\033[0;32m' | |
| local color_yellow='\033[0;33m' | |
| local color_cyan='\033[0;36m' | |
| local color_code="" | |
| case "$level" in | |
| INFO) color_code="${color_cyan}" ;; | |
| WARN) color_code="${color_yellow}" ;; | |
| ERROR) color_code="${color_red}" ;; | |
| SUCCESS) color_code="${color_green}" ;; | |
| *) color_code="${color_reset}" ;; | |
| esac | |
| echo -e "${color_code}[${timestamp}] [${level}] ${msg}${color_reset}" | |
| LOG_BUFFER="${LOG_BUFFER}[${timestamp}] [${level}] ${msg}"$'\n' | |
| } | |
| # 發送 Telegram 通知 | |
| send_log_to_telegram() { | |
| if [[ -z "$TG_BOT_TOKEN" || "$TG_BOT_TOKEN" == "YOUR_NEW_TG_BOT_TOKEN" || -z "$TG_CHAT_ID" ]]; then | |
| return 0 | |
| fi | |
| [ -z "$LOG_BUFFER" ] && return 0 | |
| echo -e "\033[0;36m正在發送日誌到 Telegram...\033[0m" | |
| curl --retry 3 --retry-delay 2 -sS -k "${PROXY}https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \ | |
| --data-urlencode "chat_id=${TG_CHAT_ID}" \ | |
| --data-urlencode "text=${LOG_BUFFER}" >/dev/null 2>&1 || true | |
| } | |
| # 腳本退出時的清理函數 | |
| cleanup() { | |
| send_log_to_telegram | |
| if [ -d "$WORK_DIR" ]; then | |
| rm -rf "$WORK_DIR" | |
| fi | |
| } | |
| trap cleanup EXIT | |
| # ================= 主程序開始 ================= | |
| log "INFO" "=== $(hostname) ===" | |
| log "INFO" "=== 開始執行 Yggdrasil 節點測速更新任務 ===" | |
| # 1. 檢查依賴 | |
| for cmd in jq curl openssl awk perl xargs; do | |
| if ! command -v $cmd &> /dev/null; then | |
| log "ERROR" "系統未安裝 $cmd,請先安裝依賴。" | |
| exit 1 | |
| fi | |
| done | |
| # 2. 判斷配置格式並統一轉換為 JSON | |
| log "INFO" "正在檢測並備份配置文件格式..." | |
| IS_HJSON=0 | |
| if jq -e . "$CONFIG_FILE" >/dev/null 2>&1; then | |
| cp "$CONFIG_FILE" "$CURRENT_JSON" | |
| else | |
| IS_HJSON=1 | |
| yggdrasil -normaliseconf -useconffile "$CONFIG_FILE" -json > "$CURRENT_JSON" | |
| if ! jq -e . "$CURRENT_JSON" >/dev/null 2>&1; then | |
| log "ERROR" "HJSON 轉換為 JSON 失敗,請手動檢查配置!" | |
| exit 1 | |
| fi | |
| fi | |
| # 3. 抓取節點列表 | |
| log "INFO" "正在從官網抓取在線節點列表..." | |
| curl -s "${PROXY}${TARGET_URL}" | perl -0777 -ne 'while (/<tr.*?>.*?<td.*?>(.*?)<\/td>.*?<td.*?>(.*?)<\/td>.*?<\/tr>/gs) { $addr = $1; $status = $2; $addr =~ s/<[^>]+>//g; $status =~ s/<[^>]+>//g; if ($status =~ /online/i && $addr =~ /(tcp|tls|quic|ws|wss):\/\//) { print "$addr\n"; } }' > "$RAW_PEERS_TXT" | |
| TOTAL_FETCHED=$(wc -l < "$RAW_PEERS_TXT") | |
| if [ "$TOTAL_FETCHED" -eq 0 ]; then | |
| log "ERROR" "獲取節點失敗,或當前無在線節點!" | |
| exit 1 | |
| fi | |
| log "INFO" "成功獲取到 $TOTAL_FETCHED 個在線節點,開始測速過濾..." | |
| # 4. 生成並發測速腳本 (包含你之前提到的 QUIC 自適應邏輯) | |
| cat << 'EOF' > "$TEST_SCRIPT" | |
| #!/bin/bash | |
| URI="$1" | |
| TIMEOUT="$2" | |
| PROTO=$(echo "$URI" | awk -F'://' '{print $1}') | |
| HOST_PORT=${URI#*://} | |
| # 提取主機和端口 | |
| if [[ "$HOST_PORT" == \[*\]:* ]]; then | |
| HOST_NO_BRACKET=$(echo "$HOST_PORT" | grep -o '\[.*\]' | tr -d '[]') | |
| PORT=$(echo "$HOST_PORT" | grep -o ':[0-9]*$' | tr -d ':') | |
| else | |
| HOST_NO_BRACKET=$(echo "$HOST_PORT" | cut -d: -f1) | |
| PORT=$(echo "$HOST_PORT" | cut -d: -f2) | |
| fi | |
| START=$(date +%s%3N) | |
| SUCCESS=0 | |
| case "$PROTO" in | |
| tcp) | |
| timeout $TIMEOUT bash -c "</dev/tcp/$HOST_NO_BRACKET/$PORT" 2>/dev/null && SUCCESS=1 | |
| ;; | |
| tls) | |
| echo -n | timeout $TIMEOUT openssl s_client -connect "$HOST_PORT" >/dev/null 2>&1 && SUCCESS=1 | |
| ;; | |
| ws|wss) | |
| SCHEME="http" | |
| [[ "$PROTO" == "wss" ]] && SCHEME="https" | |
| if timeout $TIMEOUT curl -s -k -i -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" "${SCHEME}://${HOST_PORT}/" 2>/dev/null | grep -iq "HTTP/.* 101"; then | |
| SUCCESS=1 | |
| fi | |
| ;; | |
| quic) | |
| if openssl s_client -help 2>&1 | grep -q -- "-quic"; then | |
| echo -n | timeout $TIMEOUT openssl s_client -quic -connect "$HOST_PORT" >/dev/null 2>&1 && SUCCESS=1 | |
| else | |
| timeout $TIMEOUT bash -c "</dev/udp/$HOST_NO_BRACKET/$PORT" 2>/dev/null && SUCCESS=1 | |
| fi | |
| ;; | |
| esac | |
| END=$(date +%s%3N) | |
| LATENCY=$((END - START)) | |
| if [ "$SUCCESS" -eq 1 ]; then | |
| printf "%05d %s\n" "$LATENCY" "$URI" | |
| fi | |
| EOF | |
| chmod +x "$TEST_SCRIPT" | |
| # 5. 執行多線程測速 | |
| cat "$RAW_PEERS_TXT" | xargs -P "$CONCURRENCY" -I {} bash "$TEST_SCRIPT" "{}" "$TIMEOUT" | sort -n > "$RESULTS_TXT" | |
| WORKING_COUNT=$(wc -l < "$RESULTS_TXT") | |
| if [ "$WORKING_COUNT" -eq 0 ]; then | |
| log "ERROR" "所有節點測速均失敗,網絡可能中斷,停止更新!" | |
| exit 1 | |
| fi | |
| log "INFO" "測速完成,共找到 $WORKING_COUNT 個可用節點。" | |
| log "INFO" "--- TOP $MAX_PEERS 最快節點清單 ---" | |
| # 6. 【修復 Bug 的地方】使用進程替換取代管道,確保 LOG_BUFFER 生效 | |
| while read -r latency uri; do | |
| # 將延遲前面的 0 砍掉,讓顯示更美觀 (例如 00125 變成 125) | |
| clean_latency=$(echo "$latency" | sed 's/^0*//') | |
| [ -z "$clean_latency" ] && clean_latency="0" | |
| log "INFO" "延遲 ${clean_latency}ms -> $uri" | |
| done < <(head -n "$MAX_PEERS" "$RESULTS_TXT") | |
| PEERS_JSON=$(head -n "$MAX_PEERS" "$RESULTS_TXT" | awk '{print $2}' | jq -R . | jq -s .) | |
| # 7. 更新配置到臨時文件並驗證 | |
| jq --argjson p "$PEERS_JSON" '.Peers = $p' "$CURRENT_JSON" > "$TMP_FILE" 2> "$JQ_ERR_LOG" | |
| if [ $? -eq 0 ] && [ -s "$TMP_FILE" ] && jq -e . "$TMP_FILE" >/dev/null 2>&1; then | |
| cp -p "$CONFIG_FILE" "$BACKUP_FILE" | |
| if [ "$IS_HJSON" -eq 1 ]; then | |
| yggdrasil -normaliseconf -useconffile "$TMP_FILE" > "$CONFIG_FILE" | |
| else | |
| cat "$TMP_FILE" > "$CONFIG_FILE" | |
| fi | |
| log "SUCCESS" "✅ 已成功將 $MAX_PEERS 個最快節點寫入配置!" | |
| else | |
| log "ERROR" "❌ 錯誤:生成的配置文件異常!更新中斷。" | |
| exit 1 | |
| fi | |
| # 8. 重啟服務 | |
| log "INFO" "正在重啟 Yggdrasil 服務..." | |
| systemctl restart yggdrasil | |
| if [ $? -eq 0 ]; then | |
| log "SUCCESS" "🎉 Yggdrasil 服務重啟成功!" | |
| else | |
| log "WARN" "⚠️ 服務重啟遇到問題,請手動檢查。" | |
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ================= 配置區域 ================= | |
| CONFIG_FILE="/etc/yggdrasil/yggdrasil.conf" | |
| BACKUP_FILE="${CONFIG_FILE}.bak" | |
| PROXY="" | |
| TARGET_URL="https://publicpeers.neilalexander.dev/" | |
| # ⚠️ 請確保已更換為新的 Token | |
| TG_BOT_TOKEN="YOUR_NEW_TG_BOT_TOKEN" | |
| TG_CHAT_ID="YOUR_TG_CHAT_ID" | |
| MAX_PEERS=5 # 最大保留抓取節點數 | |
| TIMEOUT=2 # 每個節點的測速超時時間(秒) | |
| CONCURRENCY=20 # 並發測速線程數 | |
| # 預設的固定節點(將會始終保留在列表最前面) | |
| # 請依照格式填寫,若無則保持空陣列 DEFAULT_PEERS=() | |
| DEFAULT_PEERS=( | |
| #"tcp://1.2.3.4:12345" | |
| #"tls://5.6.7.8:443?sni=www.visa.com" | |
| ) | |
| # ============================================ | |
| # 初始化日誌緩衝區 | |
| LOG_BUFFER="" | |
| # 創建臨時工作目錄 | |
| WORK_DIR=$(mktemp -d -t yggdrasil_update_XXXXXX) | |
| if [ ! -d "$WORK_DIR" ]; then | |
| echo "無法創建臨時目錄,腳本退出。" | |
| exit 1 | |
| fi | |
| # 新增:定義日誌文件路徑 | |
| LOG_FILE="${WORK_DIR}/update_run.log" | |
| CURRENT_JSON="${WORK_DIR}/current.json" | |
| TMP_FILE="${WORK_DIR}/yggdrasil.conf.tmp" | |
| JQ_ERR_LOG="${WORK_DIR}/jq_error.log" | |
| RAW_PEERS_TXT="${WORK_DIR}/raw_peers.txt" | |
| TEST_SCRIPT="${WORK_DIR}/test_peer.sh" | |
| RESULTS_TXT="${WORK_DIR}/results.txt" | |
| # 日誌記錄函數 | |
| log() { | |
| local level=$(echo "$1" | tr 'a-z' 'A-Z') | |
| shift | |
| local msg="$*" | |
| local timestamp=$(date +'%Y-%m-%d %H:%M:%S') | |
| local color_reset='\033[0m' | |
| local color_red='\033[0;31m' | |
| local color_green='\033[0;32m' | |
| local color_yellow='\033[0;33m' | |
| local color_cyan='\033[0;36m' | |
| local color_code="" | |
| case "$level" in | |
| INFO) color_code="${color_cyan}" ;; | |
| WARN) color_code="${color_yellow}" ;; | |
| ERROR) color_code="${color_red}" ;; | |
| SUCCESS) color_code="${color_green}" ;; | |
| *) color_code="${color_reset}" ;; | |
| esac | |
| # 1. 打印到終端 (帶顏色) | |
| echo -e "${color_code}[${timestamp}] [${level}] ${msg}${color_reset}" | |
| # 2. 存入變量,準備發給 Telegram (不帶顏色) | |
| LOG_BUFFER="${LOG_BUFFER}[${timestamp}] [${level}] ${msg}"$'\n' | |
| # 3. 追加寫入到 WORK_DIR 的日誌文件中 (不帶顏色,保持純文本乾淨) | |
| echo "[${timestamp}] [${level}] ${msg}" >> "$LOG_FILE" | |
| } | |
| # 發送 Telegram 通知 | |
| send_log_to_telegram() { | |
| if [[ -z "$TG_BOT_TOKEN" || "$TG_BOT_TOKEN" == "YOUR_NEW_TG_BOT_TOKEN" || -z "$TG_CHAT_ID" ]]; then | |
| return 0 | |
| fi | |
| [ -z "$LOG_BUFFER" ] && return 0 | |
| echo -e "\033[0;36m正在發送日誌到 Telegram...\033[0m" | |
| curl --retry 3 --retry-delay 2 -sS -k "${PROXY}https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \ | |
| --data-urlencode "chat_id=${TG_CHAT_ID}" \ | |
| --data-urlencode "text=${LOG_BUFFER}" >/dev/null 2>&1 || true | |
| } | |
| # 腳本退出時的清理函數 | |
| cleanup() { | |
| send_log_to_telegram | |
| # 提示:如果你希望腳本運行完後保留這份日誌,可以取消下面這行的註釋 | |
| # 將日誌複製到系統的 /var/log 目錄下 (需 root 權限) 或者 /tmp 目錄下 | |
| # cp "$LOG_FILE" "/var/log/yggdrasil_update_last_run.log" 2>/dev/null | |
| if [ -d "$WORK_DIR" ]; then | |
| rm -rf "$WORK_DIR" | |
| fi | |
| } | |
| trap cleanup EXIT | |
| # ================= 主程序開始 ================= | |
| log "INFO" "=== $(hostname) ===" | |
| log "INFO" "=== 開始執行 Yggdrasil 節點測速更新任務 ===" | |
| # 1. 檢查依賴 | |
| for cmd in jq curl openssl awk perl xargs dig; do | |
| if ! command -v $cmd &> /dev/null; then | |
| log "ERROR" "系統未安裝 $cmd,請先安裝依賴。" | |
| exit 1 | |
| fi | |
| done | |
| # 2. 判斷配置格式並統一轉換為 JSON | |
| log "INFO" "正在檢測並備份配置文件格式..." | |
| IS_HJSON=0 | |
| if jq -e . "$CONFIG_FILE" >/dev/null 2>&1; then | |
| cp "$CONFIG_FILE" "$CURRENT_JSON" | |
| else | |
| IS_HJSON=1 | |
| yggdrasil -normaliseconf -useconffile "$CONFIG_FILE" -json > "$CURRENT_JSON" | |
| if ! jq -e . "$CURRENT_JSON" >/dev/null 2>&1; then | |
| log "ERROR" "HJSON 轉換為 JSON 失敗,請手動檢查配置!" | |
| exit 1 | |
| fi | |
| fi | |
| # 3. 抓取節點列表 | |
| log "INFO" "正在從官網抓取在線節點列表..." | |
| curl -s "${PROXY}${TARGET_URL}" | perl -0777 -ne 'while (/<tr.*?>.*?<td.*?>(.*?)<\/td>.*?<td.*?>(.*?)<\/td>.*?<\/tr>/gs) { $addr = $1; $status = $2; $addr =~ s/<[^>]+>//g; $status =~ s/<[^>]+>//g; if ($status =~ /online/i && $addr =~ /(tcp|tls|quic|ws|wss):\/\//) { print "$addr\n"; } }' > "$RAW_PEERS_TXT" | |
| TOTAL_FETCHED=$(wc -l < "$RAW_PEERS_TXT") | |
| if [ "$TOTAL_FETCHED" -eq 0 ]; then | |
| log "ERROR" "獲取節點失敗,或當前無在線節點!" | |
| exit 1 | |
| fi | |
| log "INFO" "成功獲取到 $TOTAL_FETCHED 個在線節點,開始測速過濾..." | |
| # 4. 生成並發測速腳本 (包含 DNS IP替換 與 SNI 修改) | |
| cat << 'EOF' > "$TEST_SCRIPT" | |
| #!/bin/bash | |
| ORIG_URI="$1" | |
| TIMEOUT="$2" | |
| # 1. 使用正規表示式安全解析 URI | |
| if [[ "$ORIG_URI" =~ ^([a-zA-Z]+)://(\[[a-fA-F0-9:]+\]|[^:/]+):([0-9]+)(.*)$ ]]; then | |
| PROTO="${BASH_REMATCH[1]}" | |
| HOST="${BASH_REMATCH[2]}" | |
| PORT="${BASH_REMATCH[3]}" | |
| REST="${BASH_REMATCH[4]}" | |
| else | |
| exit 1 | |
| fi | |
| # 2. DNS 查詢邏輯:替換 Host 為 IP (使用 127.0.0.1:53 TCP) | |
| if [[ "$HOST" != \[*\] ]] && ! [[ "$HOST" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| RESOLVED=$(dig +tcp @127.0.0.1 -p 53 +short A "$HOST" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) | |
| if [ -z "$RESOLVED" ]; then | |
| RESOLVED=$(dig +tcp @127.0.0.1 -p 53 +short AAAA "$HOST" | grep -E '^[0-9a-fA-F:]+$' | head -n 1) | |
| if [ -n "$RESOLVED" ]; then | |
| RESOLVED="[$RESOLVED]" | |
| fi | |
| fi | |
| if [ -n "$RESOLVED" ]; then | |
| HOST="$RESOLVED" | |
| fi | |
| fi | |
| # 3. 處理 SNI 參數 (僅針對 tls 和 quic) | |
| if [[ "$PROTO" == "tls" || "$PROTO" == "quic" ]]; then | |
| if [ -z "$REST" ]; then | |
| REST="?sni=www.visa.com" | |
| elif [[ "$REST" == *"sni="* ]]; then | |
| REST=$(echo "$REST" | sed -E 's/sni=[^&]*/sni=www.visa.com/g') | |
| else | |
| if [[ "$REST" == *\?* ]]; then | |
| REST="${REST}&sni=www.visa.com" | |
| else | |
| REST="${REST}?sni=www.visa.com" | |
| fi | |
| fi | |
| fi | |
| # 重組即將寫入配置文件的最終 URI | |
| NEW_URI="${PROTO}://${HOST}:${PORT}${REST}" | |
| # 4. 執行連通性測試 | |
| HOST_NO_BRACKET=$(echo "$HOST" | tr -d '[]') | |
| START=$(date +%s%3N) | |
| SUCCESS=0 | |
| case "$PROTO" in | |
| tcp) | |
| timeout $TIMEOUT bash -c "</dev/tcp/$HOST_NO_BRACKET/$PORT" 2>/dev/null && SUCCESS=1 | |
| ;; | |
| tls) | |
| echo -n | timeout $TIMEOUT openssl s_client -servername www.visa.com -connect "${HOST_NO_BRACKET}:${PORT}" >/dev/null 2>&1 && SUCCESS=1 | |
| ;; | |
| ws|wss) | |
| SCHEME="http" | |
| [[ "$PROTO" == "wss" ]] && SCHEME="https" | |
| PATH_QUERY="/" | |
| if [[ "$REST" == /* ]]; then | |
| PATH_QUERY="$REST" | |
| elif [[ "$REST" == \?* ]]; then | |
| PATH_QUERY="/$REST" | |
| fi | |
| if timeout $TIMEOUT curl -s -k -i -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" "${SCHEME}://${HOST_NO_BRACKET}:${PORT}${PATH_QUERY}" 2>/dev/null | grep -iq "HTTP/.* 101"; then | |
| SUCCESS=1 | |
| fi | |
| ;; | |
| quic) | |
| if openssl s_client -help 2>&1 | grep -q -- "-quic"; then | |
| echo -n | timeout $TIMEOUT openssl s_client -quic -servername www.visa.com -connect "${HOST_NO_BRACKET}:${PORT}" >/dev/null 2>&1 && SUCCESS=1 | |
| else | |
| timeout $TIMEOUT bash -c "</dev/udp/$HOST_NO_BRACKET/$PORT" 2>/dev/null && SUCCESS=1 | |
| fi | |
| ;; | |
| esac | |
| END=$(date +%s%3N) | |
| LATENCY=$((END - START)) | |
| if [ "$SUCCESS" -eq 1 ]; then | |
| printf "%05d %s\n" "$LATENCY" "$NEW_URI" | |
| fi | |
| EOF | |
| chmod +x "$TEST_SCRIPT" | |
| # 5. 執行多線程測速 | |
| cat "$RAW_PEERS_TXT" | xargs -P "$CONCURRENCY" -I {} bash "$TEST_SCRIPT" "{}" "$TIMEOUT" | sort -n > "$RESULTS_TXT" | |
| WORKING_COUNT=$(wc -l < "$RESULTS_TXT") | |
| if [ "$WORKING_COUNT" -eq 0 ]; then | |
| log "ERROR" "所有節點測速均失敗,網絡可能中斷,停止更新!" | |
| exit 1 | |
| fi | |
| log "INFO" "測速完成,共找到 $WORKING_COUNT 個可用節點。" | |
| log "INFO" "--- 預設節點 + TOP $MAX_PEERS 最快節點清單 ---" | |
| # 6. 處理預設節點並轉換為 JSON 格式 | |
| if [ ${#DEFAULT_PEERS[@]} -eq 0 ]; then | |
| DEFAULT_PEERS_JSON="[]" | |
| else | |
| for dp in "${DEFAULT_PEERS[@]}"; do | |
| log "INFO" "預設節點 -> $dp" | |
| done | |
| DEFAULT_PEERS_JSON=$(printf '%s\n' "${DEFAULT_PEERS[@]}" | jq -R . | jq -s .) | |
| fi | |
| # 輸出日誌並生成抓取節點的 JSON 陣列 | |
| while read -r latency uri; do | |
| clean_latency=$(echo "$latency" | sed 's/^0*//') | |
| [ -z "$clean_latency" ] && clean_latency="0" | |
| log "INFO" "延遲 ${clean_latency}ms -> $uri" | |
| done < <(head -n "$MAX_PEERS" "$RESULTS_TXT") | |
| PEERS_JSON=$(head -n "$MAX_PEERS" "$RESULTS_TXT" | awk '{print $2}' | jq -R . | jq -s .) | |
| # 7. 更新配置到臨時文件並驗證 | |
| jq --argjson def "$DEFAULT_PEERS_JSON" --argjson p "$PEERS_JSON" '.Peers = $def + $p' "$CURRENT_JSON" > "$TMP_FILE" 2> "$JQ_ERR_LOG" | |
| if [ $? -eq 0 ] && [ -s "$TMP_FILE" ] && jq -e . "$TMP_FILE" >/dev/null 2>&1; then | |
| cp -p "$CONFIG_FILE" "$BACKUP_FILE" | |
| if [ "$IS_HJSON" -eq 1 ]; then | |
| yggdrasil -normaliseconf -useconffile "$TMP_FILE" > "$CONFIG_FILE" | |
| else | |
| cat "$TMP_FILE" > "$CONFIG_FILE" | |
| fi | |
| log "SUCCESS" "✅ 已成功將配置寫入文件!" | |
| else | |
| log "ERROR" "❌ 錯誤:生成的配置文件異常!更新中斷。" | |
| exit 1 | |
| fi | |
| # 8. 重啟服務 | |
| log "INFO" "正在重啟 Yggdrasil 服務..." | |
| systemctl restart yggdrasil | |
| if [ $? -eq 0 ]; then | |
| log "SUCCESS" "🎉 Yggdrasil 服務重啟成功!" | |
| else | |
| log "WARN" "⚠️ 服務重啟遇到問題,請手動檢查。" | |
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ========================================== | |
| # Yggdrasil 一鍵安裝與卸載腳本 | |
| # 適用系統: Debian / Ubuntu | |
| # ========================================== | |
| # 確保以 root 權限執行 | |
| if [ "$EUID" -ne 0 ]; then | |
| echo -e "\033[0;31m[ERROR] 請使用 root 權限 (sudo) 執行此腳本。\033[0m" | |
| exit 1 | |
| fi | |
| WORK_DIR="" | |
| ACTION="" | |
| CUSTOM_PASSWORD="" | |
| BACKUP_DIR="${HOME}/.backup/yggdrasil" | |
| # 日誌記錄函數 | |
| log() { | |
| local level=$(echo "$1" | tr 'a-z' 'A-Z') | |
| shift | |
| local msg="$*" | |
| local timestamp=$(date +'%Y-%m-%d %H:%M:%S') | |
| local color_reset='\033[0m' | |
| local color_red='\033[0;31m' | |
| local color_green='\033[0;32m' | |
| local color_yellow='\033[0;33m' | |
| local color_cyan='\033[0;36m' | |
| local color_code="" | |
| case "$level" in | |
| INFO) color_code="${color_cyan}" ;; | |
| WARN) color_code="${color_yellow}" ;; | |
| ERROR) color_code="${color_red}" ;; | |
| SUCCESS) color_code="${color_green}" ;; | |
| *) color_code="${color_reset}" ;; | |
| esac | |
| echo -e "${color_code}[${timestamp}] [${level}] ${msg}${color_reset}" | |
| } | |
| # 退出時的清理函數 | |
| cleanup() { | |
| if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then | |
| rm -rf "$WORK_DIR" | |
| log "INFO" "系統臨時目錄 $WORK_DIR 已清理。" | |
| fi | |
| } | |
| # 綁定 EXIT 信號 | |
| trap cleanup EXIT | |
| # 獲取系統架構 | |
| get_arch() { | |
| local arch=$(uname -m) | |
| case "$arch" in | |
| x86_64) echo "amd64" ;; | |
| aarch64) echo "arm64" ;; | |
| armv7l) echo "armhf" ;; | |
| i386) echo "i386" ;; | |
| *) | |
| log "ERROR" "不支持的系統架構: $arch" | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| # ================= 獨立函數:修改配置與還原密鑰 ================= | |
| modify_yggdrasil_config() { | |
| local pwd="$1" | |
| local config_file="/etc/yggdrasil.conf" | |
| local temp_json="${WORK_DIR}/temp.json" | |
| local modified_json="${WORK_DIR}/modified.json" | |
| local is_hjson=0 | |
| log "INFO" "開始修改配置 (AdminListen, IfName, Listen, 密鑰還原)..." | |
| if [ ! -f "$config_file" ]; then | |
| log "ERROR" "找不到配置文件 $config_file" | |
| return 1 | |
| fi | |
| # 1. 判斷配置格式是否為 JSON,不是則視為 HJSON 並轉換為 JSON | |
| if jq -e . "$config_file" >/dev/null 2>&1; then | |
| cp "$config_file" "$temp_json" | |
| else | |
| is_hjson=1 | |
| yggdrasil -normaliseconf -useconffile "$config_file" -json > "$temp_json" | |
| fi | |
| # 2. 準備基礎的 jq 修改指令 | |
| local jq_cmd='.AdminListen = "unix:///var/run/yggdrasil/yggdrasil.sock" | .IfName = "ygg0" | .Listen = ["quic://[::]:8000?password=" + $pw]' | |
| # 3. 尋找並加載最新備份的密鑰 | |
| local priv_key="" | |
| local pub_key="" | |
| local latest_backup=$(ls -t "${BACKUP_DIR}"/keys_*.json 2>/dev/null | head -n 1) | |
| if [ -n "$latest_backup" ] && [ -f "$latest_backup" ]; then | |
| log "INFO" "發現歷史備份密鑰,正在從 ${latest_backup} 讀取..." | |
| priv_key=$(jq -r '.PrivateKey // empty' "$latest_backup") | |
| pub_key=$(jq -r '.PublicKey // empty' "$latest_backup") | |
| fi | |
| # 4. 根據是否找到密鑰,執行對應的 jq 覆寫邏輯 | |
| if [ -n "$priv_key" ] && [ -n "$pub_key" ]; then | |
| log "SUCCESS" "✅ 已加載歷史密鑰,即將替換掉全新生成的密鑰,保持舊 IP 節點不變!" | |
| jq_cmd="${jq_cmd} | .PrivateKey = \$priv | .PublicKey = \$pub" | |
| jq --arg pw "$pwd" --arg priv "$priv_key" --arg pub "$pub_key" "$jq_cmd" "$temp_json" > "$modified_json" | |
| else | |
| log "INFO" "未找到有效的備份密鑰,將使用全新生成的節點密鑰。" | |
| jq --arg pw "$pwd" "$jq_cmd" "$temp_json" > "$modified_json" | |
| fi | |
| # 5. 根據原始格式寫回配置文件 | |
| if [ "$is_hjson" -eq 1 ]; then | |
| log "INFO" "檢測到原始配置為 HJSON,轉換回 HJSON 格式並保存..." | |
| yggdrasil -normaliseconf -useconffile "$modified_json" > "$config_file" | |
| else | |
| log "INFO" "檢測到原始配置為 JSON,直接保存..." | |
| cat "$modified_json" > "$config_file" | |
| fi | |
| } | |
| # ================= 核心功能:安裝 ================= | |
| install_yggdrasil() { | |
| log "INFO" "=== 開始安裝最新版 Yggdrasil ===" | |
| # 檢查必要依賴 | |
| if ! command -v curl &> /dev/null || ! command -v jq &> /dev/null; then | |
| log "INFO" "正在安裝必要的依賴 (curl, jq)..." | |
| apt-get update -y > /dev/null | |
| apt-get install -y curl jq > /dev/null | |
| fi | |
| # 獲取最新版本號 (從 GitHub API) | |
| log "INFO" "正在查詢 Yggdrasil 最新版本..." | |
| local api_url="https://api.github.com/repos/yggdrasil-network/yggdrasil-go/releases/latest" | |
| local latest_tag=$(curl -s "$api_url" | jq -r .tag_name) | |
| if [ -z "$latest_tag" ] || [ "$latest_tag" == "null" ]; then | |
| log "ERROR" "獲取最新版本失敗,請檢查網路連線。" | |
| exit 1 | |
| fi | |
| local version_num=${latest_tag#v} | |
| local arch=$(get_arch) | |
| log "SUCCESS" "發現最新版本: $latest_tag,系統架構: $arch" | |
| # 檢查是否已安裝相同版本 | |
| if command -v yggdrasil &> /dev/null; then | |
| local current_version=$(yggdrasil -version | awk '{print $3}') | |
| if [ "$current_version" == "$latest_tag" ] || [ "$current_version" == "$version_num" ]; then | |
| log "SUCCESS" "當前已安裝最新版本 ($current_version),無需更新二進位文件。" | |
| else | |
| log "WARN" "發現舊版本 ($current_version),即將進行升級..." | |
| WORK_DIR=$(mktemp -d -t yggdrasil_install_XXXXXX) | |
| local deb_file="yggdrasil-${version_num}-${arch}.deb" | |
| local download_url="https://github.com/yggdrasil-network/yggdrasil-go/releases/download/${latest_tag}/${deb_file}" | |
| log "INFO" "正在下載: $download_url" | |
| curl -L -o "${WORK_DIR}/${deb_file}" "$download_url" | |
| if [ ! -s "${WORK_DIR}/${deb_file}" ]; then | |
| log "ERROR" "下載失敗或文件為空!" | |
| exit 1 | |
| fi | |
| log "INFO" "正在安裝 Yggdrasil..." | |
| dpkg -i "${WORK_DIR}/${deb_file}" | |
| apt-get install -f -y > /dev/null | |
| fi | |
| else | |
| WORK_DIR=$(mktemp -d -t yggdrasil_install_XXXXXX) | |
| local deb_file="yggdrasil-${version_num}-${arch}.deb" | |
| local download_url="https://github.com/yggdrasil-network/yggdrasil-go/releases/download/${latest_tag}/${deb_file}" | |
| log "INFO" "正在下載: $download_url" | |
| curl -L -o "${WORK_DIR}/${deb_file}" "$download_url" | |
| if [ ! -s "${WORK_DIR}/${deb_file}" ]; then | |
| log "ERROR" "下載失敗或文件為空!" | |
| exit 1 | |
| fi | |
| log "INFO" "正在安裝 Yggdrasil..." | |
| dpkg -i "${WORK_DIR}/${deb_file}" | |
| apt-get install -f -y > /dev/null | |
| fi | |
| # 確保系統建立了臨時工作目錄 | |
| if [ -z "$WORK_DIR" ]; then | |
| WORK_DIR=$(mktemp -d -t yggdrasil_install_XXXXXX) | |
| fi | |
| systemctl enable yggdrasil | |
| systemctl restart yggdrasil | |
| if systemctl is-active --quiet yggdrasil; then | |
| log "SUCCESS" "✅ Yggdrasil 服務已啟動並設置為開機自啟!" | |
| # 生成默認配置(如果不存在) | |
| local config_file="/etc/yggdrasil.conf" | |
| if [ ! -f "$config_file" ]; then | |
| log "INFO" "生成初始配置文件到 $config_file..." | |
| yggdrasil -genconf > "$config_file" | |
| fi | |
| # 處理密碼 | |
| local peer_password="$CUSTOM_PASSWORD" | |
| if [ -z "$peer_password" ]; then | |
| log "INFO" "未通過 --password 指定密碼,正在自動生成 32 位隨機密碼..." | |
| peer_password=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) | |
| fi | |
| log "SUCCESS" "QUIC 監聽密碼設定為: $peer_password" | |
| # 調用獨立配置函數 (裡面會進行修改和金鑰還原) | |
| modify_yggdrasil_config "$peer_password" | |
| # 重啟服務應用修改 | |
| log "INFO" "應用配置變更,重啟 Yggdrasil 服務..." | |
| systemctl restart yggdrasil | |
| if systemctl is-active --quiet yggdrasil; then | |
| log "SUCCESS" "✅ 配置修改成功且服務運行正常!" | |
| else | |
| log "ERROR" "❌ 服務重啟失敗,請檢查系統日誌。" | |
| fi | |
| else | |
| log "ERROR" "❌ 安裝完成,但服務啟動失敗,請使用 systemctl status yggdrasil 檢查。" | |
| fi | |
| } | |
| # ================= 核心功能:卸載 ================= | |
| uninstall_yggdrasil() { | |
| log "INFO" "=== 開始卸載 Yggdrasil ===" | |
| if ! command -v yggdrasil &> /dev/null; then | |
| log "WARN" "系統中未檢測到 Yggdrasil,無需卸載。" | |
| exit 0 | |
| fi | |
| # =============== 備份密鑰邏輯 =============== | |
| local config_file="/etc/yggdrasil.conf" | |
| if [ -f "$config_file" ]; then | |
| log "INFO" "正在備份現有的 Yggdrasil 金鑰對 (以保留 IPv6 位址)..." | |
| mkdir -p "$BACKUP_DIR" | |
| WORK_DIR=$(mktemp -d -t yggdrasil_uninstall_XXXXXX) | |
| local temp_json="${WORK_DIR}/backup_temp.json" | |
| if jq -e . "$config_file" >/dev/null 2>&1; then | |
| cp "$config_file" "$temp_json" | |
| else | |
| yggdrasil -normaliseconf -useconffile "$config_file" -json > "$temp_json" 2>/dev/null | |
| fi | |
| if [ -f "$temp_json" ]; then | |
| local timestamp=$(date +'%Y%m%d_%H%M%S') | |
| local backup_file="${BACKUP_DIR}/keys_${timestamp}.json" | |
| # 提取並備份 PublicKey 與 PrivateKey | |
| jq '{PrivateKey: .PrivateKey, PublicKey: .PublicKey}' "$temp_json" > "$backup_file" | |
| if [ -s "$backup_file" ] && [ "$(jq -r '.PrivateKey // empty' "$backup_file")" != "" ]; then | |
| log "SUCCESS" "✅ 金鑰已成功備份至: $backup_file" | |
| else | |
| log "WARN" "⚠️ 金鑰備份失敗或配置中無金鑰,已忽略。" | |
| rm -f "$backup_file" | |
| fi | |
| fi | |
| fi | |
| # ========================================== | |
| # 停止並禁用服務 | |
| log "INFO" "正在停止並禁用 yggdrasil 服務..." | |
| systemctl stop yggdrasil || true | |
| systemctl disable yggdrasil || true | |
| # 卸載軟體包 | |
| log "INFO" "正在移除軟體包..." | |
| apt-get remove --purge -y yggdrasil | |
| # 清理可能殘留的自定義目錄 | |
| log "INFO" "正在清理配置文件與殘留目錄..." | |
| rm -f /etc/yggdrasil.conf | |
| rm -f /etc/yggdrasil.conf.bak | |
| rm -rf /usr/local/etc/yggdrasil/ | |
| # 重新加載 systemd | |
| systemctl daemon-reload | |
| log "SUCCESS" "✅ Yggdrasil 已從系統中完全卸載。" | |
| } | |
| # ================= 參數解析 ================= | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| install) | |
| ACTION="install" | |
| shift | |
| ;; | |
| uninstall) | |
| ACTION="uninstall" | |
| shift | |
| ;; | |
| --password) | |
| CUSTOM_PASSWORD="$2" | |
| shift 2 | |
| ;; | |
| *) | |
| echo -e "\033[0;31m[ERROR] 未知參數: $1\033[0m" | |
| echo -e "使用方法: $0 {install|uninstall} [--password <密碼>]" | |
| echo -e " \033[0;32minstall\033[0m - 自動檢測最新版本並安裝 (若已安裝則升級配置)" | |
| echo -e " \033[0;32m --password XXX\033[0m - (可選) 設置 QUIC 監聽密碼。未指定則隨機生成32位密碼" | |
| echo -e " \033[0;31muninstall\033[0m - 完全卸載並備份金鑰,清理服務與配置" | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| # 根據解析的動作執行 | |
| if [ "$ACTION" == "install" ]; then | |
| install_yggdrasil | |
| elif [ "$ACTION" == "uninstall" ]; then | |
| uninstall_yggdrasil | |
| else | |
| echo -e "\033[0;31m[ERROR] 請指定動作 (install 或 uninstall)。\033[0m" | |
| echo -e "示例: $0 install --password mysecret123" | |
| exit 1 | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment