Companion to the worker MinIO-retry fix. Makes the worker auto-recover from process death (crash, manual kill, missed boot trigger), not just MinIO outages. - start_worker.bat: propagate Python's exit code (exit /b %rc%) so Task Scheduler can actually detect a failed run (it previously always exited 0). - reconfigure_task.ps1 (new): re-registers PW-DocserverWorker with RestartCount=99 / 1-min interval, StartWhenAvailable, and two triggers — AtStartup plus a 5-min repeating trigger with MultipleInstances=IgnoreNew, so a dead worker relaunches within ~5 min and never double-runs. Idempotent. - install.ps1: same self-healing settings for fresh installs. - Verified on the box: killed the worker -> task relaunched it; firing again while running stayed at one instance. Docs updated to match reality: - docserver/README.md: new 'Reliability / self-healing' section. - document-generation.md: corrected the stale 'Flask DocServer :5050 / HTTP' description to the actual MinIO outbound-only transport. - e2e-test-plan.md: removed the outdated 'Word COM fails under SYSTEM / requires RDP after every reboot' limitation; now self-healing under SYSTEM session 0. - infrastructure.md: fixed VM spec (Win Server 2019, Word 16.0, Python 3.13, SSH port 22422) + self-healing note. - architecture.md / formation-system.md: trigger + self-healing details.
234 lines
11 KiB
PowerShell
234 lines
11 KiB
PowerShell
# Performance West Document Conversion Worker — Windows Installation
|
|
# Run as Administrator in PowerShell on the Windows VM
|
|
#
|
|
# What this does:
|
|
# 1. Verifies Python + Microsoft Word are installed
|
|
# 2. Installs Python dependencies (pywin32, minio)
|
|
# 3. Copies docserver_worker.py + config to C:\docserver\
|
|
# 4. Creates a Task Scheduler task that starts the worker at system boot
|
|
# (runs as the installing user, "Run whether user is logged on or not"
|
|
# — Word COM works in session 0 on Server 2019 with desktop interaction)
|
|
#
|
|
# Prerequisites:
|
|
# - Windows 10/11 Pro or Windows Server 2022
|
|
# - Microsoft Word installed (only Word needed, not full Office)
|
|
# - Python 3.12+ (python.org — check "Add to PATH")
|
|
# - Outbound HTTPS to minio.performancewest.net (or wherever MinIO lives)
|
|
# - No inbound ports required — the VM connects OUT to MinIO only
|
|
#
|
|
# Usage:
|
|
# .\install.ps1 -MinioEndpoint "minio.performancewest.net" `
|
|
# -MinioPort 443 `
|
|
# -MinioSecure $true `
|
|
# -MinioAccessKey "your_access_key" `
|
|
# -MinioSecretKey "your_secret_key"
|
|
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$MinioEndpoint,
|
|
|
|
[int] $MinioPort = 9000,
|
|
[bool] $MinioSecure = $false,
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$MinioAccessKey,
|
|
[Parameter(Mandatory=$true)]
|
|
[string]$MinioSecretKey,
|
|
[string]$MinioBucket = "performancewest",
|
|
[int] $PollInterval = 3,
|
|
[string]$AppDir = "C:\docserver"
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
Write-Host ""
|
|
Write-Host "=== Performance West Document Conversion Worker Setup ===" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# ── 0. Admin check ────────────────────────────────────────────────────────────
|
|
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]"Administrator")) {
|
|
Write-Host "ERROR: Run this script as Administrator!" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# ── 1. Python check ───────────────────────────────────────────────────────────
|
|
Write-Host "Checking Python..." -ForegroundColor Yellow
|
|
$python = Get-Command python -ErrorAction SilentlyContinue
|
|
if (-not $python) {
|
|
Write-Host "ERROR: Python not found." -ForegroundColor Red
|
|
Write-Host " Download from https://python.org/downloads" -ForegroundColor Red
|
|
Write-Host " Install with 'Add Python to PATH' checked, then re-run." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
$pyVersion = python --version 2>&1
|
|
Write-Host " Found: $pyVersion" -ForegroundColor Green
|
|
|
|
# ── 2. Word check ─────────────────────────────────────────────────────────────
|
|
Write-Host "Checking Microsoft Word..." -ForegroundColor Yellow
|
|
try {
|
|
$word = New-Object -ComObject Word.Application
|
|
$wordVersion = $word.Version
|
|
$word.Quit()
|
|
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null
|
|
Write-Host " Found: Microsoft Word $wordVersion" -ForegroundColor Green
|
|
} catch {
|
|
Write-Host "ERROR: Microsoft Word not found or COM registration broken." -ForegroundColor Red
|
|
Write-Host " Install Microsoft Word and retry." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
# ── 3. Create application directory ──────────────────────────────────────────
|
|
Write-Host "Creating $AppDir..." -ForegroundColor Yellow
|
|
New-Item -ItemType Directory -Path $AppDir -Force | Out-Null
|
|
New-Item -ItemType Directory -Path "$AppDir\logs" -Force | Out-Null
|
|
New-Item -ItemType Directory -Path "$AppDir\temp" -Force | Out-Null
|
|
Copy-Item -Path "$PSScriptRoot\docserver_worker.py" -Destination $AppDir -Force
|
|
Copy-Item -Path "$PSScriptRoot\requirements.txt" -Destination $AppDir -Force
|
|
Write-Host " Files copied." -ForegroundColor Green
|
|
|
|
# ── 4. Install Python dependencies ───────────────────────────────────────────
|
|
Write-Host "Installing Python dependencies..." -ForegroundColor Yellow
|
|
python -m pip install --upgrade pip --quiet
|
|
python -m pip install -r "$AppDir\requirements.txt" --quiet
|
|
# pywin32 post-install COM registration
|
|
$pyPrefix = python -c "import sys; print(sys.prefix)"
|
|
$postInstall = "$pyPrefix\Scripts\pywin32_postinstall.py"
|
|
if (Test-Path $postInstall) {
|
|
python $postInstall -install 2>$null
|
|
Write-Host " pywin32 COM registration done." -ForegroundColor Green
|
|
}
|
|
Write-Host " Dependencies installed." -ForegroundColor Green
|
|
|
|
# ── 5. Write environment config ──────────────────────────────────────────────
|
|
$envContent = @"
|
|
MINIO_ENDPOINT=$MinioEndpoint
|
|
MINIO_PORT=$MinioPort
|
|
MINIO_SECURE=$($MinioSecure.ToString().ToLower())
|
|
MINIO_ACCESS_KEY=$MinioAccessKey
|
|
MINIO_SECRET_KEY=$MinioSecretKey
|
|
MINIO_BUCKET=$MinioBucket
|
|
POLL_INTERVAL=$PollInterval
|
|
LOG_DIR=$AppDir\logs
|
|
TEMP_DIR=$AppDir\temp
|
|
"@
|
|
Set-Content -Path "$AppDir\docserver.env" -Value $envContent -Encoding UTF8
|
|
Write-Host " Config written to $AppDir\docserver.env" -ForegroundColor Green
|
|
|
|
# ── 6. Write startup batch script ────────────────────────────────────────────
|
|
# Reads .env, sets env vars, then starts the worker
|
|
$startScript = @'
|
|
@echo off
|
|
setlocal
|
|
cd /d C:\docserver
|
|
|
|
:: Load environment variables from docserver.env
|
|
for /f "usebackq tokens=1,* delims==" %%a in ("C:\docserver\docserver.env") do (
|
|
if not "%%a"=="" (
|
|
set "line=%%a"
|
|
if not "!line:~0,1!"=="#" set "%%a=%%b"
|
|
)
|
|
)
|
|
|
|
:: Start the worker
|
|
python C:\docserver\docserver_worker.py >> C:\docserver\logs\worker.log 2>&1
|
|
endlocal
|
|
'@
|
|
# Note: the batch script uses delayed expansion so we write it separately
|
|
$startScriptFull = @'
|
|
@echo off
|
|
setlocal enabledelayedexpansion
|
|
cd /d C:\docserver
|
|
|
|
echo [%date% %time%] Starting Performance West Docserver Worker...
|
|
|
|
for /f "usebackq tokens=1,* delims==" %%a in ("C:\docserver\docserver.env") do (
|
|
set "ln=%%a"
|
|
if not "!ln:~0,1!"=="#" (
|
|
if not "%%a"=="" set "%%a=%%b"
|
|
)
|
|
)
|
|
|
|
python C:\docserver\docserver_worker.py
|
|
echo [%date% %time%] Worker exited with code %errorlevel%.
|
|
endlocal
|
|
'@
|
|
Set-Content -Path "$AppDir\start_worker.bat" -Value $startScriptFull -Encoding ASCII
|
|
|
|
# ── 7. Register Task Scheduler task ──────────────────────────────────────────
|
|
Write-Host "Registering Task Scheduler task..." -ForegroundColor Yellow
|
|
|
|
$taskName = "PW-DocserverWorker"
|
|
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
|
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
|
|
|
$action = New-ScheduledTaskAction `
|
|
-Execute "cmd.exe" `
|
|
-Argument "/c `"$AppDir\start_worker.bat`"" `
|
|
-WorkingDirectory $AppDir
|
|
|
|
# Two triggers for self-healing: at boot, plus a repeating 5-minute safety net
|
|
# that relaunches the worker if its process ever dies (crash, manual kill, or a
|
|
# missed boot trigger). MultipleInstances=IgnoreNew keeps it to one instance.
|
|
$atStartup = New-ScheduledTaskTrigger -AtStartup
|
|
$repeat = New-ScheduledTaskTrigger -Once -At (Get-Date) `
|
|
-RepetitionInterval (New-TimeSpan -Minutes 5)
|
|
try { $repeat.Repetition.Duration = 'P3650D' } catch {} # some builds need an explicit long duration
|
|
$trigger = @($atStartup, $repeat)
|
|
|
|
$settings = New-ScheduledTaskSettingsSet `
|
|
-ExecutionTimeLimit (New-TimeSpan -Hours 0) `
|
|
-RestartCount 99 `
|
|
-RestartInterval (New-TimeSpan -Minutes 1) `
|
|
-StartWhenAvailable `
|
|
-MultipleInstances IgnoreNew `
|
|
-AllowStartIfOnBatteries `
|
|
-DontStopIfGoingOnBatteries
|
|
|
|
# Register as the current user with "Run whether user is logged on or not"
|
|
# This allows the task to start at boot without requiring an interactive login.
|
|
# Word COM works in session 0 on Windows Server 2019.
|
|
$password = Read-Host -Prompt "Enter password for $currentUser (needed for 'Run whether logged on or not')" -AsSecureString
|
|
$plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
|
|
[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
|
|
)
|
|
|
|
Register-ScheduledTask `
|
|
-TaskName $taskName `
|
|
-Action $action `
|
|
-Trigger $trigger `
|
|
-Settings $settings `
|
|
-User $currentUser `
|
|
-Password $plainPassword `
|
|
-RunLevel Highest `
|
|
-Description "Performance West DOCX-to-PDF worker (MinIO + Word COM)" | Out-Null
|
|
|
|
Write-Host " Task '$taskName' registered (runs at boot, restarts on failure)." -ForegroundColor Green
|
|
|
|
# ── 8. Start the task now ─────────────────────────────────────────────────────
|
|
Write-Host "Starting worker task..." -ForegroundColor Yellow
|
|
Start-ScheduledTask -TaskName $taskName
|
|
Start-Sleep -Seconds 5
|
|
|
|
# ── 9. Verify ────────────────────────────────────────────────────────────────
|
|
$taskInfo = Get-ScheduledTask -TaskName $taskName
|
|
Write-Host ""
|
|
Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
|
Write-Host " Setup Complete" -ForegroundColor Green
|
|
Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
Write-Host " Task state: $($taskInfo.State)"
|
|
Write-Host " App dir: $AppDir"
|
|
Write-Host " Logs: $AppDir\logs\worker.log"
|
|
Write-Host " Config: $AppDir\docserver.env"
|
|
Write-Host ""
|
|
Write-Host " MinIO endpoint: $MinioEndpoint`:$MinioPort"
|
|
Write-Host " MinIO bucket: $MinioBucket"
|
|
Write-Host " Poll interval: ${PollInterval}s"
|
|
Write-Host ""
|
|
Write-Host " The worker polls minio://$MinioBucket/to-convert/" -ForegroundColor White
|
|
Write-Host " Converted PDFs appear in minio://$MinioBucket/converted/" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " To verify manually, place a .docx in to-convert/ and watch" -ForegroundColor White
|
|
Write-Host " converted/ for the resulting .pdf (should appear within a few seconds)." -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host "═══════════════════════════════════════════════════════════" -ForegroundColor Cyan
|