<# GTI Fiscal Agent - Windows operations script (RC1). No Python editing needed. Usage (elevated PowerShell for install/start/stop/restart/uninstall): .\gti-agent.ps1 install create venv, install dependencies, register the Windows Service (does NOT start it) .\gti-agent.ps1 configure [-FromTemplate f] write \.env (secrets are prompted, never echoed, never stored in the package) .\gti-agent.ps1 preflight PREFLIGHT=PASS|FAIL with blockers (read-only) .\gti-agent.ps1 start | stop | restart service control (start refuses to run when preflight fails) .\gti-agent.ps1 status service state + last log lines .\gti-agent.ps1 uninstall [-Purge] remove the service; -Purge also deletes venv/logs/state (never the .env unless -Purge) The package folder is the install folder. Requires Python 3.12 (64-bit) on PATH and the Firebird client (fbclient.dll) already used by the SGV. #> param( [Parameter(Position = 0, Mandatory = $true)] [ValidateSet('install', 'configure', 'preflight', 'start', 'stop', 'restart', 'status', 'uninstall')] [string]$Command, [string]$FromTemplate, [switch]$Purge ) $ErrorActionPreference = 'Stop' $Root = Split-Path -Parent $MyInvocation.MyCommand.Path $ServiceName = 'GTIFiscalAgent' $Venv = Join-Path $Root 'venv' $Py = Join-Path $Venv 'Scripts\python.exe' $EnvFile = Join-Path $Root '.env' $LogFile = Join-Path $Root 'agent\logs\agent.log' function Assert-Admin { $principal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Run this command from an elevated (Administrator) PowerShell.' } } function Read-Plain([string]$Prompt, [string]$Default = '') { $suffix = if ($Default) { " [$Default]" } else { '' } $value = Read-Host "$Prompt$suffix" if ([string]::IsNullOrWhiteSpace($value)) { return $Default } else { return $value.Trim() } } function Read-Secret([string]$Prompt) { $secure = Read-Host $Prompt -AsSecureString $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) try { return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } } switch ($Command) { 'install' { Assert-Admin if (-not (Get-Command python -ErrorAction SilentlyContinue)) { throw 'Python 3.12 (64-bit) is required on PATH.' } if (-not (Test-Path $Py)) { python -m venv $Venv } & $Py -m pip install --upgrade pip | Out-Null & $Py -m pip install -r (Join-Path $Root 'requirements-agent.txt') $post = Join-Path $Venv 'Scripts\pywin32_postinstall.py' if (Test-Path $post) { & $Py $post -install | Out-Null } $svc = Join-Path $Root 'agent\service\gti_agent_windows_service.py' & $Py $svc --startup=auto install sc.exe failure $ServiceName reset= 86400 actions= restart/60000/restart/60000/restart/60000 | Out-Null Write-Host "Service $ServiceName registered (Automatic). Next: .\gti-agent.ps1 configure ; .\gti-agent.ps1 preflight ; .\gti-agent.ps1 start" } 'configure' { if ($FromTemplate) { Copy-Item -Path $FromTemplate -Destination $EnvFile -Force Write-Host "Template copied to $EnvFile. Fill the empty secret values (AGENT_TOKEN, FB_PASSWORD) with a text editor, then run preflight." } else { $hub = Read-Plain 'Hub URL (https://...)' $token = Read-Secret 'AGENT_TOKEN (from register_agent.py)' $agentId = Read-Plain 'AGENT_ID (label for this installation)' $db = Read-Plain 'FIREBIRD_DATABASE_PATH (real path of the SGV .FDB on THIS host)' $fbUser = Read-Plain 'FB_USER (read-only account)' 'SYSDBA' $fbPass = Read-Secret 'FB_PASSWORD' $code = Read-Plain 'COMPANY_CODE (C000004.CODIGO; empty = auto when unambiguous)' $cnpj = Read-Plain 'COMPANY_CNPJ (digits; optional alternative to COMPANY_CODE)' $crtSource = Read-Plain 'COMPANY_CRT_SOURCE (AUTO | EMISSOR | C000004)' 'AUTO' $lines = @( "GTI_HUB_URL=$hub", "AGENT_TOKEN=$token", "AGENT_ID=$agentId", "COMPANY_CODE=$code", "COMPANY_CNPJ=$cnpj", "COMPANY_CRT_SOURCE=$crtSource", "FIREBIRD_DATABASE_PATH=$db", "FB_USER=$fbUser", "FB_PASSWORD=$fbPass", 'FISCAL_MODE=DRY_RUN', 'FIREBIRD_WRITES=0', 'POLL_INTERVAL_SECONDS=5', 'AGENT_VERSION=1.0.0-rc1' ) Set-Content -Path $EnvFile -Value $lines -Encoding UTF8 } # Only Administrators and SYSTEM may read the file that holds the secrets. icacls $EnvFile /inheritance:r /grant:r 'Administrators:(F)' 'SYSTEM:(F)' | Out-Null Write-Host "Configuration saved to $EnvFile (restricted ACL). Secrets are not part of the package." } 'preflight' { if (-not (Test-Path $Py)) { throw 'Run install first.' } & $Py (Join-Path $Root 'agent\scripts\preflight.py') --env-file $EnvFile exit $LASTEXITCODE } 'start' { Assert-Admin & $Py (Join-Path $Root 'agent\scripts\preflight.py') --env-file $EnvFile if ($LASTEXITCODE -ne 0) { throw 'PREFLIGHT=FAIL: fix the blockers above before starting the service.' } Start-Service $ServiceName Write-Host "$ServiceName started." } 'stop' { Assert-Admin; Stop-Service $ServiceName -Force; Write-Host "$ServiceName stopped." } 'restart' { Assert-Admin & $Py (Join-Path $Root 'agent\scripts\preflight.py') --env-file $EnvFile if ($LASTEXITCODE -ne 0) { throw 'PREFLIGHT=FAIL: not restarting.' } Restart-Service $ServiceName -Force Write-Host "$ServiceName restarted." } 'status' { $svc = Get-Service $ServiceName -ErrorAction SilentlyContinue if ($null -eq $svc) { Write-Host 'Service not installed.'; exit 1 } Write-Host ("Service {0}: {1} (StartType {2})" -f $svc.Name, $svc.Status, $svc.StartType) if (Test-Path $LogFile) { Write-Host '--- last log lines ---'; Get-Content $LogFile -Tail 15 } } 'uninstall' { Assert-Admin $svcScript = Join-Path $Root 'agent\service\gti_agent_windows_service.py' if (Get-Service $ServiceName -ErrorAction SilentlyContinue) { Stop-Service $ServiceName -Force -ErrorAction SilentlyContinue if (Test-Path $Py) { & $Py $svcScript remove } } if ($Purge) { foreach ($p in @($Venv, (Join-Path $Root 'agent\logs'), (Join-Path $Root 'agent\agent_state.sqlite'), $EnvFile)) { if (Test-Path $p) { Remove-Item $p -Recurse -Force } } Write-Host 'Service removed; venv, logs, state and .env deleted.' } else { Write-Host 'Service removed. Configuration (.env), logs and state were kept (use -Purge to delete them).' } } }