95 lines
3.8 KiB
Python
95 lines
3.8 KiB
Python
"""Windows Service wrapper around run_agent_service.run() — reuses it as-is,
|
|
does not reimplement any polling/pipeline logic.
|
|
|
|
Requires pywin32 (`pip install pywin32`), Windows-only.
|
|
|
|
Commands (run from an elevated/administrator terminal):
|
|
python gti_agent_windows_service.py install # register the service, Startup=Automatic
|
|
python gti_agent_windows_service.py start
|
|
python gti_agent_windows_service.py stop
|
|
python gti_agent_windows_service.py restart
|
|
python gti_agent_windows_service.py remove # uninstall
|
|
python gti_agent_windows_service.py --startup=auto --username . --password . install
|
|
|
|
Recovery (restart on failure) is not set by pywin32's `install` alone; run once
|
|
after installing:
|
|
sc.exe failure "GTIFiscalAgent" reset= 86400 actions= restart/60000/restart/60000/restart/60000
|
|
|
|
This module only wraps agent/scripts/run_agent_service.py's `run()` — the
|
|
actual polling/heartbeat/pipeline logic lives there, unchanged.
|
|
"""
|
|
import sys
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
AGENT_ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(AGENT_ROOT / "scripts"))
|
|
sys.path.insert(0, str(AGENT_ROOT / "src"))
|
|
|
|
import run_agent_service as agent_service # noqa: E402
|
|
|
|
try:
|
|
import servicemanager
|
|
import win32event
|
|
import win32service
|
|
import win32serviceutil
|
|
except ImportError: # pragma: no cover — only available on Windows with pywin32 installed
|
|
servicemanager = win32event = win32service = win32serviceutil = None
|
|
|
|
|
|
class _ServiceStopFlag:
|
|
"""Plain stop flag (no signal.signal registration) — safe to flip from any
|
|
thread. The Windows Service Control Manager stops the service, not a
|
|
console SIGINT/SIGTERM, so this replaces GracefulStop for this host."""
|
|
def __init__(self):
|
|
self.stop = False
|
|
|
|
|
|
if win32serviceutil is not None:
|
|
|
|
class GTIFiscalAgentService(win32serviceutil.ServiceFramework):
|
|
_svc_name_ = "GTIFiscalAgent"
|
|
_svc_display_name_ = "GTI Fiscal Agent"
|
|
_svc_description_ = "Reads the local Firebird ERP, polls the GTI Fiscal Hub for jobs, and applies guarded fiscal updates."
|
|
|
|
def __init__(self, args):
|
|
win32serviceutil.ServiceFramework.__init__(self, args)
|
|
self.stop_event = win32event.CreateEvent(None, 0, 0, None)
|
|
self.stop_flag = _ServiceStopFlag()
|
|
self._thread = None
|
|
|
|
def SvcStop(self):
|
|
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
|
|
self.stop_flag.stop = True
|
|
win32event.SetEvent(self.stop_event)
|
|
|
|
def SvcDoRun(self):
|
|
servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE, servicemanager.PYS_SERVICE_STARTED, (self._svc_name_, ""))
|
|
self._thread = threading.Thread(target=self._run_agent, daemon=True)
|
|
self._thread.start()
|
|
win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE)
|
|
|
|
def _run_agent(self):
|
|
import os
|
|
agent_service.load_dotenv(Path(os.environ.get("GTI_AGENT_ENV_FILE") or agent_service.PROJECT_ROOT / ".env"))
|
|
config = agent_service.Config(os.environ)
|
|
agent_service.setup_logging(config.log_path)
|
|
try:
|
|
agent_service.enforce_startup_preflight(os.environ) # fail closed: a failing preflight stops the service
|
|
except SystemExit:
|
|
self.stop_flag.stop = True
|
|
servicemanager.LogErrorMsg("GTIFiscalAgent: startup preflight failed; see the agent log")
|
|
win32event.SetEvent(self.stop_event)
|
|
return
|
|
agent_service.run(config, os.environ, stopper=self.stop_flag)
|
|
|
|
|
|
def main():
|
|
if win32serviceutil is None:
|
|
raise SystemExit("pywin32 is required: pip install pywin32")
|
|
win32serviceutil.HandleCommandLine(GTIFiscalAgentService)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|