chore(scripts): 新增 Windows 等价控制脚本 ctrl.bat
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 与 scripts/ctrl.sh 同语义:start(默认)/stop/restart/status + 额外 build
- 端口读 .env 的 PORT(回退 10080);日志 %USERPROFILE%\cy.log;PID %USERPROFILE%\.cyrene.pid;Start-Process 隐藏窗口脱离控制台(等价 setsid nohup & disown);停止先优雅后强杀
- 刻意 ASCII + CRLF(cmd 解析对编码/换行敏感),并显式走 %SystemRoot%\System32 绝对路径,避免 MSYS(Git Bash) 的同名工具抢占
- 实测:status/stop/start/restart 全流程通过(后端 10942 health ok)
This commit is contained in:
AskaEth committed 2026-10-03 17:44:35 +08:00
1 parent f42ea1e41c
commit 17c9fb7545
1 file changed
+224
+224
View File
@@ -0,0 +1,224 @@
@echo off
setlocal EnableExtensions EnableDelayedExpansion
rem ============================================================
rem Cyrene v2 local control script (Windows) - equivalent to scripts/ctrl.sh
rem
rem Usage:
rem scripts\ctrl.bat start (default)
rem scripts\ctrl.bat start start
rem scripts\ctrl.bat restart restart
rem scripts\ctrl.bat stop stop
rem scripts\ctrl.bat status show status
rem scripts\ctrl.bat build build cyrene.exe into the repo root
rem
rem Equivalent to ctrl.sh:
rem - single binary, run from the repo root (reads .env PORT / DATA_DIR ...)
rem - port comes from .env PORT, falls back to 10080
rem - log: %USERPROFILE%\cy.log (app logs to stderr);
rem stdout fallback: %USERPROFILE%\cy.stdout.log
rem - pid file: %USERPROFILE%\.cyrene.pid, else detected from the port
rem - Start-Process -WindowStyle Hidden = detached start
rem (the ctrl.sh equivalent of "setsid nohup ... & disown")
rem - stop tries a graceful kill first, then force-kills (like kill, then kill -9)
rem - health: GET http://127.0.0.1:<port>/api/v1/health
rem
rem NOTE: build the frontend first (cd frontend\web && npm run build),
rem otherwise the binary falls back to its embedded dist.
rem
rem This file is intentionally ASCII-only with CRLF line endings: batch
rem parsing is codepage-sensitive, and cmd.exe requires CRLF. Windows tools
rem are called by absolute path under %SystemRoot%\System32 so the script
rem also works when an MSYS/MinGW PATH (Git Bash) comes first.
rem ============================================================
set "SYS=%SystemRoot%\System32"
rem ---- paths ----
set "REPO=%~dp0.."
for %%I in ("%REPO%") do set "REPO=%%~fI"
set "APP_BIN=%REPO%\cyrene.exe"
if not exist "%APP_BIN%" set "APP_BIN=%USERPROFILE%\apps\cyrene\cyrene.exe"
set "PID_FILE=%USERPROFILE%\.cyrene.pid"
set "LOG_FILE=%USERPROFILE%\cy.log"
set "STDOUT_FILE=%USERPROFILE%\cy.stdout.log"
rem ---- port: .env PORT wins, fallback 10080 ----
set "GW_PORT="
if exist "%REPO%\.env" (
for /f "usebackq tokens=1,* delims==" %%A in ("%REPO%\.env") do (
if /i "%%A"=="PORT" if not defined GW_PORT set "GW_PORT=%%B"
)
)
if not defined GW_PORT set "GW_PORT=10080"
set "GW_PORT=%GW_PORT: =%"
set "GW_PORT=%GW_PORT:"=%"
set "HEALTH_URL=http://127.0.0.1:%GW_PORT%/api/v1/health"
rem ---- dispatch ----
set "ACTION=%~1"
if "%ACTION%"=="" set "ACTION=start"
if /i "%ACTION%"=="build" goto :do_build
if /i "%ACTION%"=="start" goto :do_start
if /i "%ACTION%"=="stop" goto :do_stop
if /i "%ACTION%"=="restart" goto :do_restart
if /i "%ACTION%"=="status" goto :do_status
echo Usage: scripts\ctrl.bat [start^|stop^|restart^|status^|build] (no arg = start)
exit /b 1
rem ============================================================
rem build
rem ============================================================
:do_build
cd /d "%REPO%" || exit /b 1
echo Building -^> %REPO%\cyrene.exe
go build -ldflags="-s -w" -trimpath -o cyrene.exe ./cmd/cyrene/
if errorlevel 1 (
echo Build FAILED.
exit /b 1
)
echo Done. Note: if the frontend changed, run "cd frontend\web ^&^& npm run build" first.
exit /b 0
rem ============================================================
rem start
rem ============================================================
:do_start
if not exist "%SYS%\curl.exe" (
echo [ERROR] %SYS%\curl.exe not found - the health check needs it.
exit /b 1
)
call :is_running
if defined RUNNING_PID (
echo cyrene is already running ^(PID !RUNNING_PID!^). Use "restart" to restart.
exit /b 0
)
if not exist "%APP_BIN%" (
echo [ERROR] binary not found: %APP_BIN%
echo build it first: scripts\ctrl.bat build
exit /b 1
)
echo Starting cyrene -^> %APP_BIN% ^(port %GW_PORT%^)
cd /d "%REPO%" || exit /b 1
powershell -NoProfile -Command "Start-Process -FilePath '%APP_BIN%' -WorkingDirectory '%REPO%' -RedirectStandardOutput '%STDOUT_FILE%' -RedirectStandardError '%LOG_FILE%' -WindowStyle Hidden"
set /a TRIES=0
:start_wait
set /a TRIES+=1
call :healthy
if not errorlevel 1 (
echo Started: http://localhost:%GW_PORT% ^(log: %LOG_FILE%^)
call :record_pid
exit /b 0
)
if !TRIES! GEQ 20 (
echo [ERROR] port %GW_PORT% not ready after start - check %LOG_FILE%
exit /b 1
)
"%SYS%\ping.exe" -n 2 127.0.0.1 >nul
goto :start_wait
rem ============================================================
rem stop
rem ============================================================
:do_stop
call :is_running
if not defined RUNNING_PID (
echo cyrene is not running ^(no pid file, or the process exited^).
del "%PID_FILE%" >nul 2>&1
exit /b 0
)
echo Sending stop signal -^> PID !RUNNING_PID!
"%SYS%\taskkill.exe" /PID !RUNNING_PID! /T >nul 2>&1
set /a TRIES=0
:stop_wait
set /a TRIES+=1
if !TRIES! GEQ 4 (
echo Did not exit in ~3s - force killing.
"%SYS%\taskkill.exe" /PID !RUNNING_PID! /T /F >nul 2>&1
del "%PID_FILE%" >nul 2>&1
exit /b 0
)
"%SYS%\ping.exe" -n 2 127.0.0.1 >nul
call :is_running
if not defined RUNNING_PID (
echo Stopped.
del "%PID_FILE%" >nul 2>&1
exit /b 0
)
goto :stop_wait
rem ============================================================
rem restart
rem ============================================================
:do_restart
echo Restarting cyrene...
call :do_stop
"%SYS%\ping.exe" -n 2 127.0.0.1 >nul
call :do_start
exit /b %errorlevel%
rem ============================================================
rem status
rem ============================================================
:do_status
call :is_running
call :healthy
if not errorlevel 1 (
echo cyrene: running ^(PID !RUNNING_PID!^) . port %GW_PORT% . health ok
exit /b 0
)
if defined RUNNING_PID (
echo cyrene: process alive ^(PID !RUNNING_PID!^) but health not ready ^(%HEALTH_URL%^)
exit /b 1
)
echo cyrene: not running
exit /b 1
rem ============================================================
rem helpers
rem ============================================================
rem healthy: errorlevel 0 = HTTP 2xx
:healthy
"%SYS%\curl.exe" -s -f --max-time 2 -o NUL "%HEALTH_URL%" >nul 2>&1
exit /b %errorlevel%
rem find_pid_by_port -> PORT_PID (the ctrl.sh "ss" fallback)
:find_pid_by_port
set "PORT_PID="
set "NS_FILE=%TEMP%\cyrene-netstat-%RANDOM%.txt"
"%SYS%\netstat.exe" -ano -p tcp > "%NS_FILE%" 2>nul
for /f "tokens=2,5" %%A in ('%SYS%\findstr.exe /C:"LISTENING" "%NS_FILE%"') do (
if not defined PORT_PID (
echo %%A| "%SYS%\findstr.exe" /E /C:":%GW_PORT%" >nul && set "PORT_PID=%%B"
)
)
del "%NS_FILE%" >nul 2>&1
exit /b 0
rem is_running -> RUNNING_PID (pid file first, then port probe)
:is_running
set "RUNNING_PID="
if exist "%PID_FILE%" (
set "STORED="
set /p STORED=<"%PID_FILE%"
if defined STORED (
set "ALIVE="
for /f "tokens=2 delims=," %%P in ('%SYS%\tasklist.exe /FI "PID eq !STORED!" /NH /FO CSV 2^>nul') do (
if not defined ALIVE set "ALIVE=1"
)
if defined ALIVE set "RUNNING_PID=!STORED!"
)
)
if not defined RUNNING_PID (
call :find_pid_by_port
if defined PORT_PID set "RUNNING_PID=!PORT_PID!"
)
exit /b 0
rem record_pid: write the detected pid to the pid file
:record_pid
call :find_pid_by_port
if defined PORT_PID (
>"%PID_FILE%" echo !PORT_PID!
)
exit /b 0