chore(scripts): 新增 Windows 等价控制脚本 ctrl.bat
- 与 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:
1 parent
f42ea1e41c
commit
17c9fb7545
1 file changed
+224
@@ -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
|
||||
Reference in new issue
Block a user