fix: harden mirrored downloads and first-run upgrades
This commit is contained in:
1 parent
df0a7dc37a
commit
53c5680260
10 files changed
+437
-160
No files matched your search
@@ -8,7 +8,7 @@ function Get-BootstrapText {
|
||||
}
|
||||
|
||||
function Initialize-ArtemisPython {
|
||||
param([Parameter(Mandatory=$true)][string]$ProjectRoot)
|
||||
param([Parameter(Mandatory=$true)][string]$ProjectRoot, [switch]$RuntimeOnly)
|
||||
|
||||
$ProjectRoot = (Resolve-Path -LiteralPath $ProjectRoot -ErrorAction Stop).Path
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $ProjectRoot 'pyproject.toml'))) {
|
||||
@@ -65,6 +65,10 @@ function Initialize-ArtemisPython {
|
||||
throw (Get-BootstrapText 'The existing .venv is unusable or older than Python 3.12. Rename it as a backup, then rerun start.bat.' '已有 .venv 无法使用或 Python 低于 3.12。先重命名备份,再运行 start-cn.bat。')
|
||||
}
|
||||
|
||||
# The frozen source updater uses only Python's standard library. Install
|
||||
# project dependencies once, from the new lock file AFTER the fast-forward.
|
||||
if ($RuntimeOnly) { return $pythonExe }
|
||||
|
||||
# A cheap check skips installation when the lock and installed packages agree.
|
||||
# --inexact preserves any optional development tools already installed.
|
||||
$lockOption = if ($ArtemisDownloads -and $ArtemisDownloads.python_index) { '--frozen' } else { '--locked' }
|
||||
|
||||
+104
-5
@@ -1,8 +1,95 @@
|
||||
# Download settings are process-local; never edit global pip/npm/uv settings.
|
||||
# Save as UTF-8 with BOM so Windows PowerShell 5.1 reads Chinese messages correctly.
|
||||
|
||||
function Get-DownloadText {
|
||||
param([string]$English, [string]$Chinese)
|
||||
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN') { return $Chinese }
|
||||
return $English
|
||||
}
|
||||
|
||||
function Invoke-DownloadFile {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Uri,
|
||||
[Parameter(Mandatory=$true)][string]$OutFile,
|
||||
[int]$TimeoutSec = $ArtemisDownloads.timeout_seconds
|
||||
)
|
||||
$script:ArtemisDownloadError = ''
|
||||
Remove-Item -LiteralPath $OutFile -Force -ErrorAction SilentlyContinue
|
||||
if (Get-Command curl.exe -ErrorAction SilentlyContinue) {
|
||||
$previousErrors = $ErrorActionPreference
|
||||
try {
|
||||
# Native stderr is an ErrorRecord in PowerShell 5.1. Inspect the code,
|
||||
# and log only HTTP status/exit code, never a URL containing credentials.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$status = & curl.exe -f -sSL --connect-timeout 5 --max-time $TimeoutSec --proto '=https' --proto-redir '=https' --write-out '%{http_code}' -o $OutFile $Uri 2>$null
|
||||
$curlCode = $LASTEXITCODE
|
||||
if ($curlCode -eq 0 -and (Test-Path -LiteralPath $OutFile) -and (Get-Item -LiteralPath $OutFile).Length -gt 0) { return $true }
|
||||
$script:ArtemisDownloadError = if ($curlCode -eq 22) { "HTTP $status" }
|
||||
elseif ($curlCode -eq 28) { Get-DownloadText "Download timed out after $TimeoutSec seconds" "下载超时($TimeoutSec 秒)" }
|
||||
else { "curl exit $curlCode" }
|
||||
# Do not repeat an HTTP rejection or a full timeout with a second client.
|
||||
if ($curlCode -in @(22, 28)) { return $false }
|
||||
} catch {
|
||||
$script:ArtemisDownloadError = 'curl failed'
|
||||
} finally {
|
||||
$ErrorActionPreference = $previousErrors
|
||||
if ($script:ArtemisDownloadError) { Remove-Item -LiteralPath $OutFile -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
$previousProgress = $ProgressPreference
|
||||
try {
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -TimeoutSec $TimeoutSec -UseBasicParsing -ErrorAction Stop
|
||||
if ((Test-Path -LiteralPath $OutFile) -and (Get-Item -LiteralPath $OutFile).Length -gt 0) {
|
||||
$script:ArtemisDownloadError = ''
|
||||
return $true
|
||||
}
|
||||
$script:ArtemisDownloadError = Get-DownloadText 'Empty download' '下载内容为空'
|
||||
} catch {
|
||||
$response = $_.Exception.Response
|
||||
$script:ArtemisDownloadError = if ($response) { "HTTP $([int]$response.StatusCode)" }
|
||||
else { Get-DownloadText 'Network/TLS error or download timeout' '网络、TLS 连接失败或下载超时' }
|
||||
} finally {
|
||||
$ProgressPreference = $previousProgress
|
||||
if ($script:ArtemisDownloadError) { Remove-Item -LiteralPath $OutFile -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-ArtemisFileSha256 {
|
||||
param([Parameter(Mandatory=$true)][string]$Path)
|
||||
# Do not depend on Get-FileHash module autoloading: PowerShell 7 can pass its
|
||||
# own module search path to the Windows PowerShell 5.1 launcher.
|
||||
$hasher = [Security.Cryptography.SHA256]::Create()
|
||||
$stream = [IO.File]::OpenRead($Path)
|
||||
try { return [BitConverter]::ToString($hasher.ComputeHash($stream)).Replace('-', '').ToLowerInvariant() }
|
||||
finally { $stream.Dispose(); $hasher.Dispose() }
|
||||
}
|
||||
|
||||
function Invoke-ArtemisVerifiedDownload {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string[]]$Urls,
|
||||
[Parameter(Mandatory=$true)][string]$OutFile,
|
||||
[Parameter(Mandatory=$true)][string]$Sha256
|
||||
)
|
||||
$sources = @($Urls | Select-Object -Unique)
|
||||
for ($index = 0; $index -lt $sources.Count; $index++) {
|
||||
$sourceHost = ([Uri]$sources[$index]).Host
|
||||
Write-Host (Get-DownloadText " [DOWNLOAD $($index + 1)/$($sources.Count)] scrcpy: $sourceHost" " [下载 $($index + 1)/$($sources.Count)] scrcpy:$sourceHost") -ForegroundColor Cyan
|
||||
if (Invoke-DownloadFile -Uri $sources[$index] -OutFile $OutFile) {
|
||||
$actualHash = Get-ArtemisFileSha256 -Path $OutFile
|
||||
if ($actualHash -eq $Sha256) { return $true }
|
||||
$script:ArtemisDownloadError = Get-DownloadText 'Official SHA256 verification failed; archive rejected' '官方 SHA256 校验失败,已拒绝此安装包'
|
||||
Remove-Item -LiteralPath $OutFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Write-Host (Get-DownloadText " [WARN] $sourceHost`: $script:ArtemisDownloadError" " [提示] $sourceHost 下载失败:$script:ArtemisDownloadError") -ForegroundColor Yellow
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Initialize-ArtemisDownloads {
|
||||
param([string]$ProjectRoot, [string]$Mode)
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
||||
$path = Join-Path $ProjectRoot 'config\downloads.json'
|
||||
$config = Get-Content -LiteralPath $path -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
||||
if (-not $Mode) { $Mode = $config.mode }
|
||||
@@ -28,6 +115,21 @@ function Initialize-ArtemisDownloads {
|
||||
}
|
||||
}
|
||||
}
|
||||
$fallbacks = @('https://downloads.sourceforge.net/project/scrcpy.mirror/v4.1/scrcpy-win64-v4.1.zip')
|
||||
if ($Mode -eq 'China' -and $null -ne $config.china.scrcpy_fallback_urls) { $fallbacks = $config.china.scrcpy_fallback_urls }
|
||||
if ($null -ne $config.overrides.scrcpy_fallback_urls) { $fallbacks = $config.overrides.scrcpy_fallback_urls }
|
||||
if ($fallbacks -isnot [Array]) { throw 'config/downloads.json: scrcpy_fallback_urls must be an array of HTTPS URLs.' }
|
||||
foreach ($url in $fallbacks) {
|
||||
$uri = $null
|
||||
if ($url -isnot [string] -or -not [Uri]::TryCreate($url, [UriKind]::Absolute, [ref]$uri) -or $uri.Scheme -ne 'https') {
|
||||
throw 'config/downloads.json: scrcpy_fallback_urls must contain only HTTPS URLs.'
|
||||
}
|
||||
}
|
||||
$values.scrcpy_urls = @(@($values.scrcpy_url) + @($fallbacks) | Select-Object -Unique)
|
||||
if ($Mode -eq 'China') {
|
||||
# China mode must never silently fall back to a direct GitHub download.
|
||||
$values.scrcpy_urls = @($values.scrcpy_urls | Where-Object { ([Uri]$_).Host -notin @('github.com', 'www.github.com') })
|
||||
}
|
||||
# Explicit caller environment settings take priority over the selected preset.
|
||||
if ($env:UV_DEFAULT_INDEX) { $values.python_index = $env:UV_DEFAULT_INDEX }
|
||||
elseif ($env:UV_INDEX_URL) { $values.python_index = $env:UV_INDEX_URL }
|
||||
@@ -42,7 +144,7 @@ function Initialize-ArtemisDownloads {
|
||||
# Official scrcpy v4.1 win64 release digest. Verify every mirror/override equally.
|
||||
$values.scrcpy_sha256 = '5b12172b3264b2889f4583ee64752ce832e29bc8b1089dca81093459697165db'
|
||||
$script:ArtemisDownloads = [pscustomobject]$values
|
||||
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN') {
|
||||
if ($env:ARTEMIS_LAUNCH_LANG -eq 'zh-CN' -and $Mode -eq 'China') {
|
||||
Write-Host " [镜像] 使用国内下载源(config/downloads.json)。" -ForegroundColor Cyan
|
||||
} else {
|
||||
Write-Host " [INFO] Download mode: $Mode (config/downloads.json)." -ForegroundColor Cyan
|
||||
@@ -67,10 +169,7 @@ function Install-ArtemisUvFromIndex {
|
||||
if ($url.Scheme -ne 'https') { throw 'The uv download must use HTTPS.' }
|
||||
$zip = Join-Path $work 'uv.zip'
|
||||
if (-not (Invoke-DownloadFile -Uri $url.AbsoluteUri.Split('#')[0] -OutFile $zip)) { throw 'Could not download uv from the selected index.' }
|
||||
$hasher = [Security.Cryptography.SHA256]::Create()
|
||||
$stream = [IO.File]::OpenRead($zip)
|
||||
try { $digest = [BitConverter]::ToString($hasher.ComputeHash($stream)).Replace('-', '') }
|
||||
finally { $stream.Dispose(); $hasher.Dispose() }
|
||||
$digest = Get-ArtemisFileSha256 -Path $zip
|
||||
if ($digest -ne $candidate.Groups['hash'].Value) {
|
||||
throw 'The downloaded uv package failed SHA256 verification.'
|
||||
}
|
||||
|
||||
+26
-123
@@ -83,6 +83,7 @@ if ((-not $SkipUpdate) -and (Test-Path -LiteralPath $existingPython)) {
|
||||
& $existingPython (Join-Path $ScriptDir 'update_installation.py') source-check
|
||||
# Network/authentication failures are warnings, never a reason to prevent startup.
|
||||
}
|
||||
. (Join-Path $ScriptDir 'windows_tools.ps1')
|
||||
. (Join-Path $ScriptDir 'download_config.ps1')
|
||||
try {
|
||||
Initialize-ArtemisDownloads -ProjectRoot $RootDir -Mode $Mirror
|
||||
@@ -96,55 +97,6 @@ Write-Host (Get-LaunchText " Artemis Autonomous Mobile Agent UI
|
||||
Write-Host "======================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
function Update-EnvironmentPath {
|
||||
$standardDirs = @(
|
||||
"$env:LOCALAPPDATA\Microsoft\WinGet\Links",
|
||||
"$env:LOCALAPPDATA\Programs\node",
|
||||
"$env:USERPROFILE\.local\share\node",
|
||||
"$env:LOCALAPPDATA\Programs\scrcpy",
|
||||
"$env:USERPROFILE\.local\share\scrcpy",
|
||||
"$env:LOCALAPPDATA\Programs\platform-tools",
|
||||
"$env:USERPROFILE\.local\share\platform-tools",
|
||||
"$env:LOCALAPPDATA\Programs\uv",
|
||||
"$env:LOCALAPPDATA\uv",
|
||||
"$env:APPDATA\uv",
|
||||
"$env:USERPROFILE\.cargo\bin",
|
||||
"$env:USERPROFILE\.local\bin",
|
||||
"$env:USERPROFILE\scoop\shims",
|
||||
"C:\ProgramData\chocolatey\bin",
|
||||
"$env:LOCALAPPDATA\Android\Sdk\platform-tools",
|
||||
"$env:LOCALAPPDATA\Android\android-sdk\platform-tools",
|
||||
"$env:ProgramFiles\Android\platform-tools",
|
||||
"${env:ProgramFiles(x86)}\Android\android-sdk\platform-tools",
|
||||
"$env:ProgramFiles\nodejs",
|
||||
"${env:ProgramFiles(x86)}\nodejs",
|
||||
"$env:APPDATA\npm"
|
||||
)
|
||||
if ($env:CARGO_HOME) { $standardDirs = @("$env:CARGO_HOME\bin") + $standardDirs }
|
||||
if ($env:ANDROID_HOME) { $standardDirs += "$env:ANDROID_HOME\platform-tools" }
|
||||
if ($env:ANDROID_SDK_ROOT) { $standardDirs += "$env:ANDROID_SDK_ROOT\platform-tools" }
|
||||
if ($env:NVM_SYMLINK) { $standardDirs = @($env:NVM_SYMLINK) + $standardDirs }
|
||||
if ($env:NVM_HOME) { $standardDirs = @($env:NVM_HOME) + $standardDirs }
|
||||
|
||||
$regPath = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
$currentPaths = ($env:PATH -split ";") + ($regPath -split ";") + $standardDirs | Where-Object { [string]::IsNullOrWhiteSpace($_) -eq $false -and (Test-Path $_) } | Select-Object -Unique
|
||||
$env:PATH = $currentPaths -join ";"
|
||||
|
||||
# Ensure user-space portable tools take absolute priority over system-level outdated versions
|
||||
$portableNode = "$env:LOCALAPPDATA\Programs\node"
|
||||
if (Test-Path "$portableNode\node.exe") {
|
||||
$env:PATH = "$portableNode;$env:PATH"
|
||||
}
|
||||
$portableScrcpy = "$env:LOCALAPPDATA\Programs\scrcpy"
|
||||
if (Test-Path "$portableScrcpy\scrcpy.exe") {
|
||||
$env:PATH = "$portableScrcpy;$env:PATH"
|
||||
}
|
||||
$portablePt = "$env:LOCALAPPDATA\Programs\platform-tools"
|
||||
if (Test-Path "$portablePt\adb.exe") {
|
||||
$env:PATH = "$portablePt;$env:PATH"
|
||||
}
|
||||
}
|
||||
|
||||
function Start-LocalAdbServer {
|
||||
if (Test-CommandExists "adb") {
|
||||
$adbErrorPreference = $ErrorActionPreference
|
||||
@@ -167,54 +119,6 @@ function Start-LocalAdbServer {
|
||||
}
|
||||
}
|
||||
|
||||
function Test-CommandExists {
|
||||
param([string]$Command)
|
||||
$res = Get-Command $Command -ErrorAction SilentlyContinue
|
||||
return ($null -ne $res)
|
||||
}
|
||||
|
||||
function Invoke-DownloadFile {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Uri,
|
||||
[Parameter(Mandatory=$true)][string]$OutFile,
|
||||
[int]$TimeoutSec = $ArtemisDownloads.timeout_seconds
|
||||
)
|
||||
if (Test-Path $OutFile) {
|
||||
Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# 1. Try Windows built-in curl.exe first (available in Win 10 1803+ and Win 11)
|
||||
if (Test-CommandExists "curl.exe") {
|
||||
try {
|
||||
& curl.exe -f -sSL --connect-timeout 5 --max-time $TimeoutSec "$Uri" -o "$OutFile" 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and (Test-Path $OutFile) -and ((Get-Item $OutFile).Length -gt 0)) {
|
||||
return $true
|
||||
}
|
||||
} catch {}
|
||||
if (Test-Path $OutFile) {
|
||||
Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Fallback to Invoke-WebRequest (suppressing progress bar to eliminate PowerShell buffer hangs)
|
||||
try {
|
||||
$prevProgress = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Invoke-WebRequest -Uri $Uri -OutFile $OutFile -TimeoutSec $TimeoutSec -UseBasicParsing -ErrorAction Stop
|
||||
$ProgressPreference = $prevProgress
|
||||
if ((Test-Path $OutFile) -and ((Get-Item $OutFile).Length -gt 0)) {
|
||||
return $true
|
||||
}
|
||||
} catch {
|
||||
$ProgressPreference = $prevProgress
|
||||
}
|
||||
|
||||
if (Test-Path $OutFile) {
|
||||
Remove-Item -Path $OutFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Install-PortablePlatformTools {
|
||||
$ptDir = "$env:LOCALAPPDATA\Programs\platform-tools"
|
||||
if (Test-Path "$ptDir\adb.exe") {
|
||||
@@ -258,38 +162,37 @@ function Install-PortableScrcpy {
|
||||
}
|
||||
|
||||
Write-Host (Get-LaunchText " [INFO] Installing portable scrcpy in user space..." " [安装] 正在安装 scrcpy(当前用户)...") -ForegroundColor Cyan
|
||||
if ($ArtemisDownloads.mode -eq 'China' -and $ArtemisDownloads.scrcpy_url.StartsWith('https://github.com/')) {
|
||||
Write-Host (Get-LaunchText ' [WARN] China mode has no non-GitHub scrcpy source. Configure overrides.scrcpy_url or install scrcpy manually.' ' [提示] 国内镜像中没有可用的 scrcpy 下载地址;请设置 config/downloads.json 的 overrides.scrcpy_url,或自行安装。') -ForegroundColor Yellow
|
||||
if ($ArtemisDownloads.scrcpy_urls.Count -eq 0) {
|
||||
Write-Host (Get-LaunchText ' [WARN] No scrcpy download source is configured. Set overrides.scrcpy_url.' ' [提示] 未配置可用的 scrcpy 下载源;请设置 overrides.scrcpy_url。') -ForegroundColor Yellow
|
||||
return $false
|
||||
}
|
||||
$work = Join-Path ([IO.Path]::GetTempPath()) ('artemis-scrcpy-' + [Guid]::NewGuid().ToString('N'))
|
||||
New-Item -ItemType Directory -Path $work -ErrorAction Stop | Out-Null
|
||||
try {
|
||||
$zipPath = "$env:TEMP\scrcpy-win64.zip"
|
||||
if (Invoke-DownloadFile -Uri $ArtemisDownloads.scrcpy_url -OutFile $zipPath) {
|
||||
$actualHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($actualHash -ne $ArtemisDownloads.scrcpy_sha256) {
|
||||
throw 'Downloaded scrcpy ZIP failed official SHA256 verification.'
|
||||
}
|
||||
$extractDir = "$env:TEMP\scrcpy_extract"
|
||||
if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
|
||||
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
|
||||
$zipPath = Join-Path $work 'scrcpy.zip'
|
||||
if (Invoke-ArtemisVerifiedDownload -Urls $ArtemisDownloads.scrcpy_urls -OutFile $zipPath -Sha256 $ArtemisDownloads.scrcpy_sha256) {
|
||||
$extractDir = Join-Path $work 'extracted'
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $extractDir)
|
||||
$extractedFolder = Get-ChildItem -Path $extractDir -Directory | Select-Object -First 1
|
||||
if ($extractedFolder) {
|
||||
if (-not (Test-Path $scrcpyDir)) {
|
||||
New-Item -ItemType Directory -Path $scrcpyDir -Force | Out-Null
|
||||
}
|
||||
Copy-Item -Path "$($extractedFolder.FullName)\*" -Destination $scrcpyDir -Recurse -Force
|
||||
}
|
||||
Remove-Item $zipPath -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path "$scrcpyDir\scrcpy.exe") {
|
||||
$env:PATH = "$scrcpyDir;$env:PATH"
|
||||
Write-Host (Get-LaunchText " [OK] Portable scrcpy installed in user space." " [完成] scrcpy 已安装。") -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
$contentDir = if (Test-Path -LiteralPath (Join-Path $extractDir 'scrcpy.exe')) { $extractDir }
|
||||
elseif ($extractedFolder) { $extractedFolder.FullName }
|
||||
else { throw 'scrcpy ZIP contains no application directory.' }
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $contentDir 'scrcpy.exe'))) { throw 'scrcpy ZIP contains no scrcpy.exe.' }
|
||||
New-Item -ItemType Directory -Path $scrcpyDir -Force -ErrorAction Stop | Out-Null
|
||||
Get-ChildItem -LiteralPath $contentDir | Copy-Item -Destination $scrcpyDir -Recurse -Force -ErrorAction Stop
|
||||
$env:PATH = "$scrcpyDir;$env:PATH"
|
||||
Write-Host (Get-LaunchText " [OK] Portable scrcpy installed in user space." " [完成] scrcpy 已安装。") -ForegroundColor Green
|
||||
return $true
|
||||
}
|
||||
} catch {
|
||||
Write-Host (Get-LaunchText " [WARN] Failed to install portable scrcpy: $_" " [提示] scrcpy 安装失败:$_") -ForegroundColor DarkYellow
|
||||
} finally {
|
||||
$fullWork = [IO.Path]::GetFullPath($work)
|
||||
$tempPrefix = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\'
|
||||
if ($fullWork.StartsWith($tempPrefix, [StringComparison]::OrdinalIgnoreCase) -and (Split-Path $fullWork -Leaf) -like 'artemis-scrcpy-*') {
|
||||
Remove-Item -LiteralPath $fullWork -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
@@ -446,7 +349,7 @@ if ($missingCore.Count -gt 0) {
|
||||
}
|
||||
if (-not (Test-CommandExists "scrcpy")) {
|
||||
if (-not (Install-PortableScrcpy)) {
|
||||
Write-Host (Get-LaunchText ' [WARN] scrcpy download failed. Set overrides.scrcpy_url in config/downloads.json to a reachable release ZIP, then retry.' ' [提示] scrcpy 下载失败。请在 config/downloads.json 中设置可访问的 overrides.scrcpy_url,然后重试。') -ForegroundColor Yellow
|
||||
Write-Host (Get-LaunchText ' [WARN] All scrcpy sources failed. Check the HTTP/timeout/checksum error above, or configure overrides.scrcpy_url and scrcpy_fallback_urls in config/downloads.json.' ' [提示] scrcpy 下载源均未成功;请查看上方 HTTP、超时或校验错误,或在 config/downloads.json 中设置 overrides.scrcpy_url 与备用地址 scrcpy_fallback_urls。') -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -11,6 +11,7 @@ $env:PYTHONUTF8 = '1'
|
||||
$env:PYTHONIOENCODING = 'utf-8'
|
||||
if ($Help) {
|
||||
Write-Host '用法:update.bat 或 update-cn.bat [-Help]'
|
||||
Write-Host '未运行过启动脚本也可升级;缺少的 Python 环境会自动准备。'
|
||||
Write-Host '升级会终止本项目的 ARTEMIS 服务、MCP 和任务进程;保留配置、密钥与历史资料。'
|
||||
Write-Host '随后快进更新源码,同步依赖、网页构建和 Codex MCP;完成后请重新启动并重载 MCP。'
|
||||
exit 0
|
||||
@@ -33,7 +34,7 @@ if (-not $Staged) {
|
||||
if ($fullStage.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) { throw 'TEMP must be outside the project directory.' }
|
||||
New-Item -ItemType Directory -Path $stage | Out-Null
|
||||
$stageCreated = $true
|
||||
foreach ($name in @('upgrade.ps1', 'installation_lock.ps1', 'upgrade_processes.ps1', 'update_installation.py')) {
|
||||
foreach ($name in @('upgrade.ps1', 'installation_lock.ps1', 'upgrade_processes.ps1', 'update_installation.py', 'windows_tools.ps1', 'download_config.ps1', 'bootstrap_python.ps1')) {
|
||||
Copy-Item -LiteralPath (Join-Path $PSScriptRoot $name) -Destination (Join-Path $stage $name)
|
||||
}
|
||||
$forward = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', (Join-Path $stage 'upgrade.ps1'), '-Staged', '-ProjectRoot', $ProjectRoot)
|
||||
@@ -47,13 +48,15 @@ if (-not $Staged) {
|
||||
if ($stageCreated -and (Test-Path -LiteralPath $stage)) { Remove-Item -LiteralPath $stage -Recurse -Force }
|
||||
Exit-ArtemisInstallationLock -Mutex $lock
|
||||
}
|
||||
if ($code -ne 0 -and $env:ARTEMIS_NO_PAUSE -ne '1' -and -not [Console]::IsInputRedirected) {
|
||||
try { Read-Host '[升级失败] 请查看上方原因。按回车键关闭窗口' | Out-Null } catch {}
|
||||
}
|
||||
exit $code
|
||||
}
|
||||
|
||||
try {
|
||||
Set-Location -LiteralPath $PSScriptRoot
|
||||
$python = Join-Path $ProjectRoot '.venv\Scripts\python.exe'
|
||||
if (-not (Test-Path -LiteralPath $python)) { throw '尚未安装项目 Python 环境;请先运行 start.bat 或 start-cn.bat。' }
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) { throw '未安装 Git,无法升级。' }
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $ProjectRoot '.git'))) { throw '此目录不含 Git 信息;请改用 Git 克隆或新版发布源码包。' }
|
||||
# Refuse dirty checkouts before terminating the user's running processes.
|
||||
@@ -64,6 +67,15 @@ try {
|
||||
Write-Host 'ARTEMIS 独立升级:将终止当前项目的服务、MCP 及任务。' -ForegroundColor Yellow
|
||||
. (Join-Path $PSScriptRoot 'upgrade_processes.ps1')
|
||||
Stop-ArtemisUpgradeProcesses -ProjectRoot $ProjectRoot
|
||||
if (-not (Test-Path -LiteralPath $python)) {
|
||||
Write-Host '[首次准备] 未找到项目 Python 环境,正在自动安装;随后拉取源码并同步新版依赖。' -ForegroundColor Cyan
|
||||
. (Join-Path $PSScriptRoot 'windows_tools.ps1')
|
||||
. (Join-Path $PSScriptRoot 'download_config.ps1')
|
||||
. (Join-Path $PSScriptRoot 'bootstrap_python.ps1')
|
||||
Initialize-ArtemisDownloads -ProjectRoot $ProjectRoot -Mode $Mirror
|
||||
Update-EnvironmentPath
|
||||
$python = Initialize-ArtemisPython -ProjectRoot $ProjectRoot -RuntimeOnly
|
||||
}
|
||||
& $python (Join-Path $PSScriptRoot 'update_installation.py') source --root $ProjectRoot
|
||||
if ($LASTEXITCODE -notin @(0, 10)) { throw '源码升级未完成;配置和历史资料已保留。' }
|
||||
Stop-ArtemisUpgradeProcesses -ProjectRoot $ProjectRoot
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Process-local Windows tool discovery shared by startup and the frozen upgrader.
|
||||
# Keep UTF-8 BOM for Windows PowerShell 5.1.
|
||||
|
||||
function Test-CommandExists {
|
||||
param([string]$Command)
|
||||
$res = Get-Command $Command -ErrorAction SilentlyContinue
|
||||
return ($null -ne $res)
|
||||
}
|
||||
|
||||
function Update-EnvironmentPath {
|
||||
$standardDirs = @(
|
||||
"$env:LOCALAPPDATA\Microsoft\WinGet\Links",
|
||||
"$env:LOCALAPPDATA\Programs\node",
|
||||
"$env:USERPROFILE\.local\share\node",
|
||||
"$env:LOCALAPPDATA\Programs\scrcpy",
|
||||
"$env:USERPROFILE\.local\share\scrcpy",
|
||||
"$env:LOCALAPPDATA\Programs\platform-tools",
|
||||
"$env:USERPROFILE\.local\share\platform-tools",
|
||||
"$env:LOCALAPPDATA\Programs\uv",
|
||||
"$env:LOCALAPPDATA\uv",
|
||||
"$env:APPDATA\uv",
|
||||
"$env:USERPROFILE\.cargo\bin",
|
||||
"$env:USERPROFILE\.local\bin",
|
||||
"$env:USERPROFILE\scoop\shims",
|
||||
"C:\ProgramData\chocolatey\bin",
|
||||
"$env:LOCALAPPDATA\Android\Sdk\platform-tools",
|
||||
"$env:LOCALAPPDATA\Android\android-sdk\platform-tools",
|
||||
"$env:ProgramFiles\Android\platform-tools",
|
||||
"${env:ProgramFiles(x86)}\Android\android-sdk\platform-tools",
|
||||
"$env:ProgramFiles\nodejs",
|
||||
"${env:ProgramFiles(x86)}\nodejs",
|
||||
"$env:APPDATA\npm"
|
||||
)
|
||||
if ($env:CARGO_HOME) { $standardDirs = @("$env:CARGO_HOME\bin") + $standardDirs }
|
||||
if ($env:ANDROID_HOME) { $standardDirs += "$env:ANDROID_HOME\platform-tools" }
|
||||
if ($env:ANDROID_SDK_ROOT) { $standardDirs += "$env:ANDROID_SDK_ROOT\platform-tools" }
|
||||
if ($env:NVM_SYMLINK) { $standardDirs = @($env:NVM_SYMLINK) + $standardDirs }
|
||||
if ($env:NVM_HOME) { $standardDirs = @($env:NVM_HOME) + $standardDirs }
|
||||
|
||||
$regPath = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
$currentPaths = ($env:PATH -split ";") + ($regPath -split ";") + $standardDirs | Where-Object { [string]::IsNullOrWhiteSpace($_) -eq $false -and (Test-Path $_) } | Select-Object -Unique
|
||||
$env:PATH = $currentPaths -join ";"
|
||||
|
||||
# Ensure user-space portable tools take absolute priority over system-level outdated versions
|
||||
$portableNode = "$env:LOCALAPPDATA\Programs\node"
|
||||
if (Test-Path "$portableNode\node.exe") {
|
||||
$env:PATH = "$portableNode;$env:PATH"
|
||||
}
|
||||
$portableScrcpy = "$env:LOCALAPPDATA\Programs\scrcpy"
|
||||
if (Test-Path "$portableScrcpy\scrcpy.exe") {
|
||||
$env:PATH = "$portableScrcpy;$env:PATH"
|
||||
}
|
||||
$portablePt = "$env:LOCALAPPDATA\Programs\platform-tools"
|
||||
if (Test-Path "$portablePt\adb.exe") {
|
||||
$env:PATH = "$portablePt;$env:PATH"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user