Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### 修复

- Windows 安装包解压 openXYOS 时修正 `extract-openxyos.cmd` 的 `cmd.exe` 标签(`:liveNodeOk`)。原先写成 `liveNodeOk:`,goto 找不到标签会以退出码 1 结束;文件已齐备时 livez 在 90 秒内未就绪改为警告并继续安装,不再整包 Abort。`.install-ready` 在 layout 通过后、livez 探测前写入,HKCU Run / 登录任务保留,组织页可随后拉起边车。
- 安装期不再在 livez 探测后 `taskkill` 边车:Setup 把 `start-sidecar.ps1` 写到 `%LOCALAPPDATA%\\FreeOS\\openxyos`,用当前用户(IShellDispatch2 / HKCU Run / 登录计划任务)常驻拉起 FE+BE,确认 `http://127.0.0.1:3780/api/health/livez` 后保持运行。组织页在 `.install-ready` 时自动嵌入本机 openXYOS,不再把「启动边车」当主按钮;仅自动启动失败才显示「重试启动」和中文原因。
- 「下载最新 openXYOS 源码」端到端 UTF-8:Wails 文件夹选择器以 UTF-8 Base64 回传路径(不再把 GBK/ACP 当 UTF-8);落盘前修复 CP1252/Latin-1 误读的目标路径;解压按 zip UTF-8 标志或 GBK 还原中文目录名,避免乱码。
- Windows 知识库本地文件夹挂接:选择器与列表预览按 UTF-8 处理中文路径/文件名,避免 `项目…` 这类乱码;挂接与列表预览不再因向量模型未就绪返回 409。
Expand Down
20 changes: 11 additions & 9 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ Same precedence as the FreeOS CLI/server:
else `{home}/openxyos`. The NSIS installer expands a prebuilt
`openxyos-runtime.zip` **directly into that live workdir** during Setup
(and keeps `$INSTDIR\openxyos` / `$INSTDIR\openxyos-runtime` as backups).
Install fails if `node` or `dist` is missing, or if
`http://127.0.0.1:3780/api/health/livez` does not pass before Setup exits.
Setup then leaves a **user-level** sidecar running (HKCU Run
`FreeOS-openXYOS` + `start-sidecar.ps1`) so Organization embeds immediately
— no first-open copy and no 「启动边车」 click. First FreeOS start only
attaches to that live tree if the port is already healthy.
Install fails if `node` or `dist` is missing. If files are in place but
`http://127.0.0.1:3780/api/health/livez` is still down after the install-time
wait, Setup warns and continues — HKCU Run / the logon task stay registered
so Organization can attach later. Setup then leaves a **user-level** sidecar
running (HKCU Run `FreeOS-openXYOS` + `start-sidecar.ps1`) so Organization
embeds immediately when livez is already up — no first-open copy and no
「启动边车」 click. First FreeOS start only attaches to that live tree if
the port is already healthy.
- Shell prefs → `{home}/desktop-settings.json`

## Windows install finish
Expand All @@ -48,9 +50,9 @@ also copies `openxyos-runtime.zip` (prebuilt Node + openXYOS frontend/backend)
and extracts it with Windows `tar.exe` (quoted paths, so `Program Files`
works) into the **live** workdir `%LOCALAPPDATA%\FreeOS\openxyos` during
Setup. `$INSTDIR\openxyos` and `$INSTDIR\openxyos-runtime` are sealed
backups only. Setup **aborts** if `node\node.exe` or `dist\index.html` is
missing, or if the user-level sidecar cannot reach
`/api/health/livez` — a README-only tree is not a successful install.
backups only. Setup **aborts** only if `node\node.exe` or `dist\index.html` is
missing — a README-only tree is not a successful install. A livez
timeout after those files exist is a warning, not an abort.
The probe **does not** start Node as Administrator and **does not**
`taskkill` it afterwards (that left Organization asking to 启动边车).
PersistOpenXYOS launches `start-sidecar.ps1` with the explorer token,
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/build/windows/nsis/project.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ LangString OPENXYOS_PROBE ${LANG_SIMPCHINESE} "正在以当前用户启动本机
LangString OPENXYOS_PROBE ${LANG_ENGLISH} "Starting local openXYOS as the current user and checking http://127.0.0.1:3780/api/health/livez"
LangString OPENXYOS_PROBE_OK ${LANG_SIMPCHINESE} "openXYOS 已在本机运行,并已登记用户级开机自启"
LangString OPENXYOS_PROBE_OK ${LANG_ENGLISH} "openXYOS is running locally and registered for user-level auto-start"
LangString OPENXYOS_PROBE_FAIL ${LANG_SIMPCHINESE} "安装失败:已解压 openXYOS,但 http://127.0.0.1:3780/api/health/livez 未就绪。请检查 3780 端口占用后重试安装。"
LangString OPENXYOS_PROBE_FAIL ${LANG_ENGLISH} "Setup failed: openXYOS is on disk but http://127.0.0.1:3780/api/health/livez did not become ready. Free port 3780 and run Setup again."
LangString OPENXYOS_PROBE_WARN ${LANG_SIMPCHINESE} "openXYOS 文件已就绪,但 http://127.0.0.1:3780/api/health/livez 暂未响应。安装将继续;登录后会自动拉起本机服务,也可稍后在组织页重试。"
LangString OPENXYOS_PROBE_WARN ${LANG_ENGLISH} "openXYOS files are in place, but http://127.0.0.1:3780/api/health/livez is not ready yet. Setup will continue; the service auto-starts at logon, or retry from Organization later."
LangString UN_FREEOS_RUNNING ${LANG_SIMPCHINESE} "检测到 FreeOS 仍在运行(主程序、主机或组织侧车)。$\r$\n$\r$\n继续将结束这些进程,并删除安装目录中的程序文件。$\r$\n用户数据(%USERPROFILE%\.freeos)会保留。$\r$\n$\r$\n要继续卸载吗?"
LangString UN_FREEOS_RUNNING ${LANG_ENGLISH} "FreeOS is still running (shell, host, or organization sidecar).$\r$\n$\r$\nContinuing will stop those processes and remove program files from the install folder.$\r$\nUser data (%USERPROFILE%\.freeos) is kept.$\r$\n$\r$\nContinue uninstall?"

Expand Down
31 changes: 17 additions & 14 deletions desktop/src/build/windows/nsis/wails_tools.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,12 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
FileWrite $0 `if errorlevel 1 exit /b 8$\r$\n`
FileWrite $0 `if exist "%LIVE%\node\node.exe" goto liveNodeOk$\r$\n`
FileWrite $0 `if exist "$INSTDIR\openxyos-runtime\node\node.exe" xcopy /E /I /Y "$INSTDIR\openxyos-runtime\*" "%LIVE%\" >nul$\r$\n`
FileWrite $0 `liveNodeOk:$\r$\n`
# cmd.exe labels require a leading colon. "liveNodeOk:" is not a goto target.
FileWrite $0 `:liveNodeOk$\r$\n`
FileWrite $0 `if not exist "%LIVE%\node\node.exe" exit /b 6$\r$\n`
FileWrite $0 `if not exist "%LIVE%\openxyos\dist\index.html" if not exist "%LIVE%\dist\index.html" exit /b 7$\r$\n`
FileWrite $0 `if defined USERNAME icacls "$R6\FreeOS" /grant "%USERNAME%:(OI)(CI)M" /T /C /Q >nul 2>&1$\r$\n`
FileWrite $0 `exit /b 0$\r$\n`
FileClose $0
nsExec::ExecToLog '"$PLUGINSDIR\extract-openxyos.cmd"'
Pop $0
Expand All @@ -208,12 +210,14 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
FileClose $0
!insertmacro wails.writeOpenXYOSAutostart
Call PersistOpenXYOS
!insertmacro wails.probeOpenXYOS
# Stamp after layout + autostart, before livez wait. Probe must not Abort
# when node/dist exist; keep this write reachable on a slow sidecar.
FileOpen $0 "$R6\FreeOS\openxyos\.install-ready" w
FileWrite $0 "live=$R6\FreeOS\openxyos$\r$\n"
FileWrite $0 "url=http://127.0.0.1:3780/api/health/livez$\r$\n"
FileWrite $0 "autostart=hkcu-run:FreeOS-openXYOS$\r$\n"
FileClose $0
!insertmacro wails.probeOpenXYOS
SetDetailsPrint listonly
!macroend

Expand Down Expand Up @@ -323,30 +327,29 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"

# Wait for the user-level sidecar PersistOpenXYOS launched. Do not spawn
# Node as admin and do not kill the probe process — that was the #29 gap.
# livez timeout is a warning only: node + dist already passed
# requireOpenXYOSLayout, and HKCU Run / the LIMITED task stay registered.
!macro wails.probeOpenXYOS
DetailPrint "$(OPENXYOS_PROBE)"
FileOpen $0 "$PLUGINSDIR\wait-openxyos.ps1" w
FileWrite $0 `$$livez = 'http://127.0.0.1:3780/api/health/livez'$\r$\n`
FileWrite $0 `function Test-Livez { try { $$r = Invoke-WebRequest -UseBasicParsing -TimeoutSec 2 -Uri $$livez; return ($$r.StatusCode -lt 500) } catch { return $$false } }$\r$\n`
FileWrite $0 `for ($$i = 0; $$i -lt 60; $$i++) { if (Test-Livez) { exit 0 }; Start-Sleep -Seconds 1 }$\r$\n`
FileWrite $0 `for ($$i = 0; $$i -lt 90; $$i++) { if (Test-Livez) { exit 0 }; if ($$i -eq 30) { schtasks.exe /Run /TN "FreeOS-openXYOS" | Out-Null }; Start-Sleep -Seconds 1 }$\r$\n`
FileWrite $0 `exit 12$\r$\n`
FileClose $0
nsExec::ExecToLog '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File "$PLUGINSDIR\wait-openxyos.ps1"'
Pop $0
DetailPrint "$(OPENXYOS_EXTRACT_CODE)$0"
IntCmp $0 0 openxyosProbeOk openxyosProbeFail openxyosProbeFail
openxyosProbeFail:
DetailPrint "$(OPENXYOS_PROBE_FAIL)"
IfSilent openxyosProbeSilent openxyosProbeLoud
openxyosProbeSilent:
SetErrorLevel 68
Abort
openxyosProbeLoud:
MessageBox MB_OK|MB_ICONSTOP "$(OPENXYOS_PROBE_FAIL)"
SetErrorLevel 68
Abort
IntCmp $0 0 openxyosProbeOk openxyosProbeWarn openxyosProbeWarn
openxyosProbeWarn:
DetailPrint "$(OPENXYOS_PROBE_WARN)"
IfSilent openxyosProbeDone openxyosProbeNote
openxyosProbeNote:
MessageBox MB_OK|MB_ICONINFORMATION "$(OPENXYOS_PROBE_WARN)"
Goto openxyosProbeDone
openxyosProbeOk:
DetailPrint "$(OPENXYOS_PROBE_OK)"
openxyosProbeDone:
!macroend

!macro wails.writeUninstaller
Expand Down
47 changes: 44 additions & 3 deletions tests/unit/desktop/test_nsis_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,14 @@ def test_nsis_extracts_runtime_with_quoted_paths_and_aborts_if_incomplete() -> N
assert "wait-openxyos.ps1" in nsh
assert "probe-openxyos.ps1" not in nsh
assert "/api/health/livez" in nsh
assert "SetErrorLevel 68" in nsh
assert "LangString OPENXYOS_PROBE_FAIL ${LANG_SIMPCHINESE}" in nsi
assert ".install-ready" in nsh
assert "LangString OPENXYOS_PROBE_WARN ${LANG_SIMPCHINESE}" in nsi
assert "安装将继续" in nsi
assert ".install-ready" in provision
assert provision.index(".install-ready") < provision.index("!insertmacro wails.probeOpenXYOS")
assert provision.index("!insertmacro wails.requireOpenXYOSLayout") < provision.index(
".install-ready"
)
assert provision.index("Call PersistOpenXYOS") < provision.index(".install-ready")
assert "start-sidecar.ps1" in nsh
assert "FreeOS-openXYOS" in nsi
workflow = (REPO / ".github" / "workflows" / "octop-desktop.yml").read_text(encoding="utf-8")
Expand Down Expand Up @@ -261,6 +266,38 @@ def test_desktop_readme_documents_uninstall_keep_vs_remove() -> None:
assert "install-time" in text.lower() or "during Setup" in text or "安装期" in text


def _filewrite_payloads(block: str) -> list[str]:
payloads: list[str] = []
for raw in block.splitlines():
stripped = raw.strip()
if not stripped.startswith("FileWrite"):
continue
start = stripped.find("`")
end = stripped.rfind("`")
if start != -1 and end > start:
payloads.append(stripped[start + 1 : end].replace("$\\r$\\n", "\n"))
return payloads


def test_nsis_extract_cmd_uses_colon_prefixed_batch_labels() -> None:
"""cmd.exe goto targets must be ':label'; 'label:' is not found."""
nsh = NSH.read_text(encoding="utf-8")
provision = nsh[
nsh.index("!macro wails.provisionOpenXYOS") : nsh.index(
"!macro wails.requireOpenXYOSLayout"
)
]
script = "".join(_filewrite_payloads(provision))
assert "goto liveNodeOk" in script
assert ":liveNodeOk" in script
assert "exit /b 0" in script
assert "FileWrite $0 `liveNodeOk:" not in nsh
for line in script.splitlines():
token = line.strip()
if token.endswith(":") and " " not in token and not token.startswith(":"):
raise AssertionError(f"cmd label missing leading colon: {token!r}")


def test_nsis_leaves_user_level_sidecar_running() -> None:
"""#29 probed then taskkill'd Node; Organization still asked to 启动边车."""
nsi = NSI.read_text(encoding="utf-8-sig")
Expand All @@ -277,7 +314,11 @@ def test_nsis_leaves_user_level_sidecar_running() -> None:
assert "taskkill.exe" not in autostart
assert "taskkill.exe" not in probe
assert "Start-Process" not in probe
assert "Abort" not in probe
assert "SetErrorLevel 68" not in probe
assert "wait-openxyos.ps1" in probe
assert "OPENXYOS_PROBE_WARN" in probe
assert "schtasks.exe /Run" in probe
assert "Call PersistOpenXYOS" in nsh
assert "FreeOS-openXYOS" in persist
assert "A4C6892C-3BA9-11d2-9DEA-00C04FB16162" in persist
Expand Down
Loading