build(windows-node): add batch deployment workflow
This commit is contained in:
parent
8aafe3272f
commit
e6128c7e5b
|
|
@ -21,6 +21,7 @@ htmlcov/
|
|||
# .NET Windows Node build outputs
|
||||
windows-node/**/bin/
|
||||
windows-node/**/obj/
|
||||
windows-node/dist*/
|
||||
|
||||
# Virtualenv
|
||||
.venv/
|
||||
|
|
|
|||
22
RUN.md
22
RUN.md
|
|
@ -1069,26 +1069,32 @@ Node API 只能绑定受控内网地址并由安全组限制来源 IP。当前 H
|
|||
|
||||
Windows 端源码位于 `windows-node/`,要求 .NET 10 SDK:
|
||||
|
||||
```bat
|
||||
windows-node\package-windows-node.bat
|
||||
```
|
||||
|
||||
默认生成不包含 .NET Runtime 的 `windows-node\dist\zcbot-windows-node-<version>-win-x64.zip` 和对应 `.sha256`,目标机器需预装 .NET 10 Desktop Runtime;需要免安装 .NET 的离线包时使用 `package-windows-node.bat --self-contained`。解压后可通过界面注册,也可执行:
|
||||
|
||||
```powershell
|
||||
dotnet build windows-node/Zcbot.WindowsNode/Zcbot.WindowsNode.csproj
|
||||
windows-node/Zcbot.WindowsNode/bin/Debug/net10.0-windows/Zcbot.WindowsNode.exe enroll `
|
||||
Zcbot.WindowsNode.exe enroll `
|
||||
--server http://zcbot.internal:8765 `
|
||||
--name win-origin-01 `
|
||||
--code ZCN-...
|
||||
windows-node/Zcbot.WindowsNode/bin/Debug/net10.0-windows/Zcbot.WindowsNode.exe
|
||||
Zcbot.WindowsNode.exe
|
||||
```
|
||||
|
||||
Origin Worker 使用独立固定 Python 运行时。先确认该交互式 Windows 账号已安装并可启动 Origin/OriginPro,再由管理员安装运行时(不要复用服务端 `.venv`):
|
||||
Windows Node 发布目录包含纯 BAT 安装入口,不依赖 PowerShell。使用实际运行 Node 的专用 Windows 账号双击 `install-windows-node.bat`;安装器会检测 Origin/OriginPro,检测到后自动通过 `py -3.12` 或 PATH 定位 Python 3.12 并安装固定 runtime,未检测到则跳过,最后通过 `schtasks.exe` 注册该账号登录后的启动任务,结束后保留窗口。也可在命令提示符执行:
|
||||
|
||||
```powershell
|
||||
windows-node\install-origin-runtime.ps1 -BootstrapPython D:\programs\Python312\python.exe
|
||||
```bat
|
||||
cd /d D:\ZcbotNode
|
||||
install-windows-node.bat
|
||||
```
|
||||
|
||||
默认解释器为 `%ProgramData%\Zcbot\WindowsNode\runtimes\origin\python.exe`。如需放在其他受管目录,设置机器级 `ZCBOT_ORIGIN_PYTHON` 为绝对 `python.exe` 路径后重启 Node。运行时固定依赖见 `windows-node/origin-worker/requirements.txt`;任务请求无权选择解释器、脚本或路径。当前 Worker 支持 CSV/XLSX/JSON 输入,`line`、`scatter`、`line_scatter` 与 OPJU/PNG/SVG/PDF 输出。成功产物由 Node 流式上传,全部校验通过后发布到任务工作目录 `origin/<job_id>/`;上传中断会在重连时幂等续传。
|
||||
若 Python 未加入 PATH,可把绝对路径作为第一个参数,例如 `install-windows-node.bat "C:\Python312\python.exe"`。默认解释器为 `%ProgramData%\Zcbot\WindowsNode\runtimes\origin\Scripts\python.exe`。如需使用其他受管解释器,设置机器级 `ZCBOT_ORIGIN_PYTHON` 为绝对 `python.exe` 路径后重启 Node。`node.json`、可恢复任务和 runtime 集中保存在 `%ProgramData%\Zcbot\WindowsNode\`,不会因替换程序目录而丢失。运行时固定依赖见发布目录的 `origin-worker/requirements.txt`;任务请求无权选择解释器、脚本或路径。当前 Worker 支持 CSV/XLSX/JSON 输入,`line`、`scatter`、`line_scatter` 与 OPJU/PNG/SVG/PDF 输出。成功产物由 Node 流式上传,全部校验通过后发布到任务工作目录 `origin/<job_id>/`;上传中断会在重连时幂等续传。
|
||||
|
||||
Web 用户登录后,右下角 Job 中心会聚合本人最近任务。活动任务约 4 秒刷新一次,空闲时降为约 30 秒;停止已派发任务是协作取消,状态先显示“正在停止”,Node 在线时立即接收,断线后在下次连接或心跳时重放。Agent 可调用 `software_capability_list`、`software_job_submit`、`software_job_status` 和 `software_job_cancel`;提交工具只创建固定 schema 的持久任务,不会阻塞当前对话等待完成。
|
||||
|
||||
注册配置写入 `%ProgramData%\Zcbot\WindowsNode\node.json`;Token 使用 DPAPI `LocalMachine` 加密,ACL 仅允许注册账号和 `SYSTEM`。应始终用同一专用 Windows 账号注册并运行 Node。当前 MVP 以该账号的登录后计划任务启动,不安装 Windows Service。
|
||||
注册配置写入 `%ProgramData%\Zcbot\WindowsNode\node.json`;Token 使用 DPAPI `LocalMachine` 加密,ACL 仅允许注册账号和 `SYSTEM`。应始终用同一专用 Windows 账号执行统一安装器、注册并运行 Node。当前 MVP 以该账号的登录后计划任务启动,不安装 Windows Service。
|
||||
|
||||
直接双击 EXE 启动托盘 UI:红点为未注册/身份失效,黄点为连接中,绿点为在线;双击托盘图标打开配置窗。原 CLI 注册入口继续保留,无 UI 模式使用 `Zcbot.WindowsNode.exe run --headless`。
|
||||
|
||||
|
|
|
|||
|
|
@ -81,13 +81,48 @@ class WindowsNodeSourceTests(unittest.TestCase):
|
|||
tray = (PROJECT / "TrayApplicationContext.cs").read_text(encoding="utf-8")
|
||||
self.assertIn("form.ReconnectRequested += RestartConnection", tray)
|
||||
|
||||
def test_startup_task_is_login_scoped_and_runs_the_fixed_node_executable(self) -> None:
|
||||
script = (ROOT / "install-startup.ps1").read_text(encoding="utf-8")
|
||||
self.assertIn('GetFileName($resolvedExecutable) -ne "Zcbot.WindowsNode.exe"', script)
|
||||
self.assertIn("New-ScheduledTaskTrigger -AtLogOn -User $currentUser", script)
|
||||
self.assertIn("-LogonType Interactive", script)
|
||||
self.assertIn("-RunLevel Limited", script)
|
||||
self.assertNotIn("-RunLevel Highest", script)
|
||||
def test_unified_installer_registers_login_startup(self) -> None:
|
||||
launcher = (ROOT / "install-windows-node.bat").read_text(encoding="utf-8")
|
||||
self.assertNotIn("powershell", launcher.lower())
|
||||
self.assertIn('set "NODE_EXE=%~dp0Zcbot.WindowsNode.exe"', launcher)
|
||||
self.assertIn('schtasks.exe /Create /TN "Zcbot Windows Node"', launcher)
|
||||
self.assertIn("/SC ONLOGON", launcher)
|
||||
self.assertIn("/RL LIMITED", launcher)
|
||||
self.assertIn("/IT", launcher)
|
||||
self.assertIn("pause >nul", launcher)
|
||||
|
||||
def test_unified_installer_detects_origin_and_python_312(self) -> None:
|
||||
script = (ROOT / "install-windows-node.bat").read_text(encoding="utf-8")
|
||||
self.assertIn(":detect_origin", script)
|
||||
self.assertIn('reg.exe query "HKCR\\Origin.ApplicationSI\\CLSID"', script)
|
||||
self.assertIn(":find_python", script)
|
||||
self.assertIn("py.exe -3.12", script)
|
||||
self.assertIn("sys.version_info[:2] == (3, 12)", script)
|
||||
self.assertIn(
|
||||
'set "RUNTIME_DIR=%ProgramData%\\Zcbot\\WindowsNode\\runtimes\\origin"',
|
||||
script,
|
||||
)
|
||||
self.assertIn('set "RUNTIME_PYTHON=%RUNTIME_DIR%\\Scripts\\python.exe"', script)
|
||||
self.assertIn('call :run_python -m venv "%RUNTIME_DIR%"', script)
|
||||
|
||||
def test_publish_output_contains_the_complete_installer_payload(self) -> None:
|
||||
project = (PROJECT / "Zcbot.WindowsNode.csproj").read_text(encoding="utf-8")
|
||||
self.assertIn("..\\install-windows-node.bat", project)
|
||||
self.assertIn("..\\origin-worker\\requirements.txt", project)
|
||||
self.assertFalse((ROOT / "install-windows-node.ps1").exists())
|
||||
self.assertFalse((ROOT / "install-origin-runtime.ps1").exists())
|
||||
self.assertFalse((ROOT / "install-startup.ps1").exists())
|
||||
|
||||
def test_packager_defaults_to_framework_dependent_verified_zip(self) -> None:
|
||||
script = (ROOT / "package-windows-node.bat").read_text(encoding="utf-8")
|
||||
self.assertNotIn("powershell", script.lower())
|
||||
self.assertIn('set "SELF_CONTAINED=false"', script)
|
||||
self.assertIn('if /I "%~1"=="--self-contained"', script)
|
||||
self.assertIn("dotnet publish", script)
|
||||
self.assertIn('"install-windows-node.bat"', script)
|
||||
self.assertIn("tar.exe -a -c -f", script)
|
||||
self.assertIn("certutil.exe -hashfile", script)
|
||||
self.assertFalse((ROOT / "package-windows-node.ps1").exists())
|
||||
|
||||
def test_auth_rejection_is_distinct_from_http_websocket_handshake_failure(self) -> None:
|
||||
connection = (PROJECT / "NodeConnectionLoop.cs").read_text(encoding="utf-8")
|
||||
|
|
@ -163,7 +198,10 @@ class WindowsNodeSourceTests(unittest.TestCase):
|
|||
project = (PROJECT / "Zcbot.WindowsNode.csproj").read_text(encoding="utf-8")
|
||||
worker = (ROOT / "origin-worker" / "worker.py").read_text(encoding="utf-8")
|
||||
self.assertIn('Environment.GetEnvironmentVariable("ZCBOT_ORIGIN_PYTHON")', runner)
|
||||
self.assertIn('Path.Combine(paths.RootDirectory, "runtimes", "origin", "python.exe")', runner)
|
||||
self.assertIn(
|
||||
'Path.Combine(paths.RootDirectory, "runtimes", "origin", "Scripts", "python.exe")',
|
||||
runner,
|
||||
)
|
||||
self.assertIn("UseShellExecute = false", runner)
|
||||
self.assertIn("startInfo.ArgumentList.Add(workerScript)", runner)
|
||||
self.assertIn("startInfo.ArgumentList.Add(jobDirectory)", runner)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,21 @@
|
|||
|
||||
当前实现托盘状态角标、小型配置窗口、注册、DPAPI/ACL 配置保存、WebSocket `hello`/心跳和退避重连,并只读探测 Origin/OriginPro 安装版本、COM 自动化组件与桌面会话状态。Node 可以接收受控的 `origin.plot@v1` offer,在本机任务目录原子保存请求后回报 accept/reject;随后以 Node 身份流式下载任务绑定的 CSV/XLSX/JSON,校验大小与 SHA-256 后原子保存。固定 Origin Worker 独立于单次 WebSocket 执行,断线不终止已启动绘图。成功产物按 manifest 逐项流式上传并由云端复核大小与 SHA-256,全部完成后原子发布到任务工作目录的 `origin/<job_id>/`;中断后按本地 `upload-complete.json` 幂等续传。
|
||||
|
||||
固定 Origin Worker 已支持 `line`、`scatter` 和 `line_scatter`,生成 OPJU、PNG、SVG、PDF、plot spec、provenance 与原子 `terminal.json`。运行时独立于 zcbot 服务端 Python,管理员执行:
|
||||
在仓库根目录执行一条命令生成可分发 ZIP:
|
||||
|
||||
```powershell
|
||||
.\install-origin-runtime.ps1 -BootstrapPython D:\programs\Python312\python.exe
|
||||
```bat
|
||||
windows-node\package-windows-node.bat
|
||||
```
|
||||
|
||||
默认安装到 `%ProgramData%\Zcbot\WindowsNode\runtimes\origin\python.exe`;也可由管理员用绝对路径环境变量 `ZCBOT_ORIGIN_PYTHON` 指向固定解释器。任务请求不能指定解释器、脚本或路径。
|
||||
产物位于 `windows-node\dist\zcbot-windows-node-<version>-win-x64.zip`,旁边同时生成 SHA-256 文件。默认不包含 .NET Runtime,目标机器需预装 .NET 10 Desktop Runtime;需要免安装 .NET 的离线包时执行 `package-windows-node.bat --self-contained`。打包脚本是纯 BAT,使用 .NET SDK 以及 Windows 自带的 `tar.exe`、`certutil.exe`。
|
||||
|
||||
固定 Origin Worker 已支持 `line`、`scatter` 和 `line_scatter`,生成 OPJU、PNG、SVG、PDF、plot spec、provenance 与原子 `terminal.json`。运行时独立于 zcbot 服务端 Python。发布目录包含统一安装入口;使用实际运行 Node 的专用 Windows 账号直接双击:
|
||||
|
||||
```text
|
||||
install-windows-node.bat
|
||||
```
|
||||
|
||||
安装器是纯 BAT,不依赖 PowerShell:它先通过注册表检测本机软件;检测到 Origin/OriginPro 或其 COM 自动化组件时,依次尝试 `py -3.12` 和 PATH 中的 `python.exe`,安装 Origin runtime;未检测到则明确跳过。最后通过 `schtasks.exe` 为当前专用账号注册登录后启动任务,成功或失败后都会保留窗口。Python 未加入 PATH 时,可在命令提示符执行 `install-windows-node.bat "C:\Python312\python.exe"`。默认 runtime 位于 `%ProgramData%\Zcbot\WindowsNode\runtimes\origin\Scripts\python.exe`。
|
||||
|
||||
注册和运行必须使用同一专用 Windows 账号。MVP 通过该账号的登录后计划任务自动启动,不以 Windows Service 在 Session 0 运行。
|
||||
|
||||
|
|
@ -29,11 +37,4 @@ windows-node/Zcbot.WindowsNode/bin/Debug/net10.0-windows/Zcbot.WindowsNode.exe
|
|||
Zcbot.WindowsNode.exe run --headless
|
||||
```
|
||||
|
||||
确认前台连接正常后,为当前专用账号注册登录后启动任务:
|
||||
|
||||
```powershell
|
||||
windows-node/install-startup.ps1 `
|
||||
-ExecutablePath windows-node/Zcbot.WindowsNode/bin/Debug/net10.0-windows/Zcbot.WindowsNode.exe
|
||||
```
|
||||
|
||||
注册结果保存在 `%ProgramData%\Zcbot\WindowsNode\node.json`。Token 使用 DPAPI `LocalMachine` 加密,目录与文件 ACL 只保留注册时的运行账号和 `SYSTEM`。复制配置到另一台机器不能解密 Token;Windows 重装、运行账号变更或身份丢失时应由管理员禁用旧节点并重新注册。
|
||||
注册结果仍保存在 `%ProgramData%\Zcbot\WindowsNode\node.json`,可恢复任务保存在同级 `jobs`;它们不随程序包更新。Token 使用 DPAPI `LocalMachine` 加密,目录与文件 ACL 只保留注册时的运行账号和 `SYSTEM`。复制配置到另一台机器不能解密 Token;Windows 重装、运行账号变更或身份丢失时应由管理员禁用旧节点并重新注册。
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ internal static class OriginWorkerRuntime
|
|||
var paths = NodePaths.ForCurrentMachine();
|
||||
var configured = Environment.GetEnvironmentVariable("ZCBOT_ORIGIN_PYTHON");
|
||||
var candidate = string.IsNullOrWhiteSpace(configured)
|
||||
? Path.Combine(paths.RootDirectory, "runtimes", "origin", "python.exe")
|
||||
? Path.Combine(paths.RootDirectory, "runtimes", "origin", "Scripts", "python.exe")
|
||||
: configured;
|
||||
if (!Path.IsPathFullyQualified(candidate)) return null;
|
||||
var resolved = Path.GetFullPath(candidate);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,17 @@
|
|||
<Content Include="..\origin-worker\worker.py">
|
||||
<Link>origin-worker\worker.py</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="..\origin-worker\requirements.txt">
|
||||
<Link>origin-worker\requirements.txt</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="..\install-windows-node.bat">
|
||||
<Link>install-windows-node.bat</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net10.0-windows7.0": {}
|
||||
"net10.0-windows7.0": {},
|
||||
"net10.0-windows7.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BootstrapPython,
|
||||
[string]$RuntimeDirectory = "$env:ProgramData\Zcbot\WindowsNode\runtimes\origin"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$python = (Resolve-Path -LiteralPath $BootstrapPython).Path
|
||||
if ([IO.Path]::GetFileName($python) -ne "python.exe") {
|
||||
throw "BootstrapPython must point to python.exe."
|
||||
}
|
||||
$requirements = Join-Path $PSScriptRoot "origin-worker\requirements.txt"
|
||||
if (-not (Test-Path -LiteralPath $requirements -PathType Leaf)) {
|
||||
throw "Pinned Origin worker requirements are missing."
|
||||
}
|
||||
$runtime = [IO.Path]::GetFullPath($RuntimeDirectory)
|
||||
if ($runtime -eq [IO.Path]::GetPathRoot($runtime)) {
|
||||
throw "RuntimeDirectory cannot be a drive root."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $runtime "python.exe"))) {
|
||||
& $python -m venv $runtime
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed to create the Origin runtime." }
|
||||
}
|
||||
$runtimePython = Join-Path $runtime "python.exe"
|
||||
& $runtimePython -m pip install --requirement $requirements
|
||||
if ($LASTEXITCODE -ne 0) { throw "Failed to install the pinned Origin runtime packages." }
|
||||
& $runtimePython -c "import originpro, openpyxl; print('[OK] Origin worker Python packages are available.')"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Origin runtime import verification failed." }
|
||||
|
||||
Write-Output "[OK] Fixed Origin runtime installed: $runtimePython"
|
||||
Write-Output "[INFO] Restart zcbot Windows Node to refresh runtime health."
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ExecutablePath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$resolvedExecutable = (Resolve-Path -LiteralPath $ExecutablePath).Path
|
||||
if ([System.IO.Path]::GetFileName($resolvedExecutable) -ne "Zcbot.WindowsNode.exe") {
|
||||
throw "ExecutablePath must point to Zcbot.WindowsNode.exe"
|
||||
}
|
||||
|
||||
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
||||
$workingDirectory = Split-Path -Parent $resolvedExecutable
|
||||
$action = New-ScheduledTaskAction `
|
||||
-Execute $resolvedExecutable `
|
||||
-WorkingDirectory $workingDirectory
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser
|
||||
$principal = New-ScheduledTaskPrincipal `
|
||||
-UserId $currentUser `
|
||||
-LogonType Interactive `
|
||||
-RunLevel Limited
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||
-RestartCount 3 `
|
||||
-RestartInterval (New-TimeSpan -Minutes 1) `
|
||||
-MultipleInstances IgnoreNew
|
||||
|
||||
Register-ScheduledTask `
|
||||
-TaskName "Zcbot Windows Node" `
|
||||
-Description "Starts the zcbot Windows execution node after this account logs on." `
|
||||
-Action $action `
|
||||
-Trigger $trigger `
|
||||
-Principal $principal `
|
||||
-Settings $settings `
|
||||
-Force | Out-Null
|
||||
|
||||
Write-Output "[OK] Startup task registered for $currentUser."
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
@echo off
|
||||
setlocal EnableExtensions
|
||||
cd /d "%~dp0"
|
||||
|
||||
set "NODE_EXE=%~dp0Zcbot.WindowsNode.exe"
|
||||
set "REQUIREMENTS=%~dp0origin-worker\requirements.txt"
|
||||
set "RUNTIME_DIR=%ProgramData%\Zcbot\WindowsNode\runtimes\origin"
|
||||
set "RUNTIME_PYTHON=%RUNTIME_DIR%\Scripts\python.exe"
|
||||
set "PYTHON_EXE="
|
||||
set "PYTHON_ARGS="
|
||||
|
||||
echo [INFO] Installing zcbot Windows Node...
|
||||
|
||||
if not exist "%NODE_EXE%" (
|
||||
echo [ERR] Zcbot.WindowsNode.exe was not found beside this installer.
|
||||
goto :failed
|
||||
)
|
||||
|
||||
call :detect_origin
|
||||
if errorlevel 1 (
|
||||
echo [WARN] Origin/OriginPro was not detected; skipped the Origin runtime.
|
||||
goto :install_startup
|
||||
)
|
||||
|
||||
echo [INFO] Origin/OriginPro detected.
|
||||
if not exist "%REQUIREMENTS%" (
|
||||
echo [ERR] Origin worker requirements are missing: %REQUIREMENTS%
|
||||
goto :failed
|
||||
)
|
||||
|
||||
call :find_python "%~1"
|
||||
if errorlevel 1 goto :failed
|
||||
|
||||
if not exist "%RUNTIME_PYTHON%" (
|
||||
echo [INFO] Creating Origin Python runtime...
|
||||
call :run_python -m venv "%RUNTIME_DIR%"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Failed to create the Origin Python runtime.
|
||||
goto :failed
|
||||
)
|
||||
)
|
||||
if not exist "%RUNTIME_PYTHON%" (
|
||||
echo [ERR] Python venv did not create its interpreter: %RUNTIME_PYTHON%
|
||||
goto :failed
|
||||
)
|
||||
|
||||
"%RUNTIME_PYTHON%" -c "import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 12) else 1)"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Existing Origin runtime is not Python 3.12: %RUNTIME_PYTHON%
|
||||
goto :failed
|
||||
)
|
||||
|
||||
echo [INFO] Installing Origin worker dependencies...
|
||||
"%RUNTIME_PYTHON%" -m pip install --requirement "%REQUIREMENTS%"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Failed to install the Origin worker dependencies.
|
||||
goto :failed
|
||||
)
|
||||
"%RUNTIME_PYTHON%" -c "import originpro, openpyxl; print('[OK] Origin worker Python packages are available.')"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Origin runtime import verification failed.
|
||||
goto :failed
|
||||
)
|
||||
echo [OK] Origin runtime installed: %RUNTIME_PYTHON%
|
||||
|
||||
:install_startup
|
||||
echo [INFO] Registering startup task for %USERDOMAIN%\%USERNAME%...
|
||||
schtasks.exe /Create /TN "Zcbot Windows Node" /SC ONLOGON /RU "%USERDOMAIN%\%USERNAME%" /RL LIMITED /IT /TR "\"%NODE_EXE%\"" /F
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Failed to register the Windows Node startup task.
|
||||
goto :failed
|
||||
)
|
||||
|
||||
echo [OK] Windows Node installation completed.
|
||||
echo [INFO] Start Zcbot.WindowsNode.exe to register or connect the node.
|
||||
goto :finish_ok
|
||||
|
||||
:detect_origin
|
||||
reg.exe query "HKCR\Origin.ApplicationSI\CLSID" >nul 2>&1
|
||||
if not errorlevel 1 exit /b 0
|
||||
reg.exe query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s /f "OriginLab" /d >nul 2>&1
|
||||
if not errorlevel 1 exit /b 0
|
||||
reg.exe query "HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" /s /f "OriginLab" /d >nul 2>&1
|
||||
if not errorlevel 1 exit /b 0
|
||||
reg.exe query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" /s /f "OriginLab" /d >nul 2>&1
|
||||
if not errorlevel 1 exit /b 0
|
||||
exit /b 1
|
||||
|
||||
:find_python
|
||||
if not "%~1"=="" (
|
||||
if not exist "%~1" (
|
||||
echo [ERR] Python executable was not found: %~1
|
||||
exit /b 1
|
||||
)
|
||||
"%~1" -c "import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 12) else 1)"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] The specified Python must be Python 3.12: %~1
|
||||
exit /b 1
|
||||
)
|
||||
set "PYTHON_EXE=%~1"
|
||||
set "PYTHON_ARGS="
|
||||
echo [INFO] Using Python 3.12: %~1
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
where.exe py.exe >nul 2>&1
|
||||
if not errorlevel 1 (
|
||||
py.exe -3.12 -c "import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 12) else 1)" >nul 2>&1
|
||||
if not errorlevel 1 (
|
||||
set "PYTHON_EXE=py.exe"
|
||||
set "PYTHON_ARGS=-3.12"
|
||||
echo [INFO] Using Python 3.12 through py.exe.
|
||||
exit /b 0
|
||||
)
|
||||
)
|
||||
|
||||
where.exe python.exe >nul 2>&1
|
||||
if not errorlevel 1 (
|
||||
python.exe -c "import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 12) else 1)" >nul 2>&1
|
||||
if not errorlevel 1 (
|
||||
set "PYTHON_EXE=python.exe"
|
||||
set "PYTHON_ARGS="
|
||||
echo [INFO] Using Python 3.12 from PATH.
|
||||
exit /b 0
|
||||
)
|
||||
)
|
||||
|
||||
echo [ERR] Python 3.12 was not found.
|
||||
echo [INFO] Add Python 3.12 to PATH or run: install-windows-node.bat "C:\path\python.exe"
|
||||
exit /b 1
|
||||
|
||||
:run_python
|
||||
if defined PYTHON_ARGS (
|
||||
"%PYTHON_EXE%" %PYTHON_ARGS% %*
|
||||
) else (
|
||||
"%PYTHON_EXE%" %*
|
||||
)
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
:failed
|
||||
echo.
|
||||
echo [ERR] Installation failed.
|
||||
echo [INFO] Press any key to close this window.
|
||||
pause >nul
|
||||
exit /b 1
|
||||
|
||||
:finish_ok
|
||||
echo.
|
||||
echo [INFO] Press any key to close this window.
|
||||
pause >nul
|
||||
exit /b 0
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
@echo off
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
cd /d "%~dp0"
|
||||
|
||||
set "SELF_CONTAINED=false"
|
||||
if /I "%~1"=="--self-contained" (
|
||||
set "SELF_CONTAINED=true"
|
||||
shift
|
||||
)
|
||||
|
||||
set "OUTPUT_ROOT=%~f1"
|
||||
if "%~1"=="" set "OUTPUT_ROOT=%~dp0dist"
|
||||
set "PROJECT=%~dp0Zcbot.WindowsNode\Zcbot.WindowsNode.csproj"
|
||||
set "RUNTIME_ID=win-x64"
|
||||
|
||||
if not exist "%PROJECT%" (
|
||||
echo [ERR] Windows Node project was not found: %PROJECT%
|
||||
goto :failed
|
||||
)
|
||||
where.exe dotnet.exe >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERR] .NET SDK was not found in PATH.
|
||||
goto :failed
|
||||
)
|
||||
where.exe tar.exe >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Windows tar.exe was not found.
|
||||
goto :failed
|
||||
)
|
||||
where.exe certutil.exe >nul 2>&1
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Windows certutil.exe was not found.
|
||||
goto :failed
|
||||
)
|
||||
|
||||
set "VERSION="
|
||||
for /f "usebackq tokens=*" %%V in (`dotnet msbuild "%PROJECT%" -nologo -getProperty:Version`) do (
|
||||
if not "%%V"=="" set "VERSION=%%V"
|
||||
)
|
||||
if not defined VERSION (
|
||||
echo [ERR] Could not read the Windows Node version.
|
||||
goto :failed
|
||||
)
|
||||
|
||||
set "PACKAGE_NAME=zcbot-windows-node-!VERSION!-!RUNTIME_ID!"
|
||||
set "PUBLISH_DIR=!OUTPUT_ROOT!\!PACKAGE_NAME!"
|
||||
set "ARCHIVE_PATH=!OUTPUT_ROOT!\!PACKAGE_NAME!.zip"
|
||||
set "HASH_PATH=!ARCHIVE_PATH!.sha256"
|
||||
|
||||
if not exist "!OUTPUT_ROOT!" mkdir "!OUTPUT_ROOT!"
|
||||
if errorlevel 1 goto :failed
|
||||
if exist "!PUBLISH_DIR!" rmdir /s /q "!PUBLISH_DIR!"
|
||||
if exist "!ARCHIVE_PATH!" del /f /q "!ARCHIVE_PATH!"
|
||||
if exist "!HASH_PATH!" del /f /q "!HASH_PATH!"
|
||||
|
||||
echo [INFO] Publishing !PACKAGE_NAME! (self-contained=!SELF_CONTAINED!)
|
||||
dotnet publish "%PROJECT%" --configuration Release --runtime !RUNTIME_ID! --self-contained !SELF_CONTAINED! --output "!PUBLISH_DIR!"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] dotnet publish failed.
|
||||
goto :failed
|
||||
)
|
||||
|
||||
for %%F in (
|
||||
"Zcbot.WindowsNode.exe"
|
||||
"install-windows-node.bat"
|
||||
"origin-worker\worker.py"
|
||||
"origin-worker\requirements.txt"
|
||||
) do (
|
||||
if not exist "!PUBLISH_DIR!\%%~F" (
|
||||
echo [ERR] Published package is incomplete: %%~F
|
||||
goto :failed
|
||||
)
|
||||
)
|
||||
|
||||
tar.exe -a -c -f "!ARCHIVE_PATH!" -C "!OUTPUT_ROOT!" "!PACKAGE_NAME!"
|
||||
if errorlevel 1 (
|
||||
echo [ERR] Failed to create the ZIP package.
|
||||
goto :failed
|
||||
)
|
||||
|
||||
set "SHA256="
|
||||
for /f "tokens=*" %%H in ('certutil.exe -hashfile "!ARCHIVE_PATH!" SHA256 ^| findstr.exe /R /C:"^[0-9A-Fa-f][0-9A-Fa-f ]*[0-9A-Fa-f]$"') do (
|
||||
set "SHA256=%%H"
|
||||
)
|
||||
set "SHA256=!SHA256: =!"
|
||||
if not defined SHA256 (
|
||||
echo [ERR] Failed to calculate the ZIP SHA-256.
|
||||
goto :failed
|
||||
)
|
||||
>"!HASH_PATH!" echo !SHA256! !PACKAGE_NAME!.zip
|
||||
|
||||
echo [OK] Package: !ARCHIVE_PATH!
|
||||
echo [OK] SHA256: !SHA256!
|
||||
echo [INFO] Extract the ZIP and double-click install-windows-node.bat.
|
||||
goto :finish_ok
|
||||
|
||||
:failed
|
||||
echo.
|
||||
echo [ERR] Windows Node packaging failed.
|
||||
echo [INFO] Press any key to close this window.
|
||||
pause >nul
|
||||
exit /b 1
|
||||
|
||||
:finish_ok
|
||||
echo.
|
||||
echo [INFO] Press any key to close this window.
|
||||
pause >nul
|
||||
exit /b 0
|
||||
Loading…
Reference in New Issue