From 44ce5e655dee4a1136525e70affc5a4fab74abd6 Mon Sep 17 00:00:00 2001 From: Leon Date: Mon, 7 Sep 2026 11:19:45 +0800 Subject: [PATCH] Harden Windows service supervision --- autostart_runner.bat | 6 ++- check_autostart.bat | 17 +++++++++ check_autostart.ps1 | 34 +++++++++++++++++ diagnose_service.bat | 17 +++++++++ diagnose_service.ps1 | 89 ++++++++++++++++++++++++++++++++++++++++++++ run.bat | 4 +- setup_autostart.bat | 11 ++++++ setup_autostart.ps1 | 11 +++++- watchdog.bat | 19 ++++++++-- 9 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 check_autostart.bat create mode 100644 check_autostart.ps1 create mode 100644 diagnose_service.bat create mode 100644 diagnose_service.ps1 diff --git a/autostart_runner.bat b/autostart_runner.bat index 6ff84b9..69fb0b4 100644 --- a/autostart_runner.bat +++ b/autostart_runner.bat @@ -5,9 +5,11 @@ cd /d "%~dp0" set "LOG_DIR=%~dp0logs" set "LOG_FILE=%LOG_DIR%\autostart.log" +set "DOCUTRANSLATE_NONINTERACTIVE=1" if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" +:restart echo.>> "%LOG_FILE%" echo [%DATE% %TIME%] Starting DocuTranslate autostart runner...>> "%LOG_FILE%" @@ -15,5 +17,7 @@ call "%~dp0run.bat" >> "%LOG_FILE%" 2>&1 set "EXIT_CODE=%ERRORLEVEL%" echo [%DATE% %TIME%] DocuTranslate stopped with exit code %EXIT_CODE%.>> "%LOG_FILE%" +echo [%DATE% %TIME%] Restarting in 10 seconds...>> "%LOG_FILE%" -exit /b %EXIT_CODE% +timeout /t 10 /nobreak >nul +goto restart diff --git a/check_autostart.bat b/check_autostart.bat new file mode 100644 index 0000000..1b5a3a9 --- /dev/null +++ b/check_autostart.bat @@ -0,0 +1,17 @@ +@echo off +setlocal + +cd /d "%~dp0" + +powershell -NoProfile -Command "if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { exit 0 } else { exit 1 }" >nul 2>&1 +if not "%ERRORLEVEL%"=="0" ( + set "DOCUTRANSLATE_CHECK_BAT=%~f0" + echo [INFO] Administrator permission is required. Opening the UAC prompt... + powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath $env:DOCUTRANSLATE_CHECK_BAT -Verb RunAs" + exit /b +) + +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0check_autostart.ps1" + +echo. +pause diff --git a/check_autostart.ps1 b/check_autostart.ps1 new file mode 100644 index 0000000..5e343e8 --- /dev/null +++ b/check_autostart.ps1 @@ -0,0 +1,34 @@ +$ErrorActionPreference = "Continue" + +$TaskNames = @("DocuTranslate", "DocuTranslateWatchdog") + +foreach ($TaskName in $TaskNames) { + Write-Host "" + Write-Host "===== $TaskName =====" + $Task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($null -eq $Task) { + Write-Host "[ERROR] Task not found. Run setup_autostart.bat first." -ForegroundColor Red + continue + } + + $Info = Get-ScheduledTaskInfo -TaskName $TaskName + Write-Host "State: $($Task.State)" + Write-Host "Run as: $($Task.Principal.UserId)" + Write-Host "Last run: $($Info.LastRunTime)" + Write-Host "Last result: $($Info.LastTaskResult)" + Write-Host "Next run: $($Info.NextRunTime)" + Write-Host "Execution limit: $($Task.Settings.ExecutionTimeLimit)" + Write-Host "Action: $($Task.Actions.Execute) $($Task.Actions.Arguments)" +} + +Write-Host "" +Write-Host "===== HTTP health =====" +try { + $Response = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:8010/service/meta" -TimeoutSec 8 + Write-Host "[OK] DocuTranslate responded with HTTP $($Response.StatusCode)." -ForegroundColor Green +} catch { + Write-Host "[ERROR] DocuTranslate did not respond: $($_.Exception.Message)" -ForegroundColor Red +} + +Write-Host "" +Write-Host "Result notes: 0 = last run succeeded; 267009 (0x41301) = task is running." diff --git a/diagnose_service.bat b/diagnose_service.bat new file mode 100644 index 0000000..a4341c1 --- /dev/null +++ b/diagnose_service.bat @@ -0,0 +1,17 @@ +@echo off +setlocal + +cd /d "%~dp0" + +powershell -NoProfile -Command "if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { exit 0 } else { exit 1 }" >nul 2>&1 +if not "%ERRORLEVEL%"=="0" ( + set "DOCUTRANSLATE_DIAGNOSE_BAT=%~f0" + echo [INFO] Administrator permission is required. Opening the UAC prompt... + powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath $env:DOCUTRANSLATE_DIAGNOSE_BAT -Verb RunAs" + exit /b +) + +powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0diagnose_service.ps1" + +echo. +pause diff --git a/diagnose_service.ps1 b/diagnose_service.ps1 new file mode 100644 index 0000000..6a07538 --- /dev/null +++ b/diagnose_service.ps1 @@ -0,0 +1,89 @@ +$ErrorActionPreference = "Continue" + +$ProjectDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$LogDir = Join-Path $ProjectDir "logs" +if (!(Test-Path -LiteralPath $LogDir)) { + New-Item -ItemType Directory -Path $LogDir | Out-Null +} + +$ReportPath = Join-Path $LogDir ("diagnose_" + (Get-Date -Format "yyyyMMdd_HHmmss") + ".txt") +$Lines = New-Object System.Collections.Generic.List[string] + +function Add-ReportLine([string]$Text = "") { + $Lines.Add($Text) + Write-Host $Text +} + +Add-ReportLine "DocuTranslate diagnostic report" +Add-ReportLine ("Generated: " + (Get-Date)) + +$Os = Get-CimInstance Win32_OperatingSystem +Add-ReportLine "" +Add-ReportLine "===== System uptime =====" +Add-ReportLine ("Last boot: " + $Os.LastBootUpTime) +Add-ReportLine ("Uptime: " + ((Get-Date) - $Os.LastBootUpTime)) + +Add-ReportLine "" +Add-ReportLine "===== Power and restart events (last 14 days) =====" +$StartTime = (Get-Date).AddDays(-14) +$Events = Get-WinEvent -FilterHashtable @{ LogName = "System"; Id = @(41, 1074, 6005, 6006, 6008); StartTime = $StartTime } -ErrorAction SilentlyContinue | + Sort-Object TimeCreated -Descending | + Select-Object -First 30 +if ($Events) { + foreach ($Event in $Events) { + $Message = ($Event.Message -replace "`r?`n", " ") + Add-ReportLine ("{0:yyyy-MM-dd HH:mm:ss} ID={1} Provider={2} Message={3}" -f $Event.TimeCreated, $Event.Id, $Event.ProviderName, $Message) + } +} else { + Add-ReportLine "No matching events found." +} + +Add-ReportLine "" +Add-ReportLine "===== Scheduled tasks =====" +foreach ($TaskName in @("DocuTranslate", "DocuTranslateWatchdog")) { + $Task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + if ($null -eq $Task) { + Add-ReportLine ("${TaskName}: NOT FOUND") + continue + } + $Info = Get-ScheduledTaskInfo -TaskName $TaskName + Add-ReportLine ("{0}: State={1}; LastRun={2}; LastResult={3}; NextRun={4}; Limit={5}" -f $TaskName, $Task.State, $Info.LastRunTime, $Info.LastTaskResult, $Info.NextRunTime, $Task.Settings.ExecutionTimeLimit) +} + +Add-ReportLine "" +Add-ReportLine "===== Port 8010 =====" +$Connections = Get-NetTCPConnection -LocalPort 8010 -State Listen -ErrorAction SilentlyContinue +if ($Connections) { + foreach ($Connection in $Connections) { + $Process = Get-Process -Id $Connection.OwningProcess -ErrorAction SilentlyContinue + Add-ReportLine ("Listening: {0}:{1}; PID={2}; Process={3}" -f $Connection.LocalAddress, $Connection.LocalPort, $Connection.OwningProcess, $Process.ProcessName) + } +} else { + Add-ReportLine "Nothing is listening on port 8010." +} + +Add-ReportLine "" +Add-ReportLine "===== HTTP health =====" +try { + $Response = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:8010/service/meta" -TimeoutSec 8 + Add-ReportLine ("HTTP status: " + $Response.StatusCode) +} catch { + Add-ReportLine ("HTTP failed: " + $_.Exception.Message) +} + +foreach ($Name in @("autostart.log", "watchdog.log")) { + $Path = Join-Path $LogDir $Name + Add-ReportLine "" + Add-ReportLine ("===== Last 80 lines: " + $Name + " =====") + if (Test-Path -LiteralPath $Path) { + foreach ($Line in (Get-Content -LiteralPath $Path -Tail 80)) { + Add-ReportLine $Line + } + } else { + Add-ReportLine "Log file not found." + } +} + +$Lines | Out-File -LiteralPath $ReportPath -Encoding utf8 +Add-ReportLine "" +Add-ReportLine ("Report saved to: " + $ReportPath) diff --git a/run.bat b/run.bat index be6b154..a3c4ce2 100644 --- a/run.bat +++ b/run.bat @@ -7,7 +7,7 @@ cd /d "%~dp0" REM 检查 .venv 是否存在 if not exist ".venv\Scripts\activate.bat" ( echo [ERROR] 未找到 .venv\Scripts\activate.bat - pause + if not defined DOCUTRANSLATE_NONINTERACTIVE pause exit /b 1 ) @@ -29,7 +29,7 @@ call deactivate >nul 2>nul if not "%EXIT_CODE%"=="0" ( echo. echo [ERROR] 程序退出,返回码: %EXIT_CODE% - pause + if not defined DOCUTRANSLATE_NONINTERACTIVE pause ) exit /b %EXIT_CODE% diff --git a/setup_autostart.bat b/setup_autostart.bat index 7330e55..e74fdbf 100644 --- a/setup_autostart.bat +++ b/setup_autostart.bat @@ -3,6 +3,14 @@ setlocal cd /d "%~dp0" +powershell -NoProfile -Command "if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { exit 0 } else { exit 1 }" >nul 2>&1 +if not "%ERRORLEVEL%"=="0" ( + set "DOCUTRANSLATE_SETUP_BAT=%~f0" + echo [INFO] Administrator permission is required. Opening the UAC prompt... + powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath $env:DOCUTRANSLATE_SETUP_BAT -Verb RunAs" + exit /b +) + set "TASK_NAME=DocuTranslate" set "WATCHDOG_TASK_NAME=DocuTranslateWatchdog" set "PROJECT_DIR=%~dp0" @@ -55,4 +63,7 @@ echo. echo You can start it now with: echo schtasks /Run /TN "%TASK_NAME%" echo. +echo Double-click check_autostart.bat to verify tasks and HTTP health. +echo Double-click diagnose_service.bat to collect restart and failure evidence. +echo. pause diff --git a/setup_autostart.ps1 b/setup_autostart.ps1 index fe854ad..e4d1e42 100644 --- a/setup_autostart.ps1 +++ b/setup_autostart.ps1 @@ -27,12 +27,16 @@ try { $StartupSettings = New-ScheduledTaskSettingsSet ` -StartWhenAvailable ` -MultipleInstances IgnoreNew ` + -ExecutionTimeLimit ([TimeSpan]::Zero) ` + -DontStopIfGoingOnBatteries ` -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1) } catch { $StartupSettings = New-ScheduledTaskSettingsSet ` -StartWhenAvailable ` - -MultipleInstances IgnoreNew + -MultipleInstances IgnoreNew ` + -ExecutionTimeLimit ([TimeSpan]::Zero) ` + -DontStopIfGoingOnBatteries } Register-ScheduledTask ` @@ -51,7 +55,9 @@ $WatchdogTrigger = New-ScheduledTaskTrigger ` -RepetitionDuration (New-TimeSpan -Days 3650) $WatchdogSettings = New-ScheduledTaskSettingsSet ` -StartWhenAvailable ` - -MultipleInstances IgnoreNew + -MultipleInstances IgnoreNew ` + -ExecutionTimeLimit (New-TimeSpan -Minutes 2) ` + -DontStopIfGoingOnBatteries Register-ScheduledTask ` -TaskName $WatchdogTaskName ` @@ -66,6 +72,7 @@ Write-Host "[OK] Scheduled task `"$WatchdogTaskName`" has been created." Write-Host "[OK] Startup runner: `"$RunnerBat`"" Write-Host "[OK] Watchdog: `"$WatchdogBat`"" Write-Host "[OK] Log file: `"$LogFile`"" +Write-Host "[OK] Startup task execution limit: disabled" Write-Host "" Write-Host "Verify:" Write-Host "schtasks /Query /TN `"$TaskName`" /V /FO LIST" diff --git a/watchdog.bat b/watchdog.bat index 9708f18..b524ff1 100644 --- a/watchdog.bat +++ b/watchdog.bat @@ -9,9 +9,20 @@ set "LOG_FILE=%LOG_DIR%\watchdog.log" if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" powershell -NoProfile -ExecutionPolicy Bypass -Command ^ - "$ErrorActionPreference = 'SilentlyContinue';" ^ - "$ok = $false;" ^ - "try { $r = Invoke-WebRequest -UseBasicParsing -Uri '%HEALTH_URL%' -TimeoutSec 5; $ok = ($r.StatusCode -eq 200) } catch { $ok = $false };" ^ - "if (-not $ok) { Add-Content -Path '%LOG_FILE%' -Value ('[' + (Get-Date) + '] Health check failed, restarting %TASK_NAME%...'); schtasks /End /TN '%TASK_NAME%' | Out-Null; Start-Sleep -Seconds 2; schtasks /Run /TN '%TASK_NAME%' | Out-Null }" + "$ErrorActionPreference = 'Stop'; try { $r = Invoke-WebRequest -UseBasicParsing -Uri '%HEALTH_URL%' -TimeoutSec 8; if ($r.StatusCode -eq 200) { exit 0 } } catch {}; exit 1" + +if "%ERRORLEVEL%"=="0" exit /b 0 + +timeout /t 10 /nobreak >nul + +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ErrorActionPreference = 'Stop'; try { $r = Invoke-WebRequest -UseBasicParsing -Uri '%HEALTH_URL%' -TimeoutSec 8; if ($r.StatusCode -eq 200) { exit 0 } } catch {}; exit 1" + +if "%ERRORLEVEL%"=="0" exit /b 0 + +echo [%DATE% %TIME%] Two health checks failed. Restarting %TASK_NAME%...>> "%LOG_FILE%" +schtasks /End /TN "%TASK_NAME%" >> "%LOG_FILE%" 2>&1 +timeout /t 2 /nobreak >nul +schtasks /Run /TN "%TASK_NAME%" >> "%LOG_FILE%" 2>&1 exit /b 0