Files
Cyrene_v2/scripts/ctrl.bat
AskaEth 5139945bfe
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(channels): 非消息事件接入(notice 归一化 + 传入前审查,fail-safe 静默)与入站脱敏帧日志/日志轮转
- notice:adapter 归一化 seam + 结构化分类 + 净化(ANSI/控制/零宽/双向字符、200 runes 截断)
  + fail-safe 写死为「静默」(未装配审查/超时/失败 ⇒ 仅登记、不驱动 loop)
- 身份面零改动:notice 发送者恒 RoleSystem + SenderID=system
- 复用策略四的同一个审查模型(Options.Review),未另造第二套
- 入站帧脱敏 DEBUG(post_type/message_type/sub_type/self_id/user_id/sender_id/group_id/段类型/at 目标)
- ctrl.bat 启动前归档轮转(保留 20 份)+ bash 侧改追加(不再重启即截断)
2026-10-05 11:55:05 +08:00

259 lines
9.2 KiB
Batchfile

@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 - the previous run's logs are NEVER discarded: before each start they are
rem archived as <name>.<yyyyMMdd-HHmmss>.bak, newest %LOG_KEEP% kept. Needed
rem because Start-Process -RedirectStandard* OVERWRITES its target (PowerShell
rem has no append form), so every restart used to wipe the evidence in cy.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 LOG_KEEP: how many archived logs to retain (one per start, oldest pruned first)
set "LOG_KEEP=20"
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
call :rotate_logs
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
rem rotate_logs: keep the previous run's log instead of letting it be overwritten.
rem Start-Process -RedirectStandardOutput/-RedirectStandardError OVERWRITE their
rem target (PowerShell has no append form), so without this every restart wiped
rem cy.log -- i.e. each restart destroyed the evidence of the run before it.
rem Archiving (not appending) also keeps the runs separable. Failures are ignored
rem on purpose: a log we cannot archive must never block a start.
:rotate_logs
set "RUN_TS="
for /f "usebackq delims=" %%T in (`powershell -NoProfile -Command "Get-Date -Format yyyyMMdd-HHmmss"`) do if not defined RUN_TS set "RUN_TS=%%T"
if not defined RUN_TS set "RUN_TS=unknown%RANDOM%"
if exist "%LOG_FILE%" move /y "%LOG_FILE%" "%LOG_FILE%.%RUN_TS%.bak" >nul 2>&1
if exist "%STDOUT_FILE%" move /y "%STDOUT_FILE%" "%STDOUT_FILE%.%RUN_TS%.bak" >nul 2>&1
call :prune_logs "%LOG_FILE%"
call :prune_logs "%STDOUT_FILE%"
exit /b 0
rem prune_logs <base>: keep only the newest %LOG_KEEP% archives of <base>.
rem Runs with %USERPROFILE% as the working directory so the bare names dir prints
rem resolve (the repo may sit on another drive than %USERPROFILE%; pushd/popd then
rem echo the directory, hence the >nul).
:prune_logs
set "PRUNE_BASE=%~1"
pushd "%USERPROFILE%" >nul 2>&1 || exit /b 0
for /f "skip=%LOG_KEEP% delims=" %%F in ('dir /b /o-d /a-d "%PRUNE_BASE%.*.bak" 2^>nul') do del /q "%%~F" >nul 2>&1
popd >nul 2>&1
exit /b 0