"""Infer in-progress imaging sessions from server-side logs. The /imaging/status endpoint only sees clients that have reached the WinPE startnet.cmd push (stage_index 2). A bay that gets stuck earlier (no DHCP, TFTP fail, boot.wim download stall) is invisible to the operator. This module tails dnsmasq leases + Apache access log + per-host Samba logs and synthesizes a "session" record per active MAC, with a coarse inferred stage based on which boot assets the client has actually fetched. Output shape matches services/imaging_status so list_sessions() can merge both. Inferred sessions carry source="inferred" and never overwrite a real client-pushed session for the same serial (correlated by MAC when present). """ from __future__ import annotations import os import re import time from datetime import datetime, timezone from typing import Optional import config # Active window: an inferred session is shown if any evidence is newer than # this many seconds. Past that we assume the bay is idle / done / off. INFERRED_ACTIVE_WINDOW_S = 90 * 60 # 90 min # Tail size caps so a giant log doesn't pull the whole file into memory on # each dashboard refresh. APACHE_TAIL_BYTES = 512 * 1024 # 512 KB SAMBA_TAIL_BYTES = 64 * 1024 # 64 KB per file SYSLOG_TAIL_BYTES = 256 * 1024 # 256 KB # Path prefixes Apache serves, mapped to coarse imaging stage signals. # Order matters: we take the highest-numbered match (latest in the boot chain). _APACHE_STAGE_HITS = [ ("/menu.ipxe", ("stage_0_menu", 0)), ("/win11/boot/", ("stage_0_boot_pre", 0)), ("/win11/efi/", ("stage_0_boot_pre", 0)), ("/win11/sources/boot.wim", ("stage_1_wim_get", 1)), ("/win11/sources/", ("stage_1_wim_get", 1)), ] # TFTP bootloader fetches arrive via dnsmasq, not Apache. Mapped same way. _TFTP_STAGE_HITS = [ ("undionly.kpxe", ("stage_0_tftp_bios", 0)), ("ipxe.efi", ("stage_0_tftp_uefi", 0)), ] # Apache combined log: - - [DD/Mon/YYYY:HH:MM:SS +ZZZZ] "GET /path HTTP/x.y" status bytes "ref" "ua" _APACHE_RE = re.compile( r'^(?P\S+)\s+\S+\s+\S+\s+' r'\[(?P[^\]]+)\]\s+' r'"(?P\S+)\s+(?P\S+)\s+\S+"\s+' r'(?P\d+)\s+(?P\S+)' ) _APACHE_TS_FMT = "%d/%b/%Y:%H:%M:%S %z" # dnsmasq syslog: " host dnsmasq-tftp[pid]: sent /tftp/path to ip" # or "dnsmasq-dhcp[pid]: DHCPACK(...) ip mac hostname" _SYSLOG_DNSMASQ_RE = re.compile( r'^(?P\w{3})\s+(?P\d+)\s+(?P