- 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 侧改追加(不再重启即截断)
259 lines
9.2 KiB
Batchfile
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
|