# Agent for Science —— 内网安装(Windows,不经浏览器,不弹 SmartScreen) # # irm <发布目录>/install.ps1 | iex # # ## 为什么这条路不弹「Windows 已保护你的电脑」 # # SmartScreen 拦的是带「网络来源标记」(Zone.Identifier 这条 ADS,俗称 MOTW)的 # 文件,而那个标记是浏览器 / 邮件客户端在保存文件时打上去的,PowerShell 的 # Invoke-WebRequest 不打。所以经这条路装的应用,系统根本不会去问它签没签名 —— # 这不是绕过,是 Windows 自己划的线:文件从哪来由谁负责。内网发布目录由我们自己 # 负责,就像从共享盘拷过来一样。(macOS 那边同一个道理,只是标记叫 quarantine。) # # 我们在装之前仍然会 Unblock-File 一次。理由:万一有人先用浏览器下了 Setup.exe, # 再手动跑这个脚本,那个文件是带标记的 —— 那时用户看到的就是 SmartScreen 拦截页。 # # ## 它做什么 # # 下载 Setup.exe → 用发布目录里的 SHA256SUMS 核对 → 去掉网络来源标记 → 运行安装器 # (自解压到 %LOCALAPPDATA%\Programs\AgentForScience 并启动)。每一步失败就停, # 不留半个应用。 # # 环境变量:AFS_RELEASE_URL(发布目录)、AFS_AUTH(私有仓库,"user:token",只读 # token 即可)、AFS_DRY_RUN=1(只打印要做什么,不做)、AFS_NO_LAUNCH=1(只装不起)。 $ErrorActionPreference = 'Stop' # 控制台默认代码页(cp936/cp1252)打不出这个脚本里的中文,会变成一片 `?`。装东西时 # 第一眼看到乱码,人会以为装坏了 —— 所以先把输出编码扳成 UTF-8。 # (同打包器 #873 那处 `sys.stdout.reconfigure(encoding="utf-8")`,一个道理。) # 这个脚本本身必须存成 **带 BOM 的 UTF-8**:Windows PowerShell 5.1 没 BOM 就按系统 # ANSI 代码页读 .ps1,中文会被读坏、连语法都能崩(真机实测过,就是这么发现的)。 try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 } catch {} # 下面两行由打包器整行替换(占位符只能出现在这里,别处不许提它 —— macOS 那版第一次 # 就栽在把占位符也写进了判断的模式里,替换后要么语法错、要么把正确地址当成"没填"拒掉)。 $DefaultUrl = 'https://desktop-9el2944.taile9f15e.ts.net/wangd/sciencemate/releases/download/v0.4.0' $DefaultExe = 'AgentForScience-Setup.exe' $Base = if ($env:AFS_RELEASE_URL) { $env:AFS_RELEASE_URL } else { $DefaultUrl } $Exe = $DefaultExe $Dry = [bool]$env:AFS_DRY_RUN $Auth = $env:AFS_AUTH function Say([string]$m) { Write-Host $m } function Die([string]$m) { Write-Host "✗ $m" -ForegroundColor Red; exit 1 } if (-not $Base) { Die "没有发布目录地址。打包时传 --release-url,或运行前设 `$env:AFS_RELEASE_URL='https://<内网主机>/...'" } if ($env:OS -ne 'Windows_NT') { Die "这个脚本只装 Windows;macOS 用 install.sh" } $Base = $Base.TrimEnd('/') $Work = Join-Path ([IO.Path]::GetTempPath()) ("afs-install-" + [Guid]::NewGuid().ToString('N').Substring(0,8)) New-Item -ItemType Directory -Path $Work -Force | Out-Null $Target = Join-Path $Work $Exe $Sums = Join-Path $Work 'SHA256SUMS' # 私有 Forgejo 的 release 资产要登录才下得到:AFS_AUTH="user:token"(对应 curl -u)。 $Headers = @{} if ($Auth) { $Headers['Authorization'] = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($Auth)) } try { Say "① 下载 $Exe(Invoke-WebRequest,不会打网络来源标记)" if ($Dry) { Say " `$ Invoke-WebRequest $Base/$Exe -OutFile $Target" Say " `$ Invoke-WebRequest $Base/SHA256SUMS -OutFile $Sums" } else { Invoke-WebRequest -Uri "$Base/$Exe" -OutFile $Target -Headers $Headers -UseBasicParsing Invoke-WebRequest -Uri "$Base/SHA256SUMS" -OutFile $Sums -Headers $Headers -UseBasicParsing } Say "② 核对 SHA-256" if ($Dry) { Say " `$ Get-FileHash -Algorithm SHA256(对 SHA256SUMS 里 $Exe 那一行)" } else { # SHA256SUMS 是 sha256sum 的格式: $line = Get-Content $Sums | Where-Object { $_ -match ("\s+" + [regex]::Escape($Exe) + "\s*$") } | Select-Object -First 1 if (-not $line) { Die "SHA256SUMS 里没有 $Exe 这一行" } $expected = ($line -split '\s+')[0].ToLower() $actual = (Get-FileHash -Algorithm SHA256 -Path $Target).Hash.ToLower() if ($expected -ne $actual) { Die "校验和不对:发布目录说 $expected,下载到的是 $actual —— 不装" } Say " ✓ $actual" } Say "③ 去掉网络来源标记(若有)" if ($Dry) { Say " `$ Unblock-File $Target" } else { Unblock-File -Path $Target -ErrorAction SilentlyContinue } Say "④ 运行安装器(自解压到 %LOCALAPPDATA%\Programs\AgentForScience)" # 注意:不能叫 $args —— 那是 PowerShell 的自动变量,赋值行为不由我们说了算。 $exeArgs = @() if ($env:AFS_NO_LAUNCH) { $exeArgs += '--no-launch' } if ($Dry) { Say (" `$ " + $Target + " " + ($exeArgs -join ' ')) } else { $p = if ($exeArgs.Count) { Start-Process -FilePath $Target -ArgumentList $exeArgs -Wait -PassThru } else { Start-Process -FilePath $Target -Wait -PassThru } if ($p.ExitCode -ne 0) { Die "安装器退出码 $($p.ExitCode) —— 没装成" } } $Installed = Join-Path $env:LOCALAPPDATA 'Programs\AgentForScience' Say "" Say "✅ 装好了:$Installed" Say " 以后直接双击 $Installed\AfsShell.exe 即可(那个控制台窗口别关,关了后端就一起收摊)。" } finally { Remove-Item -Recurse -Force $Work -ErrorAction SilentlyContinue }