From af5da6c3324bbbb5f000db862a74901b11486b5c Mon Sep 17 00:00:00 2001 From: zwitschi Date: Tue, 11 Aug 2026 11:17:06 +0200 Subject: [PATCH] feat: implement Windows service for Intrusion Detection Flask web app --- intrusion-detection-service.py | 85 ++++++++++++++++++++++++++++++++++ requirements.txt | 3 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 intrusion-detection-service.py diff --git a/intrusion-detection-service.py b/intrusion-detection-service.py new file mode 100644 index 0000000..a931163 --- /dev/null +++ b/intrusion-detection-service.py @@ -0,0 +1,85 @@ +"""Windows service that runs the Intrusion Detection Flask web app. + +Serves the web UI on localhost using the configured defaults +(``http://127.0.0.1:54666`` from ``app.utils.constants``; the ``WEB_HOST`` +and ``WEB_PORT`` environment variables or a local ``.env`` override them). + +Manage with (from the project root, in an activated venv): + python intrusion-detection-service.py install + python intrusion-detection-service.py start + python intrusion-detection-service.py stop + python intrusion-detection-service.py remove +""" + +import os +import sys +import threading + +import servicemanager +import win32event +import win32service +import win32serviceutil + +# Ensure the project root is importable. When the SCM launches the service +# the working directory may be C:\\Windows\\System32, not the project dir. +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from app.utils.constants import WEB_HOST, WEB_PORT # noqa: E402 + + +class IntrusionDetectionService(win32serviceutil.ServiceFramework): + _svc_name_ = "IntrusionDetectionWebUI" + _svc_display_name_ = "Intrusion Detection Web UI" + _svc_description_ = ( + "Serves the Intrusion Detection Flask web app on localhost " + f"(http://{WEB_HOST}:{WEB_PORT})." + ) + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + self._flask_thread = None + + def SvcStop(self): + # Report STOP_PENDING, then signal the main loop; the SCM marks the + # service STOPPED once SvcDoRun returns (after Flask shuts down). + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + win32event.SetEvent(self.hWaitStop) + + def SvcDoRun(self): + servicemanager.LogMsg( + servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STARTED, + (self._svc_name_, '') + ) + try: + self.main() + finally: + servicemanager.LogMsg( + servicemanager.EVENTLOG_INFORMATION_TYPE, + servicemanager.PYS_SERVICE_STOPPED, + (self._svc_name_, '') + ) + + def main(self): + from app.web import serve + + # Flask's app.run() blocks, so run it on a daemon thread and keep the + # service alive here until SvcStop signals the stop event. serve() + # passes use_reloader=False, which is required under the service host + # (the reloader would spawn a child process and break the service). + self._flask_thread = threading.Thread( + target=serve, + kwargs={"host": WEB_HOST, "port": WEB_PORT}, + daemon=True, + name="flask-web", + ) + self._flask_thread.start() + + win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) + + +if __name__ == '__main__': + # Handles both the service-host launch and command-line control + # (install / start / stop / remove). + win32serviceutil.HandleCommandLine(IntrusionDetectionService) diff --git a/requirements.txt b/requirements.txt index 92377ef..7190fa1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +flask paramiko python-dotenv -flask \ No newline at end of file +pywin32