install.ps1 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. # 观澜·如东样板 v2 — Windows 离线安装
  2. # 用法: powershell -ExecutionPolicy Bypass -File install.ps1
  3. # ... -File install.ps1 -Python "C:\Python312\python.exe" # 多版本机器指定
  4. # ... -File install.ps1 -UseBundledPython # 强制用包内便携运行时
  5. #
  6. # 编码约定 (2026-09-10, 同事 xzy 实机报 ParserError 后定): 本文件必须存成 **UTF-8 带 BOM + CRLF**。
  7. # Windows PowerShell 5.1 对没有 BOM 的 .ps1 按 ANSI 代码页 (中文机 = GBK) 解码, 文件里的中文会被
  8. # 错读, 相邻的转义反引号可能被一起吞掉 → 引号不配对 → 级联 ParserError (报在看起来没问题的行上)。
  9. # install.bat 里的 chcp 65001 管不了这件事 (它改的是控制台代码页, 不是脚本文件的解码方式)。
  10. # 另: 本文件**不再使用** 形如 & cmd /c "\"$exe\" -m pip ..." 的嵌套引号写法 —— 一旦有字节被吃掉就崩; 改用
  11. # PowerShell 原生调用 & $exe arg1 arg2, 不需要任何引号转义。
  12. param([string]$Python = "", [switch]$UseBundledPython, [switch]$SkipOllama)
  13. $ErrorActionPreference = "Stop"; $env:PYTHONUTF8 = "1"
  14. # 机器上若全局设了 PYTHONPATH (例如指向 D:\Program Files\Python\Lib\site-packages), pip 会把那里的包
  15. # 当成 "已满足", 于是不装进 .venv —— venv 里就缺 urllib3 / polars / pyyaml / jinja2 等传递依赖,
  16. # 换个没设 PYTHONPATH 的 shell 立刻 import 失败; 且 PYTHONPATH 排在 venv 之前会顶掉 pin 住的版本。
  17. # 先把环境变量摘掉, 让下面 pip 老老实实按 requirements.txt 装进 .venv。
  18. Remove-Item Env:PYTHONPATH -ErrorAction SilentlyContinue
  19. Set-Location $PSScriptRoot
  20. function Say($m){ Write-Host $m }
  21. Say "== 1/5 选 Python (需 3.11+; 本项目依赖 pandas 3, 3.10 及以下没有轮子)"
  22. $py = $null
  23. if ($Python) { $py = $Python }
  24. elseif (-not $UseBundledPython) {
  25. # 列出机器上所有 Python, 选第一个 >=3.11 (多版本共存时不猜, 打印出来让人看见)
  26. $cands = @()
  27. foreach ($c in @("py -3.13","py -3.12","py -3.11","python","python3")) {
  28. $exe, $arg = $c.Split(" ", 2)
  29. try {
  30. $v = if ($arg) { & $exe $arg -c "import sys;print('%d.%d'%sys.version_info[:2])" 2>$null }
  31. else { & $exe -c "import sys;print('%d.%d'%sys.version_info[:2])" 2>$null }
  32. if ($LASTEXITCODE -eq 0 -and $v) { $cands += [pscustomobject]@{cmd=$c; exe=$exe; arg=$arg; ver=$v} }
  33. } catch {}
  34. }
  35. if ($cands) { Say (" 机器上的 Python: " + (($cands | ForEach-Object { "$($_.cmd)=$($_.ver)" }) -join ", ")) }
  36. $ok = $cands | Where-Object { [version]$_.ver -ge [version]"3.11" } | Select-Object -First 1
  37. if ($ok) { $py = $ok }
  38. }
  39. if (-not $py) {
  40. $rt = Get-ChildItem "vendor\python\cpython-3.12*windows*.tar.gz" -ErrorAction SilentlyContinue | Select-Object -First 1
  41. if ($rt) {
  42. Say " 系统无 3.11+, 解包内便携运行时: $($rt.Name)"
  43. if (Test-Path runtime) { Remove-Item runtime -Recurse -Force }
  44. New-Item -ItemType Directory runtime | Out-Null
  45. tar -xzf $rt.FullName -C runtime # Windows 10 1803+ 自带 tar
  46. $py = [pscustomobject]@{ exe = (Resolve-Path "runtime\python\python.exe").Path; arg = "" }
  47. } else {
  48. Say " X 没有 3.11+ 的 Python, 包内也没有便携运行时。"
  49. Say " 办法一: 装 https://www.python.org/downloads/windows/ 的 3.12.x (勾 Add to PATH)"
  50. Say " 办法二: -Python C:\路径\python.exe 指定"
  51. exit 2
  52. }
  53. } elseif ($py -is [string]) {
  54. $py = [pscustomobject]@{ exe = $py; arg = "" }
  55. }
  56. Say " 使用: $($py.exe) $($py.arg)"
  57. Say "== 2/5 建虚拟环境 .venv"
  58. if ($py.arg) { & $py.exe $py.arg -m venv .venv } else { & $py.exe -m venv .venv }
  59. $vpy = (Resolve-Path ".\.venv\Scripts\python.exe").Path
  60. & $vpy -m pip install --upgrade pip --quiet
  61. Say "== 3/5 装依赖 (优先包内离线轮子 wheels\win_amd64)"
  62. if (Test-Path ".\wheels\win_amd64") { & $vpy -m pip install --no-index --find-links .\wheels\win_amd64 -r requirements.txt }
  63. else { Say " 包内无轮子, 改为联网安装"; & $vpy -m pip install -r requirements.txt }
  64. if ($LASTEXITCODE -ne 0) { Say " X 依赖安装失败 (上面有 pip 的报错) —— 先解决它, 别接着跑 start.bat"; exit 3 }
  65. Say "== 4/5 写配置"
  66. # 别改回 Get-Content / Set-Content -Encoding UTF8 (2026-09-10 实机踩过):
  67. # PS 5.1 的 Get-Content 把无 BOM 的 UTF-8 当 ANSI(GBK) 解码 -> 中文注释变乱码;
  68. # Set-Content -Encoding UTF8 又写出 BOM -> Python 侧用裸 utf-8 json.loads 直接 JSONDecodeError。
  69. # 交给 venv 里的 Python 读写: 读 utf-8-sig (有 BOM 也认), 写无 BOM 的 UTF-8。
  70. # 下面 Python 代码故意只用单引号, 避开 PS 5.1 传参时的引号转义坑。
  71. $cfgFix = @'
  72. import json, pathlib, sys
  73. p = pathlib.Path('configs/serve.json')
  74. c = json.loads(p.read_text(encoding='utf-8-sig'))
  75. c['python'] = sys.executable
  76. p.write_text(json.dumps(c, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  77. print(' configs/serve.json -> ' + sys.executable)
  78. '@
  79. & $vpy -c $cfgFix
  80. if ($LASTEXITCODE -ne 0) { Say " X 写 configs\serve.json 失败"; exit 4 }
  81. Say "== 5/5 Ollama (只影响问答与本地审核; 其余页面不受影响)"
  82. if (-not $SkipOllama) {
  83. $has = Get-Command ollama -ErrorAction SilentlyContinue
  84. if ($has) { Say " 已安装: $(& ollama -v 2>&1 | Select-Object -First 1)" }
  85. elseif (Test-Path "vendor\ollama\OllamaSetup.exe") {
  86. Say " 包内有离线安装包, 现在静默安装 (约 1.5 GB, 需几分钟)…"
  87. Start-Process -FilePath "vendor\ollama\OllamaSetup.exe" -ArgumentList "/SILENT" -Wait
  88. Say " 安装完成; 模型请把有网机器的 %USERPROFILE%\.ollama\models 整个目录拷到本机同位置"
  89. } else { Say " 未装且包内无离线包: 问答不可用。装法见 说明书 §5" }
  90. }
  91. Say ""
  92. & $vpy guanlan.py check
  93. Say ""
  94. Say "安装完成. 启动: start.bat 停止: stop.bat 自检: check.bat 入口: http://127.0.0.1:28084/"