diff --git a/install/autorun.ps1 b/install/autorun.ps1 index 3ccdaa3b..94ae6285 100644 --- a/install/autorun.ps1 +++ b/install/autorun.ps1 @@ -80,116 +80,118 @@ if (-not $SkipConfirm) { } # ------------------------------------------------------------------ -# 4. 安装 nvm-windows +# 4. 统一预检 & 安装(仅调整顺序,不改动原实现) # ------------------------------------------------------------------ +$needRestart = $false + +# ---------- nvm ---------- if (-not (Test-Cmd "nvm")) { Write-Host "正在安装 nvm-windows..." -ForegroundColor Green $nvmSetup = "$env:TEMP\nvm-setup.exe" Invoke-WebRequestWithProxy "https://github.com/coreybutler/nvm-windows/releases/latest/download/nvm-setup.exe" $nvmSetup Start-Process -Wait -FilePath $nvmSetup -ArgumentList "/silent" Remove-Item $nvmSetup - Write-Host "" - Write-Host "--------------------------------------------------" -ForegroundColor Yellow - Write-Host "nvm 已安装完毕,但当前 PowerShell 会话尚未识别到它。" -ForegroundColor Yellow - Write-Host "请执行以下任意一步后再继续:" -ForegroundColor Cyan - Write-Host " 1) 关闭本窗口,重新打开一个『管理员』PowerShell后,再次执行脚本;" -ForegroundColor Cyan - Write-Host " 2) 或者再次右键选PS运行本脚本。" -ForegroundColor Cyan - Write-Host "--------------------------------------------------" -ForegroundColor Yellow - Read-Host "按 Enter 键退出本窗口" - exit + $needRestart = $true } else { Write-Host "已检测到 nvm,跳过安装" -ForegroundColor Green } -# ------------------------------------------------- -# 5. 安装/切换 Node -# ------------------------------------------------- -$needNode = $false -if (Test-Cmd "node") { - $nodeVer = (node -v) -replace '^v','' -split '\.' | ForEach-Object { [int]$_ } - $current = $nodeVer[0]*10000 + $nodeVer[1]*100 + $nodeVer[2] - $require = 20*10000 + 18*100 + 3 # 20.18.3 - if ($current -ge $require) { - Write-Host "已检测到 Node v$($nodeVer -join '.') ≥20.18.3,跳过安装" -ForegroundColor Green - } else { - Write-Host "Node 版本低于 20.18.3,将使用 nvm 安装/切换到 20.18.3" -ForegroundColor Yellow - $needNode = $true - } -} else { - Write-Host "未检测到 Node,准备安装" -ForegroundColor Yellow - $needNode = $true -} -if ($needNode) { - nvm install 20.18.3 - nvm use 20.18.3 -} - # ------------------------------------------------- # 6. 安装 Python 3.11(优先 winget) # ------------------------------------------------- -$pyNeed = $false +$pythonOk = $false try { $ver = (python -V 2>$null) -replace 'Python ','' - if ($ver -match '^3\.11') { - Write-Host "已检测到 Python 3.11 ($ver),跳过安装" -ForegroundColor Green - } else { - Write-Host "检测到非 3.11 版本,准备覆盖安装 3.11" -ForegroundColor Yellow - $pyNeed = $true + if ([version]$ver -ge [version]"3.10") { + Write-Host "已检测到 Python $ver ≥ 3.10,跳过安装" -ForegroundColor Green + $pythonOk = $true } -} catch { - Write-Host "未检测到 Python,准备安装 3.11" -ForegroundColor Yellow - $pyNeed = $true -} -if ($pyNeed) { +} catch {} +if (-not $pythonOk) { Install-Winget - Write-Host "正在通过 winget 安装 Python 3.11..." -ForegroundColor Green - winget install --id Python.Python.3.11 -e --accept-source-agreements --accept-package-agreements - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + $latestId = (winget search --id Python.Python.* --exact --source winget | + Select-String '^Python\.Python\.\d+' | + ForEach-Object { $_.Matches.Value } | + Sort-Object { [version]($_ -replace 'Python\.Python\.','') } | + Select-Object -Last 1) + if (-not $latestId) { $latestId = "Python.Python.3.12" } + Write-Host "准备通过 winget 安装 $latestId ..." -ForegroundColor Green + winget install --id $latestId -e --accept-source-agreements --accept-package-agreements + $needRestart = $true } # ------------------------------------------------- # 7. 安装 Git:winget 优先,失败自动离线 # ------------------------------------------------- -if (-not (Test-Cmd "git")) { - # 1) winget 交互式安装 +$gitOk = $false +if (Test-Cmd "git") { + Write-Host "已检测到 Git,跳过安装" -ForegroundColor Green + $gitOk = $true +} +if (-not $gitOk) { + # 1) winget 交互 Install-Winget if (Test-Cmd winget) { Write-Host "正在通过 winget 安装 Git(交互模式)..." -ForegroundColor Green try { winget install --id Git.Git -e --source winget + + # 重新加载 PATH(关键) + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + + ";" + + [System.Environment]::GetEnvironmentVariable("Path","User") + + # 再检测一次 if (Test-Cmd git) { Write-Host "Git 安装成功(winget)" -ForegroundColor Green - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") - continue + $gitOk = $true # 这里才会真正阻止后面的离线安装 } } catch { Write-Host "winget 安装失败,将使用离线包..." -ForegroundColor Yellow } } - # 2) winget 失败 → 离线安装 - Write-Host "正在解析 Git 最新版本..." -ForegroundColor Green - try { - $latestUri = (Invoke-WebRequest -Uri "https://github.com/git-for-windows/git/releases/latest" -MaximumRedirection 0 -ErrorAction SilentlyContinue).Headers.Location - $ver = if ($latestUri) { $latestUri -replace '.*/tag/v([0-9.]+).*$','$1' } else { "2.51.0" } - } catch { - $ver = "2.51.0" + # 2) winget 失败 → 离线包 + if (-not $gitOk) { + Write-Host "正在解析 Git 最新版本..." -ForegroundColor Green + try { + $latestUri = (Invoke-WebRequest -Uri "https://github.com/git-for-windows/git/releases/latest" -MaximumRedirection 0 -ErrorAction SilentlyContinue).Headers.Location + $ver = if ($latestUri) { $latestUri -replace '.*/tag/v([0-9.]+).*$','$1' } else { "2.51.0" } + } catch { + $ver = "2.51.0" + } + + Write-Host "正在下载 Git $ver ..." -ForegroundColor Green + $gitSetup = "$env:TEMP\Git-$ver-64-bit.exe" + $gitUrl = "https://github.com/git-for-windows/git/releases/download/v$ver.windows.1/Git-$ver-64-bit.exe" + Invoke-WebRequestWithProxy $gitUrl $gitSetup + Start-Process -Wait -FilePath $gitSetup -ArgumentList "/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS" + Remove-Item $gitSetup -Force + $needRestart = $true } +} - Write-Host "正在下载 Git $ver ..." -ForegroundColor Green - $gitSetup = "$env:TEMP\Git-$ver-64-bit.exe" - $gitUrl = "https://github.com/git-for-windows/git/releases/download/v$ver.windows.1/Git-$ver-64-bit.exe" - Invoke-WebRequestWithProxy $gitUrl $gitSetup - Start-Process -Wait -FilePath $gitSetup -ArgumentList "/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS" - Remove-Item $gitSetup -Force - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") -} else { - Write-Host "已检测到 Git,跳过安装" -ForegroundColor Green +# ---------- 统一重启提示 ---------- +if ($needRestart) { + Write-Host "--------------------------------------------------" -ForegroundColor Yellow + Write-Host "依赖已安装/更新完成,但需重新加载环境变量。" -ForegroundColor Yellow + Write-Host "请关闭本窗口,重新打开『管理员』PowerShell 后再运行本脚本继续后续步骤。" -ForegroundColor Cyan + Write-Host "--------------------------------------------------" -ForegroundColor Yellow + Read-Host "按 Enter 键退出" + exit } -# ------------------------------------------------- -# 8. 安装全局 npm 工具 -# ------------------------------------------------- +# ------------------------------------------------------------------ +# 5. 阶段二:Node / yarn / pm2 / 克隆 / 配置 +# ------------------------------------------------------------------ +# 此时 nvm 已生效 +if (-not (Test-Cmd "node") -or -not (node -v).StartsWith("v20.")) { + Write-Host "正在安装/切换到 Node 20.18.3 ..." -ForegroundColor Green + nvm install 20.18.3 + nvm use 20.18.3 +} + +# 安装 yarn / pm2 $tools = @{ yarn = { npm install -g yarn } pm2 = { npm install -g pm2 } @@ -241,69 +243,83 @@ Use-ProxyIfNeeded -Script { [System.IO.File]::WriteAllLines($configJson, $jsonText, $utf8NoBom) } -# 生成 .env(UTF-8 无 BOM,不乱码) -$envFile = Join-Path $projectPath ".env" -if (-not (Test-Path $envFile)) { + # 生成 .env(UTF-8 无 BOM) + $envFile = Join-Path $projectPath ".env" + if (-not (Test-Path $envFile)) { # 如果仓库没带模板,就写一份最小模板(同样无 BOM) - $template = Join-Path $projectPath ".env.development" - if (-not (Test-Path $template)) { - @" + $template = Join-Path $projectPath ".env.development" + if (-not (Test-Path $template)) { + @" NODE_ENV=development COOKIE_AUTH_CODE=drpys API_AUTH_NAME=admin API_AUTH_CODE=drpys API_PWD=dzyyds "@ | Out-File $template -Encoding UTF8 - } - + } # 复制模板 - Copy-Item $template $envFile + Copy-Item $template $envFile # 依次输入 - $cookieAuth = (Read-Host "网盘入库密码(默认 drpys)").Trim() - $apiUser = (Read-Host "登录用户名(默认 admin)").Trim() - $apiPass = (Read-Host "登录密码(默认 drpys)").Trim() - $apiPwd = (Read-Host "订阅PWD值(默认 dzyyds)").Trim() + $cookieAuth = (Read-Host "网盘入库密码(默认 drpys)").Trim() + $apiUser = (Read-Host "登录用户名(默认 admin)").Trim() + $apiPass = (Read-Host "登录密码(默认 drpys)").Trim() + $apiPwd = (Read-Host "订阅PWD值(默认 dzyyds)").Trim() # 空值兜底 - if ([string]::IsNullOrWhiteSpace($cookieAuth)) { $cookieAuth = 'drpys' } - if ([string]::IsNullOrWhiteSpace($apiUser)) { $apiUser = 'admin' } - if ([string]::IsNullOrWhiteSpace($apiPass)) { $apiPass = 'drpys' } - if ([string]::IsNullOrWhiteSpace($apiPwd)) { $apiPwd = 'dzyyds' } + if ([string]::IsNullOrWhiteSpace($cookieAuth)) { $cookieAuth = 'drpys' } + if ([string]::IsNullOrWhiteSpace($apiUser)) { $apiUser = 'admin' } + if ([string]::IsNullOrWhiteSpace($apiPass)) { $apiPass = 'drpys' } + if ([string]::IsNullOrWhiteSpace($apiPwd)) { $apiPwd = 'dzyyds' } # 逐行替换,最后统一 UTF-8 无 BOM 写回 - $utf8NoBom = [System.Text.UTF8Encoding]::new($false) - $lines = [System.IO.File]::ReadAllLines($template, $utf8NoBom) - - for ($i = 0; $i -lt $lines.Count; $i++) { - if ($lines[$i] -match '^\s*COOKIE_AUTH_CODE\s*=') { - $lines[$i] = "COOKIE_AUTH_CODE = $cookieAuth" - } - elseif ($lines[$i] -match '^\s*API_AUTH_NAME\s*=') { - $lines[$i] = "API_AUTH_NAME = $apiUser" - } - elseif ($lines[$i] -match '^\s*API_AUTH_CODE\s*=') { - $lines[$i] = "API_AUTH_CODE = $apiPass" - } - elseif ($lines[$i] -match '^\s*API_PWD\s*=') { - $lines[$i] = "API_PWD = $apiPwd" + $utf8NoBom = [System.Text.UTF8Encoding]::new($false) + $lines = [System.IO.File]::ReadAllLines($template, $utf8NoBom) + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($lines[$i] -match '^\s*COOKIE_AUTH_CODE\s*=') { + $lines[$i] = "COOKIE_AUTH_CODE = $cookieAuth" + } elseif ($lines[$i] -match '^\s*API_AUTH_NAME\s*=') { + $lines[$i] = "API_AUTH_NAME = $apiUser" + } elseif ($lines[$i] -match '^\s*API_AUTH_CODE\s*=') { + $lines[$i] = "API_AUTH_CODE = $apiPass" + } elseif ($lines[$i] -match '^\s*API_PWD\s*=') { + $lines[$i] = "API_PWD = $apiPwd" + } } + [System.IO.File]::WriteAllLines($envFile, $lines, $utf8NoBom) } - [System.IO.File]::WriteAllLines($envFile, $lines, $utf8NoBom) -} + # ---------- Node 依赖 ---------- + function Invoke-YarnWithRetry { + param([int]$MaxRetry = 3) + $mirrors = @( + 'https://registry.npmmirror.com/', + 'https://registry.yarnpkg.com', + 'https://registry.npmjs.org' + ) + $attempt = 0 + while ($attempt -lt $MaxRetry) { + $attempt++ + $mirror = $mirrors[$attempt-1] + Write-Host "尝试使用镜像 $mirror 安装 Node 依赖(第 $attempt/$MaxRetry 次)..." -ForegroundColor Cyan + yarn config set registry $mirror | Out-Null + try { + yarn --frozen-lockfile + if ($LASTEXITCODE -eq 0) { return } + } catch {} + } + Write-Host "[ERROR] 所有镜像均失败,请手动执行 yarn" -ForegroundColor Red + } - # Node 依赖 if (-not (Test-Path "node_modules")) { Write-Host "首次安装 Node 依赖..." -ForegroundColor Yellow - yarn config set registry https://registry.npmmirror.com/ - yarn - } elseif ((git diff HEAD^ HEAD --name-only 2>$null) -match [regex]::Escape("yarn.lock")) { + Invoke-YarnWithRetry + } elseif ((git diff HEAD~1 HEAD --name-only 2>$null) -match [regex]::Escape("yarn.lock")) { Write-Host "检测到 yarn.lock 变动,更新 Node 依赖..." -ForegroundColor Yellow - yarn install --registry https://registry.npmmirror.com/ + Invoke-YarnWithRetry } - # Python 虚拟环境 & 依赖 + # ---------- Python 虚拟环境 & 依赖 ---------- $venvActivate = Join-Path $projectPath ".venv\Scripts\Activate.ps1" if (-not (Test-Path ".venv\pyvenv.cfg")) { Write-Host "首次创建 Python 虚拟环境..." -ForegroundColor Yellow @@ -311,14 +327,39 @@ API_PWD=dzyyds } & $venvActivate python -m pip install --upgrade pip -q - pip install -r spider\py\base\requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple -q + Write-Host "虚拟环境创建完成" -ForegroundColor Green + function Invoke-PipWithRetry { + param( + [string]$ReqFile, + [int]$MaxRetry = 3 + ) + $mirrors = @( + 'https://mirrors.cloud.tencent.com/pypi/simple', + 'https://pypi.tuna.tsinghua.edu.cn/simple', + 'https://pypi.org/simple' + ) + $attempt = 0 + while ($attempt -lt $MaxRetry) { + $attempt++ + $mirror = $mirrors[$attempt-1] + Write-Host "尝试使用镜像 $mirror 安装 Python 依赖(第 $attempt/$MaxRetry 次)..." -ForegroundColor Cyan + try { + pip install -r $ReqFile -i $mirror --no-warn-script-location -q + Write-Host "pip install 完成" -ForegroundColor Green + if ($LASTEXITCODE -eq 0) { return } + } catch {} + } + Write-Host "[ERROR] 所有镜像均失败,请手动执行 pip install" -ForegroundColor Red + } + + Invoke-PipWithRetry "spider\py\base\requirements.txt" - if ((git diff HEAD^ HEAD --name-only 2>$null) -match [regex]::Escape("spider\py\base\requirements.txt")) { + if ((git diff HEAD~1 HEAD --name-only 2>$null) -match [regex]::Escape("spider\py\base\requirements.txt")) { Write-Host "检测到 requirements.txt 变动,更新 Python 依赖..." -ForegroundColor Yellow - pip install -r spider\py\base\requirements.txt -i https://mirrors.cloud.tencent.com/pypi/simple -q + Invoke-PipWithRetry "spider\py\base\requirements.txt" } - # PM2 + # ---------- PM2 ---------- if (-not (pm2 list | Select-String "drpyS.*online")) { Write-Host "首次启动 PM2 进程..." -ForegroundColor Yellow pm2 start index.js --name drpyS --update-env