feat: add Windows PowerShell port (clash.ps1 + install.ps1)
- clash.ps1: 1:1 port of macOS clash script to PowerShell 7
- TUN-only mode, auto UAC elevation via Start-Process -Verb RunAs
- Subcommands: start/stop/restart/reload/status/link/open/edit/
log/cleanup/doctor/verge-off/help + interactive menu
- win_adapt_config: strip Linux-only fields, stack system→gvisor
- wintun adapter + route cleanup for network recovery
- ANSI colors (pwsh 7 + Windows Terminal)
- 5-path mihomo binary detection (Get-Command→scoop→~/Mihomo→LOCALAPPDATA→ProgramFiles)
- install.ps1: one-command Windows deploy
- Detect pwsh 7, download mihomo v1.19.15 + wintun.dll 0.14.1
- Architecture-aware (amd64/arm64)
- Deploy clash.ps1 to PATH
- README.md: add Windows section
- .gitignore: add wintun.dll, *.exe
This commit is contained in:
@@ -4,11 +4,16 @@ config.yaml
|
|||||||
# 运行时数据
|
# 运行时数据
|
||||||
*.pid
|
*.pid
|
||||||
*.log
|
*.log
|
||||||
|
*.err.log
|
||||||
cache.db
|
cache.db
|
||||||
|
|
||||||
# 代理节点缓存(含订阅链接和节点信息)
|
# 代理节点缓存(含订阅链接和节点信息)
|
||||||
proxy_provider/
|
proxy_provider/
|
||||||
rules/
|
rules/
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
wintun.dll
|
||||||
|
*.exe
|
||||||
|
|
||||||
# macOS 系统文件
|
# macOS 系统文件
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
# Clash — macOS 代理管理脚本
|
# Clash — Mihomo 内核管理脚本
|
||||||
|
|
||||||
一份 YAML 配置同时在 OpenWRT 和 macOS 上使用,启动时自动适配 macOS。
|
一份 YAML 配置同时在 OpenWRT / macOS / Windows 上使用,启动时自动适配平台。
|
||||||
|
|
||||||
## 使用
|
## macOS
|
||||||
|
|
||||||
|
### 使用
|
||||||
|
|
||||||
| 命令 | 说明 |
|
| 命令 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
@@ -15,17 +17,7 @@
|
|||||||
| `clash link` | 源文件路径 |
|
| `clash link` | 源文件路径 |
|
||||||
| `clash cleanup` | 网络修复 |
|
| `clash cleanup` | 网络修复 |
|
||||||
|
|
||||||
## 目录
|
### 部署
|
||||||
|
|
||||||
```
|
|
||||||
Clash/
|
|
||||||
├── clash # 管理脚本
|
|
||||||
└── install.sh # 部署脚本
|
|
||||||
|
|
||||||
~/Mihomo/ ← 放 YAML 配置文件
|
|
||||||
```
|
|
||||||
|
|
||||||
## 新电脑部署
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:lukeopen/Clash.git
|
git clone git@github.com:lukeopen/Clash.git
|
||||||
@@ -34,6 +26,49 @@ cp /path/to/config.yaml ~/Mihomo/
|
|||||||
clash start
|
clash start
|
||||||
```
|
```
|
||||||
|
|
||||||
## 依赖
|
### 依赖
|
||||||
|
|
||||||
- Homebrew + `brew install mihomo`
|
- Homebrew + `brew install mihomo`
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
### 使用
|
||||||
|
|
||||||
|
| 命令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `clash.ps1` | 交互菜单 |
|
||||||
|
| `clash.ps1 start` | 启动 (TUN 模式) |
|
||||||
|
| `clash.ps1 stop` | 停止 |
|
||||||
|
| `clash.ps1 restart` | 重启 |
|
||||||
|
| `clash.ps1 reload` | 热重载 |
|
||||||
|
| `clash.ps1 status` | 状态 |
|
||||||
|
| `clash.ps1 link` | 源文件路径 |
|
||||||
|
| `clash.ps1 cleanup` | 网络修复 |
|
||||||
|
|
||||||
|
### 部署
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone https://github.com/lukeopen/Clash.git
|
||||||
|
cd Clash
|
||||||
|
pwsh install.ps1
|
||||||
|
# 把 .yaml 配置文件放入 ~/Mihomo/
|
||||||
|
clash.ps1 start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 要求
|
||||||
|
|
||||||
|
- Windows 11 + PowerShell 7 (`winget install Microsoft.PowerShell`)
|
||||||
|
- TUN 模式需要管理员权限(UAC 关闭时自动提权无感)
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
Clash/
|
||||||
|
├── clash # macOS 管理脚本
|
||||||
|
├── install.sh # macOS 部署脚本
|
||||||
|
├── clash.ps1 # Windows 管理脚本
|
||||||
|
└── install.ps1 # Windows 部署脚本
|
||||||
|
|
||||||
|
~/Mihomo/ ← 放 YAML 配置文件
|
||||||
|
~/.config/mihomo/ ← 运行时数据(config.yaml / pid / log / ui)
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,756 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# Mihomo Kernel Manager — Windows
|
||||||
|
# clash.ps1 <command> | clash.ps1 (交互菜单) | clash.ps1 help
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# ─── 路径变量 ───
|
||||||
|
$ConfigDir = "$env:USERPROFILE\.config\mihomo"
|
||||||
|
$ConfigFile = "$ConfigDir\config.yaml"
|
||||||
|
$PidFile = "$ConfigDir\mihomo.pid"
|
||||||
|
$LogFile = "$ConfigDir\mihomo.log"
|
||||||
|
$ErrLogFile = "$ConfigDir\mihomo.err.log"
|
||||||
|
$LinkFile = "$ConfigDir\source.txt"
|
||||||
|
$SourceDir = "$env:USERPROFILE\Mihomo"
|
||||||
|
$MixedPort = 7890
|
||||||
|
$ApiPort = 9090
|
||||||
|
|
||||||
|
if (-not (Test-Path $ConfigDir)) { New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null }
|
||||||
|
|
||||||
|
# ─── 颜色 ───
|
||||||
|
$e = [char]27
|
||||||
|
$Bold = "$e[1m"; $Dim = "$e[2m"
|
||||||
|
$Green = "$e[0;32m"; $BoldGreen = "$e[1;32m"
|
||||||
|
$Red = "$e[0;31m"; $BoldRed = "$e[1;31m"
|
||||||
|
$Yellow = "$e[0;33m"; $BoldYellow = "$e[1;33m"
|
||||||
|
$Blue = "$e[0;34m"; $BoldBlue = "$e[1;34m"
|
||||||
|
$Purple = "$e[0;35m"; $BoldPurple = "$e[1;35m"
|
||||||
|
$Cyan = "$e[0;36m"; $BoldCyan = "$e[1;36m"
|
||||||
|
$NC = "$e[0m"
|
||||||
|
$SEP = "$Dim$('─' * 42)$NC"
|
||||||
|
|
||||||
|
# ─── 输出辅助 ───
|
||||||
|
function W-Info { Write-Host " ${Cyan}ℹ${NC} $args" }
|
||||||
|
function W-Ok { Write-Host " ${Green}✓${NC} $args" }
|
||||||
|
function W-Warn { Write-Host " ${Yellow}⚠${NC} $args" }
|
||||||
|
function W-Err { Write-Host " ${Red}✗${NC} $args" }
|
||||||
|
function W-Dim { Write-Host " ${Dim}$args${NC}" }
|
||||||
|
function W-Section { Write-Host ""; Write-Host " ${Bold}── $args ──${NC}" }
|
||||||
|
|
||||||
|
# ─── mihomo 二进制查找 ───
|
||||||
|
function Get-MihomoBin {
|
||||||
|
$cmd = Get-Command mihomo -ErrorAction SilentlyContinue
|
||||||
|
if ($cmd) { return $cmd.Source }
|
||||||
|
$paths = @(
|
||||||
|
"$env:USERPROFILE\scoop\apps\mihomo\current\mihomo.exe",
|
||||||
|
"$env:USERPROFILE\Mihomo\mihomo.exe",
|
||||||
|
"$env:LOCALAPPDATA\Programs\mihomo\mihomo.exe",
|
||||||
|
"$env:ProgramFiles\mihomo\mihomo.exe"
|
||||||
|
)
|
||||||
|
foreach ($p in $paths) { if (Test-Path $p) { return $p } }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$Script:MihomoBin = Get-MihomoBin
|
||||||
|
|
||||||
|
# ─── wintun.dll 检测 ───
|
||||||
|
function Test-Wintun {
|
||||||
|
if (-not $Script:MihomoBin) { return $false }
|
||||||
|
$dir = Split-Path $Script:MihomoBin -Parent
|
||||||
|
return (Test-Path (Join-Path $dir "wintun.dll"))
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 管理员检测 ───
|
||||||
|
function Test-Admin {
|
||||||
|
$current = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
|
(New-Object Security.Principal.WindowsPrincipal($current)).IsInRole(
|
||||||
|
[Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 运行状态 ───
|
||||||
|
function Test-Running {
|
||||||
|
if (-not (Test-Path $PidFile)) { return $false }
|
||||||
|
$procId = [System.IO.File]::ReadAllText($PidFile).Trim()
|
||||||
|
if (-not $procId) { return $false }
|
||||||
|
$proc = Get-Process -Id $procId -ErrorAction SilentlyContinue
|
||||||
|
return ($null -ne $proc)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 源文件自动检测 ───
|
||||||
|
function Get-AutoDetectSource {
|
||||||
|
if (Test-Path $LinkFile) {
|
||||||
|
$linked = [System.IO.File]::ReadAllText($LinkFile).Trim()
|
||||||
|
if (Test-Path $linked) { return $linked }
|
||||||
|
W-Warn "链接文件不存在: $linked,尝试自动检测..."
|
||||||
|
}
|
||||||
|
if (Test-Path $SourceDir) {
|
||||||
|
$latest = Get-ChildItem -Path $SourceDir -File |
|
||||||
|
Where-Object { $_.Extension -in '.yaml', '.yml' } |
|
||||||
|
Sort-Object LastWriteTime -Descending |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($latest) { return $latest.FullName }
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── Windows 配置适配(不改源文件)───
|
||||||
|
function Invoke-WinAdaptConfig {
|
||||||
|
param([string]$Src)
|
||||||
|
|
||||||
|
if (-not $Src) { $Src = Get-AutoDetectSource }
|
||||||
|
if (-not $Src -or -not (Test-Path $Src)) {
|
||||||
|
W-Err "未找到 YAML 配置文件"
|
||||||
|
W-Dim "请把 .yaml 放到 $SourceDir,或用: clash.ps1 link C:\path\to\file.yaml"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host -NoNewline " 适配 Windows 配置 ... "
|
||||||
|
Copy-Item $Src $ConfigFile -Force
|
||||||
|
$content = [System.IO.File]::ReadAllText($ConfigFile)
|
||||||
|
|
||||||
|
# Windows 兼容修复(与 macOS 版逻辑一致)
|
||||||
|
$content = $content -replace '(?m)^\s*device:\s*nikki\s*\r?\n', ''
|
||||||
|
$content = $content -replace '(?m)^\s*auto-redirect:\s*true\s*\r?\n', ''
|
||||||
|
$content = $content -replace '(?m)^(\s*stack:)\s*system', '$1 gvisor'
|
||||||
|
$content = $content -replace ' - tcp://', ' - '
|
||||||
|
$content = $content -replace ' - udp://', ' - '
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText($ConfigFile, $content, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
|
||||||
|
Write-Host "${Green}✓${NC}"
|
||||||
|
W-Dim "源文件 $Src"
|
||||||
|
W-Dim "适配后 $ConfigFile"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 管理面板 ───
|
||||||
|
function Get-UiUrl {
|
||||||
|
$uiDir = "$ConfigDir\ui"
|
||||||
|
if (Test-Path $uiDir) {
|
||||||
|
$dash = Get-ChildItem $uiDir -Directory | Sort-Object Name | Select-Object -First 1
|
||||||
|
if ($dash) { return "http://127.0.0.1:$ApiPort/ui/$($dash.Name)/" }
|
||||||
|
}
|
||||||
|
return "http://127.0.0.1:$ApiPort/ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Update-UiIndex {
|
||||||
|
$uiDir = "$ConfigDir\ui"
|
||||||
|
if (-not (Test-Path $uiDir)) { return }
|
||||||
|
$dash = Get-ChildItem $uiDir -Directory | Sort-Object Name | Select-Object -First 1
|
||||||
|
if (-not $dash) { return }
|
||||||
|
$indexFile = "$uiDir\index.html"
|
||||||
|
if (-not (Test-Path $indexFile)) {
|
||||||
|
$html = @"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="refresh" content="0; url=./$($dash.Name)/">
|
||||||
|
<title>Mihomo Dashboard</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>正在打开面板 <a href="./$($dash.Name)/">$($dash.Name)</a> ...</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"@
|
||||||
|
[System.IO.File]::WriteAllText($indexFile, $html, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
W-Info "已生成面板首页跳转: $indexFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 残留网络状态检测 ───
|
||||||
|
function Test-NetworkLeftovers {
|
||||||
|
# 1. 残留 mihomo 进程
|
||||||
|
if (Get-Process -Name mihomo -ErrorAction SilentlyContinue) { return $true }
|
||||||
|
|
||||||
|
# 2. 残留 wintun 网卡
|
||||||
|
$adapters = Get-NetAdapter -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.InterfaceDescription -like "*Wintun*" -or $_.InterfaceDescription -like "*Mihomo*" }
|
||||||
|
if ($adapters) { return $true }
|
||||||
|
|
||||||
|
# 3. 残留路由(指向 198.18.0.1)
|
||||||
|
$routes = Get-NetRoute -ErrorAction SilentlyContinue | Where-Object { $_.NextHop -eq "198.18.0.1" }
|
||||||
|
if ($routes) { return $true }
|
||||||
|
|
||||||
|
# 4. DNS 劫持(指向 127.0.0.1 或 198.18.x)
|
||||||
|
$dns = Get-DnsClientServerAddress -ErrorAction SilentlyContinue | Where-Object {
|
||||||
|
$_.ServerAddresses -contains "127.0.0.1" -or
|
||||||
|
($_.ServerAddresses | Where-Object { $_ -match "^198\.18\." })
|
||||||
|
}
|
||||||
|
if ($dns) { return $true }
|
||||||
|
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 网络恢复(TUN/路由/DNS 全面清理)───
|
||||||
|
function Invoke-NetworkRestore {
|
||||||
|
param([bool]$ShowProgress = $true)
|
||||||
|
|
||||||
|
if ($ShowProgress) { W-Section "网络恢复" }
|
||||||
|
|
||||||
|
# 1. 清理残留 mihomo 进程
|
||||||
|
$procs = Get-Process -Name mihomo -ErrorAction SilentlyContinue
|
||||||
|
if ($procs) {
|
||||||
|
if ($ShowProgress) { W-Warn "发现残留进程,正在清理..." }
|
||||||
|
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. 清理残留 wintun 网卡
|
||||||
|
$adapters = Get-NetAdapter -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.InterfaceDescription -like "*Wintun*" -or $_.InterfaceDescription -like "*Mihomo*" }
|
||||||
|
foreach ($adapter in $adapters) {
|
||||||
|
if ($ShowProgress) { Write-Host -NoNewline " 清理残留 TUN $($adapter.Name) ... " }
|
||||||
|
netsh interface set interface "$($adapter.Name)" admin=disable 2>$null | Out-Null
|
||||||
|
if ($ShowProgress) { Write-Host "${Green}✓${NC}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. 清理残留路由
|
||||||
|
$mihomoRoutes = Get-NetRoute -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.NextHop -eq "198.18.0.1" }
|
||||||
|
foreach ($route in $mihomoRoutes) {
|
||||||
|
Remove-NetRoute -DestinationPrefix $route.DestinationPrefix -NextHop "198.18.0.1" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4. 刷新 DNS 缓存
|
||||||
|
if ($ShowProgress) { Write-Host -NoNewline " 刷新 DNS 缓存 ... " }
|
||||||
|
ipconfig /flushdns 2>$null | Out-Null
|
||||||
|
if ($ShowProgress) { Write-Host "${Green}✓${NC}" }
|
||||||
|
|
||||||
|
# 5. 还原系统 DNS(如果被 mihomo 指到 127.0.0.1 / 198.18.x)
|
||||||
|
$dnsHijacked = Get-DnsClientServerAddress -ErrorAction SilentlyContinue | Where-Object {
|
||||||
|
$_.ServerAddresses -contains "127.0.0.1" -or
|
||||||
|
($_.ServerAddresses | Where-Object { $_ -match "^198\.18\." })
|
||||||
|
}
|
||||||
|
if ($dnsHijacked) {
|
||||||
|
if ($ShowProgress) { Write-Host -NoNewline " 还原系统 DNS ... " }
|
||||||
|
foreach ($adapter in $dnsHijacked) {
|
||||||
|
Set-DnsClientServerAddress -InterfaceIndex $adapter.InterfaceIndex -ResetServerAddresses -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
ipconfig /flushdns 2>$null | Out-Null
|
||||||
|
if ($ShowProgress) { Write-Host "${Green}✓${NC}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ShowProgress) {
|
||||||
|
Write-Host ""
|
||||||
|
W-Ok "网络已恢复"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 网络诊断 ───
|
||||||
|
function Invoke-NetworkHealthCheck {
|
||||||
|
$issues = $false
|
||||||
|
W-Section "网络诊断"
|
||||||
|
|
||||||
|
# DNS 解析
|
||||||
|
try {
|
||||||
|
$null = Resolve-DnsName baidu.com -ErrorAction Stop
|
||||||
|
W-Ok "DNS 解析正常 (baidu.com)"
|
||||||
|
} catch {
|
||||||
|
W-Err "DNS 解析失败"
|
||||||
|
$issues = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# 外网连通
|
||||||
|
try {
|
||||||
|
$ping = New-Object System.Net.NetworkInformation.Ping
|
||||||
|
$reply = $ping.Send("8.8.8.8", 2000)
|
||||||
|
if ($reply.Status -eq 'Success') {
|
||||||
|
W-Ok "外网连通 (8.8.8.8)"
|
||||||
|
} else {
|
||||||
|
W-Err "外网不可达"
|
||||||
|
$issues = $true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
W-Err "外网不可达"
|
||||||
|
$issues = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# TUN 接口检查
|
||||||
|
$wintunAdapters = Get-NetAdapter -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.InterfaceDescription -like "*Wintun*" -or $_.InterfaceDescription -like "*Mihomo*" }
|
||||||
|
if (-not $wintunAdapters) {
|
||||||
|
W-Ok "无残留 TUN 接口"
|
||||||
|
} else {
|
||||||
|
W-Warn "发现 $($wintunAdapters.Count) 个残留 TUN 接口"
|
||||||
|
$issues = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($issues) {
|
||||||
|
W-Warn "网络可能有异常,试试: clash.ps1 cleanup"
|
||||||
|
} else {
|
||||||
|
W-Ok "网络一切正常"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 启动 ───
|
||||||
|
function Invoke-CmdStart {
|
||||||
|
param([string]$Src)
|
||||||
|
|
||||||
|
$doAdapt = $true
|
||||||
|
if ($Src) {
|
||||||
|
$linked = if (Test-Path $LinkFile) { [System.IO.File]::ReadAllText($LinkFile).Trim() } else { "" }
|
||||||
|
if ($Src -eq $ConfigFile -or $Src -eq $linked) { $doAdapt = $false }
|
||||||
|
}
|
||||||
|
if (-not $Src) { $Src = Get-AutoDetectSource }
|
||||||
|
|
||||||
|
if ($doAdapt -and $Src -ne $ConfigFile) {
|
||||||
|
$result = Invoke-WinAdaptConfig $Src
|
||||||
|
if (-not $result) { return }
|
||||||
|
} elseif (-not (Test-Path $ConfigFile)) {
|
||||||
|
W-Err "配置文件不存在: $ConfigFile"
|
||||||
|
W-Dim "试试: clash.ps1 link C:\path\to\your.yaml"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Running) {
|
||||||
|
W-Warn "mihomo 已经在运行了"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查 mihomo 二进制
|
||||||
|
if (-not $Script:MihomoBin) {
|
||||||
|
W-Err "未找到 mihomo.exe"
|
||||||
|
W-Dim "请先运行 install.ps1 安装,或手动下载 mihomo.exe"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查 wintun.dll
|
||||||
|
if (-not (Test-Wintun)) {
|
||||||
|
W-Warn "未找到 wintun.dll(TUN 模式需要)"
|
||||||
|
W-Dim "请将 wintun.dll 放在 mihomo.exe 同目录,或运行 install.ps1"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# 启动前清理残留
|
||||||
|
if (Test-NetworkLeftovers) {
|
||||||
|
Write-Host ""
|
||||||
|
W-Info "检测到上次残留网络状态,先清理 ..."
|
||||||
|
Invoke-NetworkRestore $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host -NoNewline " 启动中 ... "
|
||||||
|
|
||||||
|
# 清理旧日志
|
||||||
|
Remove-Item $LogFile -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $ErrLogFile -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# 启动 mihomo(隐藏窗口,重定向输出)
|
||||||
|
$argStr = "-d `"$ConfigDir`" -f `"$ConfigFile`""
|
||||||
|
$proc = Start-Process -FilePath $Script:MihomoBin `
|
||||||
|
-ArgumentList $argStr `
|
||||||
|
-WindowStyle Hidden `
|
||||||
|
-RedirectStandardOutput $LogFile `
|
||||||
|
-RedirectStandardError $ErrLogFile `
|
||||||
|
-PassThru
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText($PidFile, $proc.Id.ToString())
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
|
||||||
|
if (Test-Running) {
|
||||||
|
Write-Host "${Green}✓${NC} 已就绪"
|
||||||
|
Update-UiIndex
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Dim}代理端口 ${NC}${Cyan}127.0.0.1:$MixedPort${NC} ${Dim}(HTTP/SOCKS5)${NC}"
|
||||||
|
Write-Host " ${Dim}API 端口 ${NC}${Cyan}127.0.0.1:$ApiPort${NC}"
|
||||||
|
Write-Host " ${Dim}管理面板 ${NC}${Cyan}$(Get-UiUrl)${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
} else {
|
||||||
|
W-Err "启动失败"
|
||||||
|
W-Dim "查看日志: Get-Content $LogFile"
|
||||||
|
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 停止 ───
|
||||||
|
function Invoke-CmdStop {
|
||||||
|
if (-not (Test-Running)) {
|
||||||
|
W-Warn "mihomo 没有在运行"
|
||||||
|
if (Test-NetworkLeftovers) {
|
||||||
|
Write-Host ""
|
||||||
|
W-Info "检测到残留网络状态,正在清理 ..."
|
||||||
|
Invoke-NetworkRestore $true
|
||||||
|
Write-Host ""
|
||||||
|
Invoke-NetworkHealthCheck
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$procId = [int][System.IO.File]::ReadAllText($PidFile).Trim()
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host -NoNewline " 停止中 ... "
|
||||||
|
|
||||||
|
Stop-Process -Id $procId -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
for ($i = 0; $i -lt 10; $i++) {
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
$proc = Get-Process -Id $procId -ErrorAction SilentlyContinue
|
||||||
|
if (-not $proc) {
|
||||||
|
Write-Host "${Green}✓${NC} 已停止 (PID: $procId)"
|
||||||
|
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Host ""
|
||||||
|
Invoke-NetworkRestore $false
|
||||||
|
Invoke-NetworkHealthCheck
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
W-Warn "超时,强制终止..."
|
||||||
|
Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if (Get-Process -Name mihomo -ErrorAction SilentlyContinue) {
|
||||||
|
W-Err "停止失败,残留进程仍在运行"
|
||||||
|
W-Dim "试试: Stop-Process -Name mihomo -Force"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
W-Warn "已强制停止"
|
||||||
|
W-Info "正在恢复网络 ..."
|
||||||
|
Invoke-NetworkRestore $true
|
||||||
|
Write-Host ""
|
||||||
|
Invoke-NetworkHealthCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 重启 / 热重载 ───
|
||||||
|
function Invoke-CmdRestart {
|
||||||
|
param([string]$Src)
|
||||||
|
Invoke-CmdStop
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
Invoke-CmdStart $Src
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CmdReload {
|
||||||
|
param([string]$Cfg = $ConfigFile)
|
||||||
|
|
||||||
|
if (-not (Test-Running)) {
|
||||||
|
W-Warn "mihomo 没在运行,直接启动"
|
||||||
|
Invoke-CmdStart $Cfg
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $Cfg)) {
|
||||||
|
W-Err "配置文件不存在: $Cfg"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host -NoNewline " 热重载配置 ... "
|
||||||
|
try {
|
||||||
|
$body = @{ path = $Cfg } | ConvertTo-Json -Compress
|
||||||
|
$uri = "http://127.0.0.1:$ApiPort/configs"
|
||||||
|
$resp = Invoke-WebRequest -Method PUT -Uri $uri -ContentType "application/json" -Body $body -UseBasicParsing -ErrorAction Stop
|
||||||
|
W-Ok "成功"
|
||||||
|
} catch {
|
||||||
|
W-Err "API 不可达,尝试重启..."
|
||||||
|
Invoke-CmdRestart $Cfg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 状态 ───
|
||||||
|
function Invoke-CmdStatus {
|
||||||
|
if (Test-Running) {
|
||||||
|
$procId = [System.IO.File]::ReadAllText($PidFile).Trim()
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${BoldGreen}● 运行中${NC} ${Dim}PID: $procId${NC}"
|
||||||
|
Write-Host " $SEP"
|
||||||
|
Write-Host " ${Dim}代理端口 ${NC}${Cyan}127.0.0.1:$MixedPort${NC} (HTTP/SOCKS5)"
|
||||||
|
Write-Host " ${Dim}API 端口 ${NC}${Cyan}127.0.0.1:$ApiPort${NC}"
|
||||||
|
Write-Host " ${Dim}管理面板 ${NC}${Cyan}$(Get-UiUrl)${NC}"
|
||||||
|
Write-Host " ${Dim}配置文件 ${NC}$ConfigFile"
|
||||||
|
Write-Host " ${Dim}日志文件 ${NC}$LogFile"
|
||||||
|
$src = if (Test-Path $LinkFile) { [System.IO.File]::ReadAllText($LinkFile).Trim() } else { Get-AutoDetectSource }
|
||||||
|
if ($src) { Write-Host " ${Dim}源文件 ${NC}$src" }
|
||||||
|
Write-Host ""
|
||||||
|
} else {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${BoldRed}○ 已停止${NC}"
|
||||||
|
Write-Host " $SEP"
|
||||||
|
W-Dim "mihomo 当前未运行"
|
||||||
|
Write-Host ""
|
||||||
|
Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 链接源配置 ───
|
||||||
|
function Invoke-CmdLink {
|
||||||
|
param([string]$Src)
|
||||||
|
|
||||||
|
if (-not $Src) {
|
||||||
|
Write-Host ""
|
||||||
|
W-Section "源文件链接"
|
||||||
|
if (Test-Path $LinkFile) {
|
||||||
|
W-Info "当前链接: $([System.IO.File]::ReadAllText($LinkFile).Trim())"
|
||||||
|
} else {
|
||||||
|
$detected = Get-AutoDetectSource
|
||||||
|
if ($detected) {
|
||||||
|
W-Info "自动检测: $detected"
|
||||||
|
} else {
|
||||||
|
W-Warn "未检测到源文件"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
$input = Read-Host " 请输入 YAML 文件路径 (Enter 取消)"
|
||||||
|
if (-not $input) { W-Dim "已取消"; return }
|
||||||
|
$Src = $input.Trim().Trim('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $Src)) {
|
||||||
|
W-Err "文件不存在: $Src"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.IO.File]::WriteAllText($LinkFile, $Src, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
W-Ok "已链接"
|
||||||
|
W-Dim "源文件 $Src"
|
||||||
|
Write-Host ""
|
||||||
|
$ans = Read-Host " 立即适配并启动? (y/n)"
|
||||||
|
if ($ans -eq 'y') {
|
||||||
|
$result = Invoke-WinAdaptConfig $Src
|
||||||
|
if (-not $result) { return }
|
||||||
|
if (Test-Running) {
|
||||||
|
Invoke-CmdRestart
|
||||||
|
} else {
|
||||||
|
Invoke-CmdStart
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 日志 / 编辑 / 面板 / 修复 / 诊断 ───
|
||||||
|
function Invoke-CmdLog {
|
||||||
|
if (Test-Path $LogFile) {
|
||||||
|
Write-Host ""
|
||||||
|
W-Section "实时日志 (Ctrl+C 退出)"
|
||||||
|
Get-Content $LogFile -Wait
|
||||||
|
} else {
|
||||||
|
W-Warn "日志文件不存在"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CmdEdit {
|
||||||
|
W-Info "打开编辑器: $ConfigFile"
|
||||||
|
$editor = $env:EDITOR
|
||||||
|
if (-not $editor) { $editor = "notepad" }
|
||||||
|
Start-Process $editor -ArgumentList "`"$ConfigFile`"" -Wait
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CmdOpen {
|
||||||
|
if (Test-Running) {
|
||||||
|
Update-UiIndex
|
||||||
|
$url = Get-UiUrl
|
||||||
|
Start-Process $url
|
||||||
|
W-Ok "已打开管理面板"
|
||||||
|
W-Dim $url
|
||||||
|
} else {
|
||||||
|
W-Warn "mihomo 未运行,请先启动"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CmdCleanup {
|
||||||
|
Invoke-NetworkRestore $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-CmdDoctor {
|
||||||
|
Invoke-NetworkHealthCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 关闭 Clash Verge 后台服务 ───
|
||||||
|
function Invoke-CmdVergeOff {
|
||||||
|
Write-Host ""
|
||||||
|
W-Section "关闭 Clash Verge"
|
||||||
|
|
||||||
|
$svc = Get-Service -ErrorAction SilentlyContinue | Where-Object {
|
||||||
|
$_.Name -like "*clash*verge*" -or $_.Name -like "*clash_verge*"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($svc) {
|
||||||
|
W-Info "发现 Clash Verge 服务: $($svc.Name) ($($svc.Status))"
|
||||||
|
Write-Host -NoNewline " 正在停止 ... "
|
||||||
|
if ($svc.Status -eq 'Running') {
|
||||||
|
Stop-Service -Name $svc.Name -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
# 杀残留进程
|
||||||
|
Get-Process -Name "clash-verge*", "clash_verge*" -ErrorAction SilentlyContinue |
|
||||||
|
Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
W-Ok "已停止"
|
||||||
|
} else {
|
||||||
|
$procs = Get-Process -Name "clash-verge*", "clash_verge*" -ErrorAction SilentlyContinue
|
||||||
|
if ($procs) {
|
||||||
|
W-Info "发现 Clash Verge 进程"
|
||||||
|
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||||
|
W-Ok "已停止"
|
||||||
|
} else {
|
||||||
|
W-Ok "Clash Verge 服务未运行"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
$ans = Read-Host " 是否同时卸载开机自启? (y/n)"
|
||||||
|
if ($ans -eq 'y') {
|
||||||
|
if ($svc) {
|
||||||
|
sc.exe delete $svc.Name 2>$null | Out-Null
|
||||||
|
W-Ok "已卸载开机自启"
|
||||||
|
} else {
|
||||||
|
W-Dim "未找到注册服务,跳过"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
W-Ok "现在可以纯用 clash.ps1 管理了"
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 帮助 ───
|
||||||
|
function Show-Help {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${BoldPurple}Mihomo Kernel Manager${NC} ${Dim}— Windows${NC}"
|
||||||
|
Write-Host " $SEP"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}用法:${NC}"
|
||||||
|
Write-Host " ${Cyan}clash.ps1 <command>${NC} 执行子命令"
|
||||||
|
Write-Host " ${Cyan}clash.ps1${NC} 进入交互菜单"
|
||||||
|
Write-Host " ${Cyan}clash.ps1 help${NC} 显示本帮助"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}核心:${NC}"
|
||||||
|
Write-Host " ${Cyan}start [file]${NC} 启动(自动适配 Windows 配置,TUN 模式)"
|
||||||
|
Write-Host " ${Cyan}stop${NC} 停止并恢复网络"
|
||||||
|
Write-Host " ${Cyan}restart${NC} 重启"
|
||||||
|
Write-Host " ${Cyan}status${NC} 查看运行状态"
|
||||||
|
Write-Host " ${Cyan}reload [file]${NC} 热重载配置"
|
||||||
|
Write-Host " ${Cyan}open${NC} 打开管理面板"
|
||||||
|
Write-Host " ${Cyan}log${NC} 实时日志"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}配置:${NC}"
|
||||||
|
Write-Host " ${Cyan}link [file]${NC} 查看/设置源 YAML"
|
||||||
|
Write-Host " ${Cyan}edit${NC} 编辑当前配置"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}网络:${NC}"
|
||||||
|
Write-Host " ${Cyan}cleanup${NC} 网络修复(清理残留 TUN/路由/DNS)"
|
||||||
|
Write-Host " ${Cyan}doctor${NC} 网络诊断"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}系统:${NC}"
|
||||||
|
Write-Host " ${Cyan}verge-off${NC} 关闭 Clash Verge 后台服务"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}示例:${NC}"
|
||||||
|
W-Dim "clash.ps1 start"
|
||||||
|
W-Dim 'clash.ps1 link C:\Users\you\Mihomo\config.yaml'
|
||||||
|
W-Dim "clash.ps1 cleanup"
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 交互菜单 ───
|
||||||
|
function Show-Menu {
|
||||||
|
Clear-Host
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${BoldPurple}Mihomo Kernel Manager${NC} ${Dim}— Windows${NC}"
|
||||||
|
Write-Host " $SEP"
|
||||||
|
|
||||||
|
if (Test-Running) {
|
||||||
|
$procId = [System.IO.File]::ReadAllText($PidFile).Trim()
|
||||||
|
Write-Host " ${BoldGreen}● 运行中${NC} ${Dim}PID: $procId 代理: $MixedPort API: $ApiPort${NC}"
|
||||||
|
} else {
|
||||||
|
Write-Host " ${BoldRed}○ 已停止${NC}"
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host " ${Bold}1${NC} 启动 ${Bold}5${NC} 状态"
|
||||||
|
Write-Host " ${Bold}2${NC} 停止 ${Bold}6${NC} 实时日志"
|
||||||
|
Write-Host " ${Bold}3${NC} 打开面板 ${Bold}7${NC} 热重载"
|
||||||
|
Write-Host " ${Bold}4${NC} 重启 ${Bold}8${NC} 网络修复"
|
||||||
|
Write-Host " ${Bold}0${NC} 退出"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host -NoNewline " ${Bold}❯${NC} "
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-InteractiveMenu {
|
||||||
|
while ($true) {
|
||||||
|
Show-Menu
|
||||||
|
$choice = Read-Host
|
||||||
|
Write-Host ""
|
||||||
|
switch ($choice) {
|
||||||
|
'1' { Invoke-CmdStart }
|
||||||
|
'2' { Invoke-CmdStop }
|
||||||
|
'3' { Invoke-CmdOpen }
|
||||||
|
'4' { Invoke-CmdRestart }
|
||||||
|
'5' { Invoke-CmdStatus }
|
||||||
|
'6' { Invoke-CmdLog }
|
||||||
|
'7' { Invoke-CmdReload }
|
||||||
|
'8' { Invoke-CmdCleanup }
|
||||||
|
{ $_ -in '0', 'q', 'Q' } {
|
||||||
|
Write-Host " ${Dim}再见 👋${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default { W-Warn "无效选项,请输入 0-8" }
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host -NoNewline " 按 Enter 返回菜单..."
|
||||||
|
Read-Host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# 入口
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# 检测是否为提权重启
|
||||||
|
$Script:Elevated = $false
|
||||||
|
$scriptArgs = $args
|
||||||
|
if ($args.Count -gt 0 -and $args[0] -eq '--elevated') {
|
||||||
|
$Script:Elevated = $true
|
||||||
|
$scriptArgs = if ($args.Count -gt 1) { $args[1..($args.Count-1)] } else { @() }
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = if ($scriptArgs.Count -gt 0) { $scriptArgs[0] } else { "" }
|
||||||
|
$cmdArgs = if ($scriptArgs.Count -gt 1) { $scriptArgs[1..($scriptArgs.Count-1)] } else { @() }
|
||||||
|
|
||||||
|
# 需要管理员的命令
|
||||||
|
$adminCmds = @('start', 's', 'stop', 'restart', 'r', 'cleanup', 'c', 'verge-off', '')
|
||||||
|
|
||||||
|
# 自动提权(UAC 关闭时无感)
|
||||||
|
if (-not $Script:Elevated -and ($cmd -in $adminCmds) -and -not (Test-Admin)) {
|
||||||
|
$elevateArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $PSCommandPath, "--elevated")
|
||||||
|
if ($cmd) { $elevateArgs += $cmd }
|
||||||
|
foreach ($a in $cmdArgs) { $elevateArgs += $a }
|
||||||
|
Start-Process "pwsh.exe" -Verb RunAs -ArgumentList $elevateArgs
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
# 命令分发
|
||||||
|
if ($cmd -eq 'start' -or $cmd -eq 's') {
|
||||||
|
Invoke-CmdStart ($cmdArgs | Select-Object -First 1)
|
||||||
|
} elseif ($cmd -eq 'stop') {
|
||||||
|
Invoke-CmdStop
|
||||||
|
} elseif ($cmd -eq 'restart' -or $cmd -eq 'r') {
|
||||||
|
Invoke-CmdRestart ($cmdArgs | Select-Object -First 1)
|
||||||
|
} elseif ($cmd -eq 'reload' -or $cmd -eq 'rl') {
|
||||||
|
Invoke-CmdReload ($cmdArgs | Select-Object -First 1)
|
||||||
|
} elseif ($cmd -eq 'status' -or $cmd -eq 'st') {
|
||||||
|
Invoke-CmdStatus
|
||||||
|
} elseif ($cmd -eq 'log' -or $cmd -eq 'l') {
|
||||||
|
Invoke-CmdLog
|
||||||
|
} elseif ($cmd -eq 'link' -or $cmd -eq 'lk') {
|
||||||
|
Invoke-CmdLink ($cmdArgs | Select-Object -First 1)
|
||||||
|
} elseif ($cmd -eq 'open' -or $cmd -eq 'o') {
|
||||||
|
Invoke-CmdOpen
|
||||||
|
} elseif ($cmd -eq 'edit' -or $cmd -eq 'e') {
|
||||||
|
Invoke-CmdEdit
|
||||||
|
} elseif ($cmd -eq 'cleanup' -or $cmd -eq 'c') {
|
||||||
|
Invoke-CmdCleanup
|
||||||
|
} elseif ($cmd -eq 'doctor' -or $cmd -eq 'd') {
|
||||||
|
Invoke-CmdDoctor
|
||||||
|
} elseif ($cmd -eq 'verge-off') {
|
||||||
|
Invoke-CmdVergeOff
|
||||||
|
} elseif ($cmd -eq 'help' -or $cmd -eq '-h' -or $cmd -eq '--help' -or $cmd -eq 'h') {
|
||||||
|
Show-Help
|
||||||
|
} else {
|
||||||
|
Invoke-InteractiveMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
# 提权模式下非交互命令结束时暂停(让用户看到输出)
|
||||||
|
if ($Script:Elevated -and $cmd -ne '' -and $cmd -ne 'help' -and $cmd -ne '-h' -and $cmd -ne '--help' -and $cmd -ne 'h') {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host -NoNewline " 按 Enter 关闭..."
|
||||||
|
Read-Host
|
||||||
|
}
|
||||||
+224
@@ -0,0 +1,224 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# Mihomo Kernel Manager — Windows 一键部署脚本
|
||||||
|
# 用法: pwsh install.ps1
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$ProgressPreference = "SilentlyContinue"
|
||||||
|
|
||||||
|
# ─── 版本 ───
|
||||||
|
$MihomoVersion = "v1.19.15"
|
||||||
|
$WintunVersion = "0.14.1"
|
||||||
|
|
||||||
|
# ─── 路径 ───
|
||||||
|
$InstallDir = "$env:LOCALAPPDATA\Programs\mihomo"
|
||||||
|
$ConfigDir = "$env:USERPROFILE\.config\mihomo"
|
||||||
|
$SourceDir = "$env:USERPROFILE\Mihomo"
|
||||||
|
$ScriptSrc = Join-Path $PSScriptRoot "clash.ps1"
|
||||||
|
$ScriptDst = Join-Path $InstallDir "clash.ps1"
|
||||||
|
|
||||||
|
# ─── 架构检测 ───
|
||||||
|
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "amd64" }
|
||||||
|
|
||||||
|
# ─── 下载地址 ───
|
||||||
|
$mihomoAsset = if ($arch -eq "arm64") {
|
||||||
|
"mihomo-windows-arm64-$MihomoVersion.zip"
|
||||||
|
} else {
|
||||||
|
"mihomo-windows-amd64-v1-$MihomoVersion.zip"
|
||||||
|
}
|
||||||
|
$mihomoUrl = "https://github.com/MetaCubeX/mihomo/releases/download/$MihomoVersion/$mihomoAsset"
|
||||||
|
$wintunUrl = "https://www.wintun.net/builds/wintun-$WintunVersion.zip"
|
||||||
|
|
||||||
|
# ─── 颜色 ───
|
||||||
|
$e = [char]27
|
||||||
|
$Bold = "$e[1m"
|
||||||
|
$Green = "$e[0;32m"; $BoldGreen = "$e[1;32m"
|
||||||
|
$Red = "$e[0;31m"
|
||||||
|
$Yellow= "$e[0;33m"
|
||||||
|
$Cyan = "$e[0;36m"
|
||||||
|
$Purple= "$e[0;35m"
|
||||||
|
$NC = "$e[0m"
|
||||||
|
|
||||||
|
function W-Step { Write-Host " ${Cyan}▶${NC} $args" }
|
||||||
|
function W-Done { Write-Host " ${Green}✓${NC} $args" }
|
||||||
|
function W-Fail { Write-Host " ${Red}✗${NC} $args" }
|
||||||
|
function W-Note { Write-Host " ${Yellow}⚠${NC} $args" }
|
||||||
|
|
||||||
|
# ─── 横幅 ───
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Purple}╭──────────────────────────────────────╮${NC}"
|
||||||
|
Write-Host " ${Purple}│${NC} ${Bold}Mihomo Kernel Manager 部署脚本${NC} ${Purple}│${NC}"
|
||||||
|
Write-Host " ${Purple}╰──────────────────────────────────────╯${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# ─── 前置检查 ───
|
||||||
|
Write-Host " ${Bold}── 系统检查 ──${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# PowerShell 版本
|
||||||
|
W-Step "PowerShell 版本 ... "
|
||||||
|
$psVersion = $PSVersionTable.PSVersion
|
||||||
|
if ($psVersion.Major -ge 7) {
|
||||||
|
Write-Host "${Green}已安装 ($psVersion)${NC}"
|
||||||
|
} else {
|
||||||
|
Write-Host "${Yellow}版本过低 ($psVersion),需要 7.0+${NC}"
|
||||||
|
Write-Host " 请运行: winget install Microsoft.PowerShell"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 创建目录
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}── 初始化目录 ──${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
W-Step "安装目录 ($InstallDir) ... "
|
||||||
|
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||||
|
W-Done "已创建"
|
||||||
|
|
||||||
|
W-Step "配置目录 ($ConfigDir) ... "
|
||||||
|
New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null
|
||||||
|
W-Done "已创建"
|
||||||
|
|
||||||
|
W-Step "源文件目录 ($SourceDir) ... "
|
||||||
|
New-Item -ItemType Directory -Path $SourceDir -Force | Out-Null
|
||||||
|
W-Done "已创建"
|
||||||
|
|
||||||
|
# ─── 下载 mihomo 内核 ───
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}── 下载 mihomo 内核 ──${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
$mihomoExe = Join-Path $InstallDir "mihomo.exe"
|
||||||
|
$needDownload = $true
|
||||||
|
if (Test-Path $mihomoExe) {
|
||||||
|
W-Note "mihomo.exe 已存在"
|
||||||
|
$ans = Read-Host " 重新下载? (y/n)"
|
||||||
|
if ($ans -ne 'y') { $needDownload = $false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($needDownload) {
|
||||||
|
W-Step "架构: $arch"
|
||||||
|
W-Step "版本: $MihomoVersion"
|
||||||
|
Write-Host " 下载中 ..."
|
||||||
|
|
||||||
|
$tempDir = New-Item -ItemType Directory -Path ([System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "mihomo-install-$(Get-Random)")) -Force
|
||||||
|
$mihomoZip = Join-Path $tempDir.FullName $mihomoAsset
|
||||||
|
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $mihomoUrl -OutFile $mihomoZip -UseBasicParsing
|
||||||
|
} catch {
|
||||||
|
W-Fail "下载失败: $_"
|
||||||
|
W-Note "手动下载: $mihomoUrl"
|
||||||
|
W-Note "解压 mihomo.exe 到: $InstallDir"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解压
|
||||||
|
W-Step "解压中 ..."
|
||||||
|
Expand-Archive -Path $mihomoZip -DestinationPath $tempDir.FullName -Force
|
||||||
|
|
||||||
|
# 找到 mihomo.exe 并复制
|
||||||
|
$extracted = Get-ChildItem $tempDir.FullName -Recurse -Filter "mihomo.exe" | Select-Object -First 1
|
||||||
|
if ($extracted) {
|
||||||
|
Copy-Item $extracted.FullName $mihomoExe -Force
|
||||||
|
W-Done "mihomo.exe 已安装"
|
||||||
|
} else {
|
||||||
|
W-Fail "未在压缩包中找到 mihomo.exe"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
Remove-Item $tempDir.FullName -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 下载 wintun.dll ───
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}── 下载 wintun.dll ──${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
$wintunDll = Join-Path $InstallDir "wintun.dll"
|
||||||
|
$needWintun = $true
|
||||||
|
if (Test-Path $wintunDll) {
|
||||||
|
W-Note "wintun.dll 已存在"
|
||||||
|
$ans = Read-Host " 重新下载? (y/n)"
|
||||||
|
if ($ans -ne 'y') { $needWintun = $false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($needWintun) {
|
||||||
|
W-Step "版本: $WintunVersion"
|
||||||
|
Write-Host " 下载中 ..."
|
||||||
|
|
||||||
|
$tempDir = New-Item -ItemType Directory -Path ([System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "wintun-install-$(Get-Random)")) -Force
|
||||||
|
$wintunZip = Join-Path $tempDir.FullName "wintun.zip"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $wintunUrl -OutFile $wintunZip -UseBasicParsing
|
||||||
|
} catch {
|
||||||
|
W-Fail "下载失败: $_"
|
||||||
|
W-Note "手动下载: $wintunUrl"
|
||||||
|
W-Note "提取 wintun\bin\$arch\wintun.dll 到: $InstallDir"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 解压
|
||||||
|
W-Step "解压中 ..."
|
||||||
|
Expand-Archive -Path $wintunZip -DestinationPath $tempDir.FullName -Force
|
||||||
|
|
||||||
|
# 找到对应架构的 wintun.dll
|
||||||
|
$wintunSrc = Join-Path $tempDir.FullName "wintun\bin\$arch\wintun.dll"
|
||||||
|
if (Test-Path $wintunSrc) {
|
||||||
|
Copy-Item $wintunSrc $wintunDll -Force
|
||||||
|
W-Done "wintun.dll 已安装 ($arch)"
|
||||||
|
} else {
|
||||||
|
W-Fail "未找到 $arch 架构的 wintun.dll"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 清理临时文件
|
||||||
|
Remove-Item $tempDir.FullName -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 部署 clash.ps1 ───
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}── 部署管理脚本 ──${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if (Test-Path $ScriptSrc) {
|
||||||
|
W-Step "安装 clash.ps1 ... "
|
||||||
|
Copy-Item $ScriptSrc $ScriptDst -Force
|
||||||
|
W-Done "已部署 → $ScriptDst"
|
||||||
|
} else {
|
||||||
|
W-Fail "未找到 clash.ps1(请确保 install.ps1 和 clash.ps1 在同一目录)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 添加到 PATH ───
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Bold}── 配置 PATH ──${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
$userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
|
||||||
|
if ($userPath -and $userPath.Split(';') -contains $InstallDir) {
|
||||||
|
W-Done "PATH 中已存在: $InstallDir"
|
||||||
|
} else {
|
||||||
|
W-Step "添加到用户 PATH ... "
|
||||||
|
$newPath = if ($userPath) { "$userPath;$InstallDir" } else { $InstallDir }
|
||||||
|
[Environment]::SetEnvironmentVariable("PATH", $newPath, "User")
|
||||||
|
W-Done "已添加: $InstallDir"
|
||||||
|
W-Note "请重新打开终端使 PATH 生效"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─── 完成 ───
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ${Purple}╭──────────────────────────────────────╮${NC}"
|
||||||
|
Write-Host " ${Purple}│${NC} ${BoldGreen}✅ 部署完成!${NC} ${Purple}│${NC}"
|
||||||
|
Write-Host " ${Purple}╰──────────────────────────────────────╯${NC}"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " 接下来:"
|
||||||
|
Write-Host " ${Cyan} 1.${NC} 把你的 .yaml 配置文件放入 ${Bold}$SourceDir${NC}"
|
||||||
|
Write-Host " ${Cyan} 2.${NC} 重新打开终端,运行 ${Bold}clash.ps1 start${NC} 启动"
|
||||||
|
Write-Host " ${Cyan} 3.${NC} 运行 ${Bold}clash.ps1${NC} 进入交互菜单"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " 更多命令: ${Bold}clash.ps1 help${NC}"
|
||||||
|
Write-Host ""
|
||||||
Reference in New Issue
Block a user