[{"section":"guides","text":"Cross-platform printer/copier fleet management for MSPs, MPS providers, and IT departments.\nQuick Start Document Description Installation Guide Install on Windows, Linux, macOS, Docker Getting Started First steps after installation User Guides Document Description Features Guide All features explained with examples Configuration Config files, environment variables, UI settings Troubleshooting Common issues and solutions FAQ Frequently asked questions Deployment Document Description Docker Deployment Docker and Docker Compose setup Database Upgrade PostgreSQL and TimescaleDB upgrade procedure Unraid Deployment Unraid-specific installation API Reference Document Description API Reference REST API for agent and server What is PrintMaster? PrintMaster consists of two components:\nAgent A lightweight service that runs at each site:\nDiscovers printers on your network via SNMP Collects page counts, toner levels, device info Local web UI for management Can run standalone or report to server Server Central hub for managing multiple agents:\nAggregates data from all agents Fleet view dashboard Remote agent access via WebSocket proxy Multi-tenant support Architecture ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Agent │────────▶│ Server │◀────────│ Agent │ │ Site A │ │ (Central) │ │ Site B │ └─────────────┘ └─────────────┘ └─────────────┘ ↓ ↓ ↓ Printers Web Dashboard Printers Getting Help GitHub Issues — Bug reports GitHub Discussions — Questions and ideas For Developers See Developer Documentation for:\nBuild instructions and project structure Internal architecture and design Contributing guidelines ","title":"PrintMaster Documentation","url":"/guides/"},{"section":"deployment","text":"Deploy PrintMaster across your infrastructure. Start with Docker for a central server, then install agents at each site.\n","title":"Deployment","url":"/deployment/"},{"section":"api","text":"REST API documentation for PrintMaster Agent and Server.\nAgent API Base URL: http://localhost:8080 (default agent port)\nDevice Management List Discovered Devices GET /devices/discovered Returns devices found but not yet saved.\nResponse:\n[{ \"IP\": \"10.0.0.100\", \"Manufacturer\": \"HP\", \"Model\": \"LaserJet Pro M404n\", \"Serial\": \"JPBCD12345\", \"PageCount\": 12453, \"TonerLevels\": {\"Black\": 45} }] List Saved Devices GET /devices/list Returns all saved devices with full metadata.\nGet Device Profile GET /api/devices/profile?serial={serial} Get canonical device profile by serial number.\nResponse:\n{ \"device\": { /* Device object */ }, \"latest_metrics\": { /* MetricsSnapshot or null */ } } Save Device POST /devices/save Content-Type: application/json {\"serial\": \"JPBCD12345\"} Save All Discovered POST /devices/save/all Delete Device POST /devices/delete Content-Type: application/json {\"serial\": \"JPBCD12345\"} Permanently removes device and all history.\nUpdate Device Metadata POST /devices/update Content-Type: application/json { \"serial\": \"JPBCD12345\", \"asset_number\": \"IT-2024-001\", \"location\": \"3rd Floor Copy Room\", \"description\": \"Main office printer\" } Discovery Start Discovery Scan POST /discover Scans configured IP ranges and local subnet.\nMetrics Collect Device Metrics POST /devices/metrics/collect Content-Type: application/json {\"serial\": \"JPBCD12345\"} Get Latest Metrics GET /api/devices/metrics/latest?serial={serial} Get Metrics History GET /api/devices/metrics/history?serial={serial}\u0026since={iso}\u0026until={iso} Query params use RFC3339 timestamps.\nSettings Get Settings GET /settings Response:\n{ \"discovery\": { \"subnet_scan\": true, \"manual_ranges\": true, \"ranges_text\": \"10.0.0.1-10.0.0.254\", \"enable_snmp\": true, \"snmp_timeout_ms\": 2000, \"discover_concurrency\": 20 } } Update Settings POST /settings Content-Type: application/json { \"discovery\": { \"subnet_scan\": true, \"snmp_timeout_ms\": 3000 } } Partial updates supported.\nReal-Time Updates Server-Sent Events GET /events SSE stream for real-time UI updates.\nEvents:\nconnected - Connection established discovery_update - Discovery progress device_change - Device updated const eventSource = new EventSource('/events'); eventSource.addEventListener('discovery_update', (e) =\u003e { console.log('Progress:', JSON.parse(e.data)); }); Logging Get Recent Logs GET /logs?level=INFO\u0026tail=100 Download Log File GET /logfile Server API Base URL: http://localhost:9090 (default server port)\nHealth Check GET /api/v1/health Agent Registration Register Agent POST /api/v1/agents/register Content-Type: application/json { \"agent_id\": \"uuid\", \"name\": \"Office Agent\", \"version\": \"0.23.6\" } Agent Heartbeat POST /api/v1/agents/heartbeat Content-Type: application/json { \"agent_id\": \"uuid\", \"device_count\": 15 } WebSocket Connection Agent WebSocket WS /api/v1/agents/ws Real-time communication channel for agent-server communication.\nFleet Management List Agents GET /api/v1/agents Get Agent Details GET /api/v1/agents/{agent_id} List All Devices GET /api/v1/devices Aggregated view across all agents.\nAuthentication Agent API The agent UI supports multiple authentication modes:\nMode Behavior local No login required; admin tasks require loopback access server Delegates authentication to central server disabled No authentication (development only) Configure in config.toml:\n[web.auth] mode = \"local\" allow_local_admin = true Server API The server requires authentication for most endpoints.\nLogin POST /api/v1/auth/login Content-Type: application/json { \"username\": \"admin\", \"password\": \"your-password\" } Response:\n{ \"token\": \"session-token\", \"expires_at\": \"2025-12-29T12:00:00Z\" } Include the token in subsequent requests:\nAuthorization: Bearer {token} Error Handling HTTP Status Codes:\n200 OK - Success 400 Bad Request - Invalid parameters 401 Unauthorized - Authentication required 404 Not Found - Resource not found 500 Internal Server Error - Server error Error Response:\n{ \"error\": \"Device not found\", \"details\": \"No device with serial JPBCD12345\" } Examples JavaScript: Discover and Save // Start discovery await fetch('/discover', {method: 'POST'}); // Get discovered devices const resp = await fetch('/devices/discovered'); const devices = await resp.json(); // Save first device await fetch('/devices/save', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({serial: devices[0].Serial}) }); curl: Update Settings curl -X POST http://localhost:8080/settings \\ -H \"Content-Type: application/json\" \\ -d '{ \"discovery\": { \"subnet_scan\": true, \"snmp_timeout_ms\": 3000 } }' PowerShell: Get Device List $devices = Invoke-RestMethod -Uri \"http://localhost:8080/devices/list\" $devices | ForEach-Object { \"$($_.Model) - $($_.Serial)\" } See Also Configuration Guide Features Guide ","title":"API Reference","url":"/api/"},{"section":"development","text":"Technical documentation for PrintMaster development. For user documentation, see /docs.\nCurrent Version: Agent v0.23.6, Server v0.23.6\nStart Here TODO.md – Consolidated pending features and improvements PROJECT_STRUCTURE.md – Repository layout and module overview BUILD_WORKFLOW.md – Build/test/release commands + VS Code tasks User Documentation (Moved) These docs are now in the parent /docs folder:\nAPI Reference – REST API for agent and server Configuration – Config files and environment variables Docker Deployment – Container deployment Unraid Deployment – Unraid-specific setup Architecture \u0026 Internals SECURITY_ARCHITECTURE.md – Authentication/authorization design WEBSOCKET_PROXY.md – Server proxy tunnel details SNMP_REFERENCE.md – OIDs, vendor detection, discovery process SNMP_RESEARCH_NOTES.md – Protocol research and notes Printer-MIB.mib – Standard Printer-MIB file RANGE_SYNTAX.md – IP range syntax documentation USB_IMPLEMENTATION.md – USB printer support via IPP-USB proxy (Windows only) Development \u0026 Testing TESTING.md – Testing strategy and patterns TEST_COVERAGE_ANALYSIS.md – Coverage status and gaps Feature Plans (In Progress) AUTO_UPDATE_PLAN.md – Agent/server auto-update implementation EPSON_REMOTE_MODE_PLAN.md – Epson remote-mode integration Reference DEPRECATIONS.md – Removed features and migration notes vendor/ – Vendor-specific OID documentation For condensed AI/assistant guidance, see .github/copilot-instructions.md\n","title":"PrintMaster Developer Documentation","url":"/development/"},{"section":"components","text":"Implementation reference for PrintMaster components. These documents were originally maintained beside their source code.\n","title":"Components","url":"/components/"},{"section":"project","text":"PrintMaster is open-source printer fleet management. Learn about the project, contribute improvements, or report a security issue privately.\n","title":"Project","url":"/project/"},{"section":"components","text":"Location: agent/agent/\nThe agent module handles network discovery protocols, device detection, SNMP operations, and live discovery methods. It sits between the scanner (device querying) and storage (persistence) layers.\nArchitecture Overview agent/ ├── detect.go # Main Discover() function, IP enumeration, ARP/ICMP ├── probe.go # TCP port probing, connectivity checks ├── parse.go # SNMP PDU parsing, OID interpretation ├── snmp.go # Legacy SNMP operations (being phased out) ├── snmp_iface.go # SNMP interface definitions ├── mdns.go # mDNS/Bonjour discovery ├── ssdp.go # SSDP/UPnP discovery ├── wsdiscovery.go # WS-Discovery protocol ├── snmptraps.go # SNMP trap listener ├── llmnr.go # LLMNR name resolution ├── arp.go # ARP table reading ├── merge.go # Device data merging and deduplication ├── helpers.go # Utility functions ├── metrics.go # Performance metrics collection ├── report.go # Scan result reporting ├── types.go # Data structures (PrinterInfo, ScanMeta, etc.) ├── config.go # Configuration structures ├── diagnostics.go # Diagnostic logging and debugging ├── update.go # Device update and refresh logic └── vendor_roots.go # Vendor OID root definitions Discovery Methods 1. Active Network Scanning (detect.go) Purpose: Enumerate IP addresses and perform liveness checks.\nKey Functions:\nDiscover(ctx, ranges, mode, config, store, concurrency, timeout): Main discovery entry point GetLocalSubnets(): Enumerate local network interfaces and subnets EnumerateIPs(cidr): Generate all IPs in CIDR range Discovery Modes:\n\"full\": Complete scan (ARP + ICMP + TCP + SNMP) \"quick\": Fast scan (TCP ports only) \"deep\": Full scan + extended SNMP walks Flow:\nParse IP ranges (CIDR notation or range syntax) Enumerate all IPs in ranges Read ARP table for known devices Probe IPs with TCP/ICMP (parallel worker pool) Query promising devices with SNMP Store results in database Example:\nconfig := \u0026DiscoveryConfig{ ARPEnabled: true, ICMPEnabled: true, TCPEnabled: true, SNMPEnabled: true, } results, err := Discover(ctx, []string{\"192.168.1.0/24\"}, \"full\", config, db, 50, 10) 2. mDNS/Bonjour Discovery (mdns.go) Purpose: Passive discovery of printers advertising via mDNS/DNS-SD.\nKey Functions:\nStartMDNS(ctx, callback): Listen for mDNS advertisements Services monitored: _ipp._tcp: Internet Printing Protocol _ipps._tcp: IPP over TLS _printer._tcp: Generic printer service How It Works:\nListens on multicast address 224.0.0.251:5353 Receives mDNS announcements from printers Extracts IP, hostname, service info from TXT records Calls callback for SNMP enrichment Best For: macOS/Linux networks, modern IPP-capable printers, zero-configuration environments\n3. SSDP/UPnP Discovery (ssdp.go) Purpose: Discover devices via UPnP/SSDP protocol.\nKey Functions:\nStartSSDP(ctx, callback): Listen for SSDP notifications SendSSDP_MSearch(): Active discovery broadcast How It Works:\nListens on multicast 239.255.255.250:1900 Receives NOTIFY messages from devices Sends M-SEARCH queries every 5 minutes Filters for printer-like device types Device Type Filters:\nprinter, scanner, multifunction UPnP device types containing “print” keyword Best For: Consumer printers, UPnP-enabled devices, mixed vendor environments\n4. WS-Discovery (wsdiscovery.go) Purpose: Discover printers via Web Services Discovery protocol.\nKey Functions:\nStartWSDiscovery(ctx, callback): Listen for WSD messages SendWSProbe(): Active probe for WSD devices How It Works:\nListens on multicast 239.255.255.250:3702 SOAP-based protocol over UDP Receives Hello/Bye messages Sends Probe requests for active discovery Message Types:\nHello: Device announces presence Bye: Device announces departure Probe: Agent requests device responses ProbeMatch: Device responds to probe Best For: Windows environments, enterprise printers (HP, Canon, Epson), corporate networks\n5. SNMP Traps (snmptraps.go) Purpose: Listen for SNMP trap notifications from printers.\nKey Functions:\nStartSNMPTraps(ctx, callback): Listen on UDP 162 Processes SNMPv1 and SNMPv2c traps How It Works:\nBinds to UDP port 162 (requires admin/root) Receives trap notifications from configured printers Extracts source IP from trap Calls callback for SNMP enrichment 10-minute throttle to prevent duplicate discoveries Common Traps:\nDevice status changes Supply level alerts (toner low, paper out) Error conditions (paper jam, cover open) Warmup/cooldown events Configuration Required: Printers must be configured to send traps to agent’s IP address\nBest For: Enterprise environments, proactive monitoring, real-time status updates\n6. LLMNR (llmnr.go) Purpose: Link-Local Multicast Name Resolution for Windows networks.\nKey Functions:\nStartLLMNR(ctx, callback): Listen for LLMNR queries/responses How It Works:\nListens on multicast 224.0.0.252:5355 Windows alternative to mDNS Resolves hostnames to IPs on local network Enriches discovered IPs with hostnames Best For: Windows-only networks without DNS, workgroup environments\n7. ARP Table Reading (arp.go) Purpose: Extract recently-seen devices from OS ARP cache.\nKey Functions:\nGetARPTable(): Read system ARP cache Cross-platform implementation (Windows, Linux, macOS) How It Works:\nLinux: Parses /proc/net/arp Windows: Executes arp -a command macOS: Executes arp -an command Returns IP → MAC address mappings Best For: Initial seed of known devices, offline discovery, passive monitoring\nData Structures PrinterInfo (types.go) Core data structure representing a discovered printer:\ntype PrinterInfo struct { IP string MAC string Hostname string Vendor string Model string Serial string Location string Contact string Description string PageCount int64 ColorPageCount int64 DiscoveryMethods []string OpenPorts []int SNMPAvailable bool LastSeen time.Time ExtendedMetrics map[string]interface{} } DiscoveryConfig (config.go) Controls which discovery methods are enabled:\ntype DiscoveryConfig struct { ARPEnabled bool ICMPEnabled bool TCPEnabled bool SNMPEnabled bool MDNSEnabled bool } ScanMeta (types.go) Metadata about scan operations:\ntype ScanMeta struct { StartTime time.Time EndTime time.Time IPsScanned int DevicesFound int Errors []string } Integration with Scanner The agent module delegates device querying to the scanner package:\n// In detect.go import \"printmaster/agent/scanner\" // Enrich discovered IP with SNMP data pi, err := scanner.QueryDevice(ctx, ip, \"public\", timeout) Separation of Concerns:\nAgent: Network discovery, protocol handling, IP enumeration Scanner: SNMP queries, vendor detection, data parsing Probing and Detection (probe.go) Purpose: Fast connectivity checks before expensive SNMP queries.\nKey Functions:\nProbeTCPPorts(ip, ports, timeout) []int: Test open TCP ports PingHost(ip, timeout) bool: ICMP echo request Printer Ports:\n9100: HP JetDirect (raw TCP printing) 631: IPP/IPPS (Internet Printing Protocol) 515: LPD (Line Printer Daemon) 80/443: HTTP/HTTPS (web interface) Probing Strategy:\nTry TCP connect to printer ports (fast) If any printer port open → likely printer Proceed to SNMP enrichment If no printer ports → skip SNMP (save time) Parsing and Data Extraction (parse.go) Purpose: Parse SNMP PDUs and extract printer information.\nKey Functions:\nParsePrinterInfo(pdus []gosnmp.SnmpPDU) PrinterInfo ParseSupplyLevels(pdus) []SupplyInfo ParseCounters(pdus) map[string]int64 OID Mapping:\n1.3.6.1.2.1.1.5.0 → sysName (hostname) 1.3.6.1.2.1.25.3.2.1.3.1 → hrDeviceDescr (model) 1.3.6.1.2.1.43.5.1.1.17.1 → prtGeneralSerialNumber 1.3.6.1.2.1.43.10.2.1.4.1.1 → prtMarkerLifeCount (page count) Helpers (helpers.go):\nDecodeOctetString(bytes) string: Handle non-UTF8 SNMP strings CoerceToInt(interface{}) (int64, bool): Parse numeric values from hex/decimal Merging and Deduplication (merge.go) Purpose: Combine data from multiple discovery sources.\nKey Functions:\nMergeDiscoveredDevice(existing, new PrinterInfo) PrinterInfo DeduplicateBySerial(devices) []PrinterInfo Merge Strategy:\nPrefer non-empty values (new overwrites empty) Combine discovery methods array Union of open ports Most recent LastSeen timestamp Deduplicate by serial number (handle multiple IPs for same device) Diagnostics and Debugging (diagnostics.go) Purpose: Generate diagnostic files for troubleshooting.\nKey Functions:\nWriteDiagnostics(ip, data, filename): Save debug JSON DumpSNMPWalk(ip, pdus): Log full SNMP walk Diagnostic Files:\nlogs/parse_debug_\u003cip\u003e.json: SNMP parsing details logs/mib_walk_\u003cip\u003e.json: Full OID walk results logs/discovered_printers.json: All discovered devices When Generated:\nOn parsing errors (parse_debug) On new device discovery (discovered_printers) On SNMP walk completion (mib_walk) Performance and Concurrency Worker Pools Discovery uses bounded worker pools to control concurrency:\n// Liveness probes: 100-200 workers // SNMP queries: 20-50 workers // Deep scans: 3-10 workers Semaphore Pattern:\nsem := make(chan struct{}, maxConcurrency) for _, ip := range ips { sem \u003c- struct{}{} // Acquire go func(ip string) { defer func() { \u003c-sem }() // Release // Probe IP }(ip) } Rate Limiting SNMP Query Throttling:\nConfigurable delay between queries (default: 0ms) Per-device timeout (default: 2000ms) Retry with exponential backoff Discovery Throttling:\n10-minute minimum between SNMP trap re-discoveries 5-minute interval for SSDP M-SEARCH broadcasts Configuration Agent behavior is controlled via:\nEnvironment: config.ini or config.json Database: Settings stored in SQLite Runtime: Passed via function parameters Example Configuration:\n{ \"snmp_community\": \"public\", \"snmp_timeout_ms\": 2000, \"snmp_retries\": 1, \"discover_concurrency\": 50, \"discovery_methods\": { \"arp\": true, \"icmp\": true, \"tcp\": true, \"snmp\": true, \"mdns\": true, \"ssdp\": true, \"wsd\": true, \"traps\": false } } Testing Test Files:\nparse_test.go: SNMP parsing logic (15 tests) probe_test.go: TCP/ICMP probing (8 tests) rangeparser_test.go: IP range parsing (12 tests) replay_parse_test.go: Real-world SNMP data replay (5 tests) Run Tests:\ncd agent go test ./agent/... -v Error Handling Common Errors:\nSNMP timeout: Device offline or SNMP disabled Permission denied: Requires admin for ICMP/trap listener Port in use: Another service using UDP 162/5353 Invalid CIDR: Malformed IP range syntax Recovery Strategies:\nContinue on individual device failures Log errors but don’t halt discovery Retry SNMP queries with backoff Graceful degradation (skip unavailable protocols) Integration Points With Scanner (agent/scanner/) QueryDevice(): SNMP enrichment IsPrinterDevice(): Confidence scoring Vendor detection and parsing With Storage (agent/storage/) UpsertDevice(): Save discovered printer GetDevices(): Load saved devices UpdateMetrics(): Store periodic metrics With Logger (agent/logger/) Structured logging with levels Rate-limited warnings (prevent log spam) SSE broadcasting to UI Related Documentation Scanner Module - SNMP querying and vendor profiles Logger Module - Logging system Live Discovery TODO (legacy document unavailable) - Discovery method status API Reference - HTTP endpoints Settings TODO (legacy document unavailable) - Future features ","title":"Agent Module Documentation","url":"/components/agent/historical-overview/"},{"section":"development","text":"This document captures the agreed strategy for server- and agent-driven updates, including manifest signing, installer repackaging, and rollout choreography. It serves as a checklist we can iterate through incrementally.\nGoals Enable the PrintMaster server to self-update without user intervention (non-Docker deployments). Allow the server to orchestrate agent updates, including fully background installations triggered from the fleet UI. Provide customized installers (per fleet/tenant) that embed configuration data and can be used for both onboarding and update flows. Maintain trust without external code-signing certificates by using server-signed manifests and checksum validation. Keep Docker deployments opt-in / manual since they already rely on image pulls. High-Level Architecture Release Intake\nServer polls the authoritative release feed (GitHub or artifact bucket) for new agent/server versions. Downloaded artifacts are verified with upstream checksums and cached per platform. Manifest Signing\nServer maintains an Ed25519 signing key pair. For each cached version the server emits a manifest describing version, platform, SHA-256 hash, and supported minor line. Agents embed the public key; all update/install downloads must match a signed manifest. Installer Repackaging\nWhen the admin requests an installer, the server unwraps the official artifact, injects fleet-specific config (join tokens, CA path, policy), then repackages it. Resulting artifacts are available via authenticated endpoints (e.g., /api/v1/installers/{fleet}/{platform}) and reused by auto-update flows. Server Self-Update\nNon-Docker deployments download the new server build, verify the manifest, stage it, and swap binaries with automatic rollback. Docker deployments are detected (env flag / filesystem marker) and instructed to update through container orchestration instead. Agent Auto-Update\nAgents poll the server (per policy) for new updates and fetch repackaged installers. Updates respect version pinning strategy (major: stay on 0.x, minor: stay on 0.9.x, patch: stay on 0.9.14) unless an admin explicitly initiates an upgrade. The agent stages the new build, replaces the running service, and reports status. Policy \u0026 UX\nFleet setting controls cadence (disabled, daily, weekly, monthly) plus target minor version. Agents can override locally (important for air-gapped installs) but default to fleet policy. Server UI shows per-agent status, current/target versions, manual “Update now” actions, and install links. Implementation Checklist Phase 1 – Foundations \u0026 Policy Define fleet-level auto-update settings (cadence, version pinning strategy: major/minor/patch, allow-major-upgrade flag) in server storage schema. Add maintenance window scheduling (time-of-day preferences, timezone support) to avoid business-hour disruptions. Add rollout control settings (staggered deployment, max concurrent updates, jitter, emergency abort flag) to prevent bandwidth saturation. Add agent-side configuration fields for local override, defaulting to fleet settings when connected. Expose settings in server admin UI + API. Add and validate tests for this phase. Phase 2 – Release Intake \u0026 Manifests Implement server job to fetch official release metadata + artifacts for each supported platform. Fetch and cache release notes/changelogs alongside artifacts for UI display and audit logs. Store artifacts in a versioned cache with integrity data (SHA-256, upstream signature if available). Introduce manifest-signing module (Ed25519 key generation, rotation, storage) and embed public key in agent + server binaries. Provide CLI/admin endpoints to rotate signing keys and regenerate manifests. Add and validate tests for this phase. Phase 2 Notes:\nServer exposes /api/v1/releases/signing-keys (list/rotate) and /api/v1/releases/manifests routes gated behind releases.read/release.write scopes. Release intake worker now ensures manifests are generated for every cached artifact; regeneration re-signs existing manifests on key rotation. Tests cover storage schema v5, manager rotation/regeneration, and HTTP handlers to prevent regressions. Phase 3 – Installer Repackaging Service Build packager that unpacks cached release, injects fleet config (join token, CA path, policy), and repacks per OS (ZIP/TAR/MSI wrapper). Ensure sensitive data (tokens) are encrypted at rest within server cache. Add authenticated download endpoints for the customized installers + raw update bundles. Surface “Download installer” button in server UI referencing those endpoints. Add and validate tests for this phase. Phase 3 Notes:\nPackager manager scaffolding is in place with cache TTL enforcement, builder registry, and encryption-at-rest. Remaining work covers config injection, fleet-aware repackaging, API surface, and UI hooks. Phase 4 – Server Self-Update (Non-Docker) Add component that checks for new server version respecting target minor. (Target-minor enforcement still TODO; current implementation compares semantic versions and records skipped/pending runs.) Download + verify manifest/hash, stage binary, back up current version. Integrate with Windows service + Linux systemd to perform controlled restart and rollback on failure. Detect Docker environments and disable automated self-update, displaying guidance instead. Record and expose self-update history/status in UI/logs. Add and validate tests for this phase. Phase 4 Notes:\nselfupdate.Manager now creates self_update_runs records each tick, evaluates cached release artifacts (platform/channel aware), stages newer versions by copying the cached artifact into a run-scoped staging directory, validates the SHA-256 from the manifest, and keeps a backup of the current binary for rollback. Runtime detection now skips self-update work inside container/CI environments so Docker users continue to follow image-based upgrades. A detached helper binary is spawned with a signed instruction file to stop the Windows service or systemd unit, replace the on-disk binary, restart it, and roll back to the backup if the restart fails. Helper runs record success/failure back into self_update_runs so history is persisted automatically. Self-update history is exposed via GET /api/v1/selfupdate/runs and displayed in the Settings \u003e Updates panel in the server UI. Tests cover candidate selection, staging/backup flows, container skips, and the new apply-launch handoff. Phase 5 – Agent Auto-Update Worker Implement agent background worker honoring fleet/local cadence and maintenance windows. Pre-flight checks: verify sufficient disk space for staging + backup before starting download. Request manifest + download from server with exponential backoff retry (max attempts configurable), verify signature/hash, stage update safely. Support HTTP range requests for partial download resume on interrupted transfers. Use existing install scripts (PowerShell/service manager) to replace binaries and restart. Post-update health check: verify server connectivity and basic functionality after restart; trigger rollback if checks fail. Keep previous version for rollback; automatically retry on transient failures. Report progress and telemetry to server (e.g., pending/downloading/installing/restarting/done, download time, success/failure) for UI consumption and metrics. Add and validate tests for this phase. Phase 6 – UI \u0026 Operational UX Server UI: dashboard widgets showing current vs target versions, rollout status, and manual update controls. Display cached release notes/changelogs for pending updates before admin approval. Implement staggered rollout UI controls (percentage/batch size, delay between waves, emergency abort button). Add telemetry dashboard showing update success rate, average download time, rollback frequency per version. Agent UI: settings page showing current policy, next scheduled check, and last update result (read-only unless override enabled). Notification/log integration (e.g., toasts, audit log entries) for update events with changelog snippets. Add and validate tests for this phase. Phase 7 – Testing \u0026 Rollout Unit/integration tests for manifest signing, download verification, and packaging logic. End-to-end tests (possibly via CI) that spin up server + agent, trigger update, and assert version change. Documentation updates (admin guide, deployment notes) covering new features. Gradual rollout plan (beta fleet, staged deployment) before enabling for all installations. Add and validate tests for this phase. Edge Cases \u0026 Notes Docker/Kubernetes: provide environment flag (PM_DISABLE_SELFUPDATE=1) and UI hints; rely on container image updates. Schema migrations: auto-updater must confirm compatibility before applying a version with DB changes (migration gating and backups). Offline agents: if unable to reach server, fall back to local override schedule or postpone until connectivity returns. Security posture: regular key rotation, audit of downloaded artifacts, and strict auth on installer endpoints are mandatory. Rollback triggers: define explicit criteria for automatic rollback (service start failure, post-update health check failure, connectivity loss). Bandwidth management: staggered rollout with jitter prevents simultaneous downloads from overwhelming server/network; configurable per fleet. Maintenance windows: respect local time zones and business hour preferences; defer updates outside configured windows. Network resilience: exponential backoff with jitter for retries; support HTTP range requests to resume interrupted downloads. Disk constraints: pre-flight disk space checks prevent partial installs; alert admins when agents lack sufficient space. Version control: admins can pin to major, minor, or specific patch versions for validation/testing before fleet-wide rollout. Telemetry feedback loop: track success rates and download metrics to identify problematic releases early and inform rollout decisions. Force Reinstall Controls The server UI now exposes a Force Reinstall action on each agent detail view. This button is only enabled when the agent maintains an active WebSocket session so the command can be delivered instantly. Clicking the action prompts for confirmation, then issues a force_update command over the agent command channel. The payload includes a simple reason tag (currently server_ui_force_reinstall) for downstream logging and auditing. Upon receiving the command, the agent’s auto-update manager bypasses the usual isUpdateNeeded guard and downloads/reinstalls the latest manifest even when the reported version already matches. Maintenance-window and version-pin policies are intentionally skipped for this manual override, but disk-space checks, hashing, staging, and telemetry reporting still run. The force flow reuses the existing download/staging pipeline, so telemetry and log noise remain consistent with regular updates, and the helper restart logic still ensures the service restarts cleanly after the reinstall. Every manual check_update or force_update invocation now emits a structured audit log entry capturing the actor, agent identity, payload metadata (reason/trigger), and tenant scope so compliance teams can trace both ad-hoc and scheduled rollouts. Future orchestration jobs should call the shared logAgentUpdateAudit helper to record automated runs with a trigger=scheduled tag. Agent UI Self-Update Controls The agent settings page now includes an Agent Updates panel that surfaces the current/available version, channel, effective policy source, and the timestamps for the last/next scheduled check. Status pills reflect the manager lifecycle (checking, downloading, applying, etc.) so desk-side operators can see whether an update is already running before triggering new work. The panel polls /api/autoupdate/status every 45 seconds and exposes a “Refresh Status” button for on-demand snapshots when troubleshooting. Two local actions are available: Check for Update: POST /api/autoupdate/check, identical to the server-driven check_update command. Force Reinstall: POST /api/autoupdate/force with reason agent_ui_force_reinstall, which bypasses version/policy guards but still enforces disk-space, hashing, and restart health checks. Buttons automatically disable when the auto-update manager is unavailable (agent offline, policy disabled, etc.) or when a run is already in progress, preventing conflicting operations. Callouts highlight when a newer build is available so onsite staff know when a manual reinstall will have an effect. This plan should be treated as a living document; check off tasks as they land and adjust phases as we learn more from early prototypes.\n","title":"Auto-Update \u0026 Installer Repackaging Plan","url":"/development/auto-update-plan/"},{"section":"development","text":"Quick Start (Development) Windows PowerShell Quick Launch Use the helper script to test, build, and launch the agent:\n# From project root (where dev/ exists) pwsh -NoProfile -ExecutionPolicy Bypass .\\dev\\launch.ps1 This script will:\nRun go test ./... (exits if tests fail) Build agent into ./bin/printmaster-agent.exe Start the built binary Open browser to http://localhost:8080 Manual Development Workflow If you prefer manual control:\n# Run tests go test ./... # Build (from project root) go build -o ./bin/printmaster-agent.exe ./agent # Run the agent ./bin/printmaster-agent.exe # Open UI Start-Process 'http://localhost:8080' Note: The launch script is intentionally conservative - tests must pass before build and server start.\nQuick Reference Daily Development # Build agent for development (with debug info) .\\build.ps1 agent # Build server .\\build.ps1 server # Build both .\\build.ps1 both # Run tests .\\build.ps1 test-all # Clean artifacts .\\build.ps1 clean VS Code Tasks (Ctrl+Shift+B) Build: Agent (Dev) - Default build task Build: Server (Dev) - Build server Build: Both (Dev) - Build both components Test: Agent (all) - Run all agent tests Test: Server (all) - Run all server tests Show Version - Display current versions Show Build Log - View recent build output VS Code Debug (F5) Debug: Agent (Default Port) - Launch agent on port 8080 Debug: Agent (Port 9090) - Launch agent on port 9090 Debug: Server (Default Port) - Launch server on port 3000 Debug: Agent + Server Together - Launch both simultaneously Making Releases # Patch release (0.1.0 → 0.1.1) - Bug fixes .\\release.ps1 agent patch # Minor release (0.1.0 → 0.2.0) - New features, backward compatible .\\release.ps1 agent minor # Major release (0.1.0 → 1.0.0) - Breaking changes .\\release.ps1 agent major # Release server .\\release.ps1 server patch # Release both components together .\\release.ps1 both patch What release.ps1 does:\n✅ Checks git status (warns if uncommitted changes) ✅ Bumps version in VERSION file ✅ Runs all tests ✅ Builds release binary (optimized, stripped) ✅ Commits VERSION change ✅ Tags release (e.g., v0.2.0) ✅ Pushes to GitHub Release Flags # Dry run (see what would happen without doing it) .\\release.ps1 agent patch -DryRun # Skip tests (not recommended!) .\\release.ps1 agent patch -SkipTests # Skip GitHub push (for local testing) .\\release.ps1 agent patch -SkipPush Git Workflow # Check status git status # Stage all changes git add -A # Commit git commit -m \"your message\" # Push git push # Or use VS Code tasks: # - Git: Status # - Git: Commit All # - Git: Push # - Git: Pull Semantic Versioning Guide Format: MAJOR.MINOR.PATCH\nPATCH (0.1.0 → 0.1.1) Bug fixes Performance improvements Documentation updates No new features 100% backward compatible Examples:\nFix SNMP parsing error Update vendor OID mapping Improve error messages MINOR (0.1.0 → 0.2.0) New features New functionality Deprecations (with backward compatibility) Backward compatible (existing code still works) Examples:\nAdd new printer vendor support Add metrics export endpoint Add configuration option MAJOR (0.1.0 → 1.0.0) Breaking changes Remove deprecated features Change API contracts NOT backward compatible Examples:\nRemove old API endpoints Change database schema (non-compatible) Change configuration format Pre-1.0 Development During 0.x.x versions, breaking changes are acceptable in MINOR releases since the API is not yet stable. Once you hit 1.0.0, you must follow strict SemVer rules.\nVersion Strategy Agent: Independent versioning (VERSION file at root) Server: Independent versioning (server/VERSION file) Tags: Agent releases: v0.2.0 Server releases: server-v0.2.0 Combined releases: v0.2.0 (both bumped together) CI/CD Integration (Future) When you add GitHub Actions:\n# .github/workflows/release.yml on: push: tags: - 'v*' jobs: release: runs-on: windows-latest steps: - uses: actions/checkout@v3 - name: Build Release run: .\\build.ps1 agent - name: Create GitHub Release # ... attach binaries Troubleshooting “Uncommitted changes detected” # Commit or stash changes first git add -A git commit -m \"description\" # Or stash temporarily git stash .\\release.ps1 agent patch git stash pop “Tests failed” # Run tests manually to see details cd agent go test ./... -v # Fix tests, then retry release “Build failed” # Check build log Get-Content logs\\build.log -Tail 50 # Or use VS Code task: \"Show Build Log\" Release went wrong # Undo last commit (keep changes) git reset HEAD~1 # Restore VERSION file git restore VERSION # Delete tag git tag -d v0.2.0 # Start over Best Practices Always commit working code before releasing Write meaningful commit messages Test locally before pushing Use patch for bug fixes, minor for features Document breaking changes in CHANGELOG.md Tag releases immediately after merge to main Example Workflow # 1. Start feature work git checkout -b feature/new-scanner # 2. Make changes, test locally .\\build.ps1 agent .\\build.ps1 test-all # 3. Commit work git add -A git commit -m \"feat: Add Ricoh network scanner support\" # 4. Merge to main git checkout main git merge feature/new-scanner # 5. Release (minor version - new feature) .\\release.ps1 agent minor # Done! Version bumped, tagged, and pushed to GitHub VS Code Integration All build, test, and release commands are available via:\nCommand Palette (Ctrl+Shift+P): “Tasks: Run Task” Keyboard Shortcuts: Ctrl+Shift+B - Build menu F5 - Start debugging Shift+F5 - Stop debugging Tasks Explorer (Terminal → Run Task) Cross-Platform Testing Testing on Linux (WSL) For cross-platform validation, test on Linux using WSL (Windows Subsystem for Linux):\nInstall Go in WSL (one-time setup) # Download and install Go 1.27.0 wget https://go.dev/dl/go1.27.0.linux-amd64.tar.gz sudo tar -C /usr/local -xzf go1.27.0.linux-amd64.tar.gz rm go1.27.0.linux-amd64.tar.gz # Add to PATH (append to ~/.bashrc) echo 'export PATH=$PATH:/usr/local/go/bin' \u003e\u003e ~/.bashrc source ~/.bashrc # Verify installation go version Run tests on Linux # Navigate to agent directory (WSL can access Windows drives at /mnt/c/) cd /mnt/c/temp/printmaster/agent # Run all tests go test -v ./... # Run specific package tests go test -v ./storage/... Cross-Platform Storage Paths The storage package uses platform-specific paths:\nWindows: %LOCALAPPDATA%\\PrintMaster\\devices.db\n(e.g., C:\\Users\\username\\AppData\\Local\\PrintMaster\\devices.db) Linux: ~/.local/share/PrintMaster/devices.db\n(e.g., /home/username/.local/share/PrintMaster/devices.db) macOS: ~/Library/Application Support/PrintMaster/devices.db All tests pass on Windows, Linux, and macOS, confirming full cross-platform compatibility.\nLast Updated: November 6, 2025\n","title":"Build \u0026 Release Workflow","url":"/development/build-workflow/"},{"section":"guides","text":"Complete reference for configuring PrintMaster agent and server.\nTable of Contents Configuration Methods Agent Configuration Server Configuration Environment Variables Command Line Options Configuration Methods PrintMaster supports multiple configuration methods, applied in this order (later overrides earlier):\nBuilt-in defaults Configuration file (config.toml) Environment variables Database-stored settings (UI changes) Command-line flags Configuration File Location Platform Agent Path Server Path Windows C:\\ProgramData\\PrintMaster\\agent\\config.toml C:\\ProgramData\\PrintMaster\\server\\config.toml Linux /etc/printmaster/agent.toml /etc/printmaster/server.toml macOS /Library/Application Support/PrintMaster/agent/config.toml /Library/Application Support/PrintMaster/server/config.toml Docker /var/lib/printmaster/agent/config.toml /var/lib/printmaster/server/config.toml Or place config.toml in the same directory as the binary.\nAgent Configuration Complete Example # PrintMaster Agent Configuration # Asset ID regex pattern for extracting asset tags from device data asset_id_regex = \"\\\\b\\\\d{5}\\\\b\" # Number of concurrent SNMP queries (adjust based on network capacity) discovery_concurrency = 50 # Enable Epson remote-mode commands (experimental) epson_remote_mode_enabled = false [snmp] # Default SNMP community string community = \"public\" # SNMP timeout in milliseconds timeout_ms = 2000 # Number of retries for failed SNMP queries retries = 1 [web] # HTTP port for web UI http_port = 8080 # HTTPS port (if TLS enabled) https_port = 8443 # Enable TLS/HTTPS enable_tls = false # TLS certificate file (if enable_tls = true) # cert_file = \"/path/to/cert.pem\" # TLS key file (if enable_tls = true) # key_file = \"/path/to/key.pem\" [web.auth] # Authentication mode: local, server, disabled mode = \"local\" # Allow admin access from localhost without login allow_local_admin = true [server] # Enable server upload mode enabled = false # PrintMaster Server URL url = \"http://printmaster-server:9090\" # Friendly name for this agent agent_name = \"Main Office\" # Path to server CA certificate (for self-signed certs) ca_path = \"\" # How often to upload discovery data (seconds) upload_interval_seconds = 300 # How often to send heartbeat (seconds) heartbeat_interval_seconds = 60 # Authentication token (if server requires it) token = \"\" [database] # SQLite database path (blank = default location) path = \"\" [logging] # Log level: debug, info, warn, error level = \"info\" [auto_update] # Update mode: inherit, local, disabled mode = \"inherit\" [auto_update.local_policy] # Days between update checks update_check_days = 7 # Version pin strategy: minor, major version_pin_strategy = \"minor\" # Allow major version upgrades allow_major_upgrade = false # Pin to specific version (blank = latest) target_version = \"\" # Send telemetry data collect_telemetry = true [auto_update.local_policy.maintenance_window] enabled = false timezone = \"UTC\" start_hour = 2 start_min = 0 end_hour = 5 end_min = 0 days_of_week = [\"Sunday\"] [auto_update.local_policy.rollout_control] staggered = true jitter_seconds = 300 SNMP Settings Setting Default Description community public SNMP v1/v2c community string timeout_ms 2000 Query timeout in milliseconds retries 1 Retry attempts for failed queries Tip: If you use a different community string, set it here to avoid manual configuration for each scan.\nWeb UI Settings Setting Default Description http_port 8080 HTTP port for web interface https_port 8443 HTTPS port (when TLS enabled) enable_tls false Enable HTTPS cert_file - Path to TLS certificate key_file - Path to TLS private key Server Connection Settings Setting Default Description enabled false Enable server upload mode url - Server URL (e.g., http://server:9090) agent_name hostname Friendly name for this agent upload_interval_seconds 300 Full sync interval heartbeat_interval_seconds 60 Status ping interval token - Authentication token ca_path - CA cert for self-signed server certs Auto-Update Settings Setting Default Description mode inherit inherit, local, or disabled update_check_days 7 Days between checks version_pin_strategy minor minor or major allow_major_upgrade false Allow major version jumps Server Configuration Complete Example # PrintMaster Server Configuration [web] # HTTP port http_port = 9090 # HTTPS port https_port = 9443 # Enable TLS enable_tls = false # Bind address bind_address = \"0.0.0.0\" [database] # Database type: sqlite, postgres type = \"sqlite\" # SQLite path (if type = sqlite) path = \"/var/lib/printmaster/server/printmaster.db\" # PostgreSQL connection string (if type = postgres) # postgres_url = \"postgres://user:pass@host:5432/printmaster\" [logging] # Log level: debug, info, warn, error level = \"info\" # Log file path (blank = stdout only) file = \"/var/log/printmaster/server/server.log\" [auth] # Session timeout in hours session_timeout_hours = 24 # Allow registration (multi-tenant mode) allow_registration = false [self_update] # Enable server self-update feature enabled = true # Release channel: stable, beta channel = \"stable\" # Check interval in minutes check_interval_minutes = 360 [releases] # Max releases to cache max_releases = 6 # Poll interval for new releases poll_interval_minutes = 240 Database Settings SQLite (Default):\n[database] type = \"sqlite\" path = \"/var/lib/printmaster/server/printmaster.db\" PostgreSQL:\n[database] type = \"postgres\" postgres_url = \"postgres://printmaster:password@localhost:5432/printmaster?sslmode=disable\" Authentication Settings Setting Default Description session_timeout_hours 24 Session expiration time allow_registration false Allow new user registration Environment Variables Both agent and server support environment variable configuration. Variables override config file values.\nCommon Variables Variable Description Default LOG_LEVEL Log level: debug, info, warn, error info CONFIG Path to config file — DB_PATH Database path Component default PM_DISABLE_SELFUPDATE Disable auto-updates false Agent Variables Variable Description Default AGENT_CONFIG Path to config file — AGENT_DB_PATH Agent database path — WEB_HTTP_PORT HTTP port 8080 WEB_HTTPS_PORT HTTPS port 8443 WEB_AUTH_MODE Auth mode: local, server, disabled local WEB_ALLOW_LOCAL_ADMIN Allow localhost admin true SNMP_COMMUNITY SNMP community string public SNMP_TIMEOUT_MS SNMP timeout (ms) 3000 SNMP_RETRIES SNMP retry count 2 DISCOVERY_CONCURRENCY Concurrent scans 100 SERVER_ENABLED Enable server mode false SERVER_URL Central server URL — AGENT_NAME Display name Hostname Server Variables Variable Description Default SERVER_CONFIG Path to config file — SERVER_DB_PATH Server database path — SERVER_HTTP_PORT HTTP port 9090 SERVER_HTTPS_PORT HTTPS port 9443 BIND_ADDRESS Bind address 127.0.0.1 BEHIND_PROXY Behind reverse proxy false TRUSTED_PROXIES Trusted proxy CIDRs Private ranges ADMIN_USER Initial admin username admin ADMIN_PASSWORD Initial admin password printmaster AUTO_APPROVE_AGENTS Auto-approve agents false AGENT_TIMEOUT_MINUTES Agent offline timeout 5 TLS Variables Variable Description Default TLS_MODE none, self-signed, acme, manual self-signed TLS_CERT_PATH Certificate path (manual) — TLS_KEY_PATH Key path (manual) — LETSENCRYPT_DOMAIN Let’s Encrypt domain — LETSENCRYPT_EMAIL Let’s Encrypt email — LETSENCRYPT_ACCEPT_TOS Accept ToS false SMTP Variables Variable Description Default SMTP_ENABLED Enable email false SMTP_HOST SMTP server — SMTP_PORT SMTP port 587 SMTP_USER SMTP username — SMTP_PASS SMTP password — SMTP_FROM Sender address — Docker Example environment: - ADMIN_PASSWORD=secure-password - BIND_ADDRESS=0.0.0.0 - LOG_LEVEL=info - BEHIND_PROXY=true - PM_DISABLE_SELFUPDATE=true systemd Service Example [Service] Environment=SERVER_ENABLED=true Environment=SERVER_URL=https://printmaster.example.com:9443 Environment=AGENT_NAME=office-hq Environment=LOG_LEVEL=info Command Line Options Agent printmaster-agent [options] Options: -config string Path to configuration file -port int HTTP port (default 8080) -data-dir string Data directory path -log-level string Log level: debug, info, warn, error -service string Service command: install, uninstall, start, stop, run -quiet Suppress informational output -help Show help -version Show version Server printmaster-server [options] Options: -config string Path to configuration file -port int HTTP port (default 9090) -data-dir string Data directory path -log-level string Log level: debug, info, warn, error -help Show help -version Show version Configuration via Web UI Most settings can be changed through the web interface:\nAgent UI Settings → Discovery: SNMP settings, concurrency Settings → Server: Server connection settings Settings → Updates: Auto-update preferences Devices → IP Ranges: Networks to scan Server UI Settings → General: Basic server settings Settings → Authentication: User management Settings → Updates: Fleet update policies Settings → Integrations: Webhooks and API Changes made in the UI are saved to the database and take effect immediately. They override file-based configuration.\nConfiguration Precedence When the same setting is configured in multiple places, the last value wins:\nBuilt-in defaults (lowest priority) Configuration file (config.toml) Environment variables Database (UI settings) Command-line flags (highest priority) Example: If you set http_port = 8080 in the config file but run with -port 9000, the agent will use port 9000.\n","title":"Configuration Guide","url":"/guides/configuration/"},{"section":"project","text":"Thank you for your interest in contributing to PrintMaster! This document provides guidelines and information to help you get started.\nTable of Contents Code of Conduct Getting Started Development Setup Project Structure Making Changes Testing Submitting Changes Adding Printer Support Code of Conduct Please be respectful and constructive in all interactions. We’re building something useful together.\nGetting Started Fork the repository on GitHub Clone your fork locally Create a branch for your changes git clone https://github.com/YOUR_USERNAME/printmaster.git cd printmaster git checkout -b feature/your-feature-name Development Setup Prerequisites Go 1.24+ - Download Git - For version control PowerShell (Windows) or Bash (Linux/macOS) - For build scripts Building # Windows - Build agent .\\build.ps1 agent # Windows - Build server .\\build.ps1 server # Windows - Build both .\\build.ps1 both Running Tests # Test agent packages cd agent; go test ./... -v # Test server packages cd server; go test ./... -v Quick Development Workflow # Windows - Kill existing processes, build, and launch .\\dev\\launch.ps1 Project Structure printmaster/ ├── agent/ # Agent binary - printer discovery \u0026 monitoring │ ├── scanner/ # SNMP scanning pipeline │ │ └── vendor/ # Vendor-specific OID profiles │ ├── storage/ # SQLite device/metrics storage │ └── web/ # Embedded web UI ├── server/ # Server binary - multi-agent management │ ├── handlers/ # HTTP API handlers │ ├── storage/ # Database layer │ └── web/ # Embedded web UI ├── common/ # Shared packages (logger, config, etc.) └── docs/ # Design documentation Key documentation:\ndocs/BUILD_WORKFLOW.md - Build, test, and release procedures docs/PROJECT_STRUCTURE.md - Detailed architecture overview docs/ROADMAP.md (legacy document unavailable) - Planned features and priorities Making Changes Code Style Follow standard Go conventions (gofmt, go vet) Use meaningful variable and function names Add comments for non-obvious logic Keep functions focused and reasonably sized Commit Messages Write clear, descriptive commit messages:\ncomponent: short description of change Longer explanation if needed. Explain the \"why\" not just the \"what\". Examples:\nagent/scanner: add Brother MFC series support server/api: fix pagination on device list endpoint docs: update SNMP reference with new OIDs Don’t Don’t write bandaid fixes - If there’s a bug, fix the root cause Don’t edit VERSION files manually - Use .\\release.ps1 for releases Don’t commit large uncommitted diffs - Land changes incrementally Testing Running Tests # Run all tests for a component cd agent \u0026\u0026 go test ./... cd server \u0026\u0026 go test ./... # Run specific test go test -v -run TestFunctionName ./package/ # Run with race detection go test -race ./... Writing Tests Use table-driven tests where appropriate Use t.Parallel() for independent tests Mock external dependencies (SNMP, network, etc.) See existing tests for patterns, e.g., agent/scanner/ tests Test Coverage Before submitting:\nEnsure all existing tests pass Add tests for new functionality Add tests for bug fixes (to prevent regression) Submitting Changes Pull Request Process Update your branch with the latest main:\ngit fetch origin git rebase origin/main Run tests and ensure they pass\nPush your branch and create a Pull Request\nFill out the PR template completely\nRespond to feedback promptly\nPR Guidelines Keep PRs focused - one feature or fix per PR Include tests for new code Update documentation if needed Reference related issues (e.g., “Fixes #123”) Adding Printer Support One of the most valuable contributions is adding support for new printer models!\nQuick Guide Find the vendor file in agent/scanner/vendor/ Add OID mappings for the printer’s SNMP data Test with a real device if possible Submit a PR with the model info Getting SNMP Data If you have access to the printer:\n# Basic device info snmpwalk -v2c -c public \u003cprinter-ip\u003e 1.3.6.1.2.1.1 # Printer MIB snmpwalk -v2c -c public \u003cprinter-ip\u003e 1.3.6.1.2.1.43 # Full walk (large output) snmpwalk -v2c -c public \u003cprinter-ip\u003e 1.3.6.1 Resources docs/SNMP_REFERENCE.md - OID documentation agent/scanner/vendor/ - Existing vendor profiles Open a Printer Support Request if you need help Questions? Check existing issues Open a Question issue Read the documentation Thank you for contributing! 🖨️\n","title":"Contributing to PrintMaster","url":"/project/contributing/"},{"section":"development","text":"This document tracks features that have been removed or are pending removal, with rationale and migration notes.\nRemoved Features Candidate/MIB Profile Workflow Status: ✅ Removed When: Early 2025 What: Agent no longer loads or parses vendor candidate files or MIB profiles. All associated UI and HTTP endpoints removed. Rationale: Simplify agent, reduce maintenance cost, avoid tight coupling to vendor-specific data files Migration: None required. Discovery now relies on Printer-MIB and minimal vendor-agnostic heuristics Cleanup: Any data under mib_profiles/ can be deleted safely Sandbox Simulation Status: ✅ Removed What: Sandbox feature (simulate candidates against saved walks) removed Rationale: Depended on candidates/MIB profiles; added complexity without core value Migration: Use built-in discovery and targeted diagnostic walks /mib_walk HTTP Endpoint Status: ✅ Removed What: On-demand MIB walk HTTP endpoint Rationale: Encourage bounded, targeted walks inside discovery pipeline; avoid broad, ad-hoc walks Migration: Use discovery and “Walk All” device action in UI where applicable; targeted walks occur automatically for confirmed printers /saved_ranges HTTP Endpoint Status: ✅ Removed (Replaced) When: October 2025 What: Legacy IP ranges endpoint Replacement: Use unified /settings endpoint (GET/POST for discovery.ranges_text) Migration: // Old: fetch('/saved_ranges'); // New: const settings = await fetch('/settings').then(r =\u003e r.json()); const ranges = settings.discovery.ranges_text; Manual MIB Walk UI Functions Status: ✅ Removed Location: agent/web/app.js Functions: runMibWalk(), runMibWalkFor(ip) Rationale: Full device information now gathered automatically during discovery; deep scan pipeline provides comprehensive data collection Migration: Automated scanning provides all needed data Removed Features (Previously Pending) Old Logging System Status: ✅ Removed When: December 2025 Location: Was in agent/main.go Functions: logMsg(msg string), logMutex, logBuffer []string Description: Simple timestamp-prefixed logging to in-memory buffer and file Replacement: Structured logger package (common/logger/logger.go) - now used throughout codebase Migration: Complete - all logging now uses appLogger structured logging Deprecated (Still Supported) /settings/subnet_scan HTTP Endpoint Status: ⚠️ Deprecated (still works) Replacement: Use /settings endpoint (GET/POST for discovery.subnet_scan) Migration: // Old: fetch('/settings/subnet_scan'); // New: const settings = await fetch('/settings').then(r =\u003e r.json()); const enabled = settings.discovery.subnet_scan; Removal Date: v1.0 (will be removed in 1.0 release) Migration Checklist When removing deprecated code:\nSearch codebase for all usages Update documentation (API.md, README.md) Add migration guide for users Test thoroughly before removal Mark as ✅ in this document Update CHANGELOG.md Notes Tests and code paths updated to avoid all candidate/MIB profile logic Do not remove anything from “Pending Removal” section without team consensus All removals should follow semantic versioning rules: PATCH: Remove internal deprecated code (not user-facing) MINOR: Deprecate features (add warnings, keep working) MAJOR: Remove deprecated features (breaking change) Last Updated: December 28, 2025\n","title":"Deprecations and Removed Features","url":"/development/deprecations/"},{"section":"development","text":"This document describes the Cloudflare Worker that handles device diagnostic reports for PrintMaster.\nOverview The proxy worker receives diagnostic reports from PrintMaster agents, creates a GitHub Gist with the full diagnostic data, and returns URLs for creating a pre-filled GitHub issue.\nEndpoint URL: https://api.printmaster.work/diagnostic\nMethod: POST\nContent-Type: application/json\nRequest Format { \"report\": { \"report_id\": \"RPT-18B2A3C4D5E6-1234\", \"timestamp\": \"2024-01-15T10:30:00Z\", \"agent_version\": \"0.25.7\", \"os\": \"windows\", \"arch\": \"amd64\", \"issue_type\": \"wrong_manufacturer\", \"expected_value\": \"HP LaserJet Pro M404dn\", \"user_message\": \"Device shows as Unknown instead of HP\", \"device_ip\": \"10.0.1.50\", \"device_serial\": \"VNC1234567\", \"device_model\": \"HP LaserJet Pro M404dn\", \"device_mac\": \"00:11:22:33:44:55\", \"current_manufacturer\": \"Unknown\", \"current_model\": \"Unknown Printer\", \"current_serial\": \"VNC1234567\", \"current_hostname\": \"[hash:a1b2c3d4]\", \"current_page_count\": 15234, \"detected_vendor\": \"Unknown\", \"detection_steps\": [ \"Querying sysDescr (.1.3.6.1.2.1.1.1.0)\", \"No vendor match found\" ], \"snmp_responses\": [ { \"oid\": \".1.3.6.1.2.1.1.1.0\", \"type\": \"OctetString\", \"value\": \"HP LaserJet Pro M404dn\", \"hex_value\": \"\" } ], \"recent_logs\": [ \"2024-01-15T10:29:55Z [INFO] Scanning device 10.0.1.50\" ] } } Response Format Success (200 OK) { \"success\": true, \"gist_url\": \"https://gist.github.com/printmaster-bot/abc123def456\", \"issue_url\": \"https://github.com/printmaster-org/printmaster/issues/new?template=device-report.yml\u0026title=%5BDevice+Report%5D+wrong_manufacturer+%E2%80%93+HP+LaserJet+Pro+M404dn\u0026gist_url=https%3A%2F%2Fgist.github.com%2Fprintmaster-bot%2Fabc123def456\u0026issue_type=Wrong+manufacturer+detection\u0026expected_value=HP+LaserJet+Pro+M404dn\u0026device_model=HP+LaserJet+Pro+M404dn\u0026device_manufacturer=Unknown\" } Error (4xx/5xx) { \"success\": false, \"error\": \"Failed to create gist: rate limit exceeded\" } Cloudflare Worker Implementation Create a new Cloudflare Worker with the following code:\n// wrangler.toml // name = \"printmaster-diagnostic-proxy\" // main = \"src/worker.js\" // compatibility_date = \"2024-01-01\" // // [vars] // GITHUB_REPO = \"printmaster-org/printmaster\" // // [secrets] // GITHUB_PAT = \"ghp_...\" (set via wrangler secret put) export default { async fetch(request, env) { // Handle CORS preflight if (request.method === 'OPTIONS') { return new Response(null, { headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Max-Age': '86400', }, }); } // Only allow POST to /diagnostic const url = new URL(request.url); if (url.pathname !== '/diagnostic' || request.method !== 'POST') { return new Response(JSON.stringify({ success: false, error: 'Not found' }), { status: 404, headers: { 'Content-Type': 'application/json' }, }); } try { const body = await request.json(); const report = body.report; if (!report || !report.report_id) { return new Response(JSON.stringify({ success: false, error: 'Invalid report format' }), { status: 400, headers: corsHeaders('application/json'), }); } // Create Gist with diagnostic data const gistResponse = await fetch('https://api.github.com/gists', { method: 'POST', headers: { 'Authorization': `Bearer ${env.GITHUB_PAT}`, 'Accept': 'application/vnd.github+json', 'User-Agent': 'PrintMaster-Diagnostic-Proxy/1.0', 'X-GitHub-Api-Version': '2022-11-28', }, body: JSON.stringify({ description: `PrintMaster Device Report: ${report.issue_type} - ${report.device_model || 'Unknown'}`, public: false, files: { 'diagnostic_report.json': { content: JSON.stringify(report, null, 2), }, 'summary.md': { content: generateSummary(report), }, }, }), }); if (!gistResponse.ok) { const errorText = await gistResponse.text(); console.error('GitHub API error:', errorText); return new Response(JSON.stringify({ success: false, error: `Failed to create gist: ${gistResponse.status}` }), { status: 502, headers: corsHeaders('application/json'), }); } const gist = await gistResponse.json(); const gistUrl = gist.html_url; // Build pre-filled issue URL const issueUrl = buildIssueUrl(env.GITHUB_REPO, report, gistUrl); return new Response(JSON.stringify({ success: true, gist_url: gistUrl, issue_url: issueUrl, }), { status: 200, headers: corsHeaders('application/json'), }); } catch (error) { console.error('Worker error:', error); return new Response(JSON.stringify({ success: false, error: error.message }), { status: 500, headers: corsHeaders('application/json'), }); } }, }; function corsHeaders(contentType) { return { 'Content-Type': contentType, 'Access-Control-Allow-Origin': '*', }; } function generateSummary(report) { const issueTypeLabels = { 'wrong_manufacturer': 'Wrong Manufacturer Detection', 'wrong_model': 'Wrong Model Detection', 'missing_serial': 'Missing Serial Number', 'wrong_serial': 'Wrong Serial Number', 'incorrect_counters': 'Incorrect Page Counters', 'missing_toner': 'Missing Toner/Ink Levels', 'missing_supplies': 'Missing Supplies Data', 'wrong_hostname': 'Wrong Hostname', 'other': 'Other Issue', }; return `# Device Data Report ## Issue Type ${issueTypeLabels[report.issue_type] || report.issue_type} ## Expected Value ${report.expected_value || 'Not specified'} ## User Message ${report.user_message || 'No additional message'} ## Current Detection Results - **Manufacturer**: ${report.current_manufacturer || 'Unknown'} - **Model**: ${report.current_model || 'Unknown'} - **Serial**: ${report.current_serial || 'Unknown'} - **Page Count**: ${report.current_page_count || 'N/A'} ## Device Info - **IP**: ${report.device_ip || 'Unknown'} - **MAC**: ${report.device_mac || 'Unknown'} - **Detected Vendor**: ${report.detected_vendor || 'Unknown'} ## Agent Info - **Version**: ${report.agent_version || 'Unknown'} - **OS**: ${report.os || 'Unknown'} - **Arch**: ${report.arch || 'Unknown'} ## Detection Steps ${(report.detection_steps || []).map(s =\u003e '- ' + s).join('\\n') || 'No steps recorded'} ## SNMP Responses \\`\\`\\`json ${JSON.stringify(report.snmp_responses || [], null, 2)} \\`\\`\\` --- *Report ID: ${report.report_id}* *Timestamp: ${report.timestamp}* `; } function buildIssueUrl(repo, report, gistUrl) { const issueTypeLabels = { 'wrong_manufacturer': 'Wrong manufacturer detection', 'wrong_model': 'Wrong model detection', 'missing_serial': 'Missing serial number', 'wrong_serial': 'Wrong serial number', 'incorrect_counters': 'Incorrect page counters', 'missing_toner': 'Missing toner/ink levels', 'missing_supplies': 'Missing supplies data', 'wrong_hostname': 'Wrong hostname', 'other': 'Other', }; const title = `[Device Report] ${report.issue_type} – ${report.device_model || 'Unknown Device'}`; const params = new URLSearchParams({ template: 'device-report.yml', title: title, gist_url: gistUrl, issue_type: issueTypeLabels[report.issue_type] || 'Other', expected_value: report.expected_value || '', device_model: report.device_model || '', device_manufacturer: report.current_manufacturer || '', }); return `https://github.com/${repo}/issues/new?${params.toString()}`; } Setup Instructions Create a GitHub Personal Access Token (PAT)\nGo to GitHub Settings \u003e Developer Settings \u003e Personal Access Tokens \u003e Fine-grained tokens Create a token with gist scope (read/write) Note: Use a bot account to avoid rate limits on your personal account Create the Cloudflare Worker\nnpm create cloudflare@latest printmaster-diagnostic-proxy cd printmaster-diagnostic-proxy Configure wrangler.toml\nname = \"printmaster-diagnostic-proxy\" main = \"src/worker.js\" compatibility_date = \"2024-01-01\" [vars] GITHUB_REPO = \"printmaster-org/printmaster\" Set the GitHub PAT secret\nwrangler secret put GITHUB_PAT # Paste your PAT when prompted Deploy the Worker\nwrangler deploy Configure Custom Domain (optional)\nIn Cloudflare Dashboard \u003e Workers \u003e your worker \u003e Settings \u003e Domains \u0026 Routes Add custom domain: api.printmaster.work Rate Limiting The GitHub Gist API has rate limits:\nAuthenticated requests: 5000/hour Consider implementing caching or rate limiting in the worker if needed Privacy Considerations The worker:\nDoes NOT log or store report data beyond the Gist Gists are created as private (unlisted) by default Only the user with the URL can access the Gist IP addresses in reports are pre-anonymized by the agent (private IPs kept, public IPs hashed) Hostnames with company identifiers are hashed by the agent Monitoring Set up Cloudflare Worker analytics to monitor:\nRequest volume Error rates Response times Fallback Behavior If the proxy is unavailable:\nAgent returns the report data to the frontend Frontend downloads the report as a JSON file Frontend opens GitHub issue form with manual instructions User can attach the JSON file to the issue manually ","title":"Device Report Proxy Worker","url":"/development/cloudflare-proxy-worker/"},{"section":"deployment","text":"Deploy PrintMaster Server using Docker containers with multi-architecture support.\nQuick Start docker run -d \\ --name printmaster-server \\ -p 9090:9090 \\ -v printmaster-data:/var/lib/printmaster/server \\ -e ADMIN_PASSWORD=your-secure-password \\ ghcr.io/printmaster-org/printmaster-server:latest Access at http://localhost:9090 with username admin.\nSupported Architectures All images are built for multiple architectures automatically:\nArchitecture Platform Use Case linux/amd64 x86_64 servers Intel/AMD servers, cloud VMs linux/arm64 ARM 64-bit Apple Silicon, AWS Graviton, Raspberry Pi 4+ linux/arm/v7 ARM 32-bit Raspberry Pi 3/4 (32-bit OS) Docker automatically pulls the correct architecture for your platform.\nImage Details Base Image: gcr.io/distroless/static:nonroot\nSize: ~30MB (70% smaller than Alpine-based) Security: No shell, no package manager, minimal attack surface User: Runs as non-root (UID 65532) Image tags:\nlatest - Latest stable release (recommended) v0.23.6 - Specific version Docker Compose Create a docker-compose.yml file:\nversion: '3.8' services: printmaster-server: image: ghcr.io/printmaster-org/printmaster-server:latest container_name: printmaster-server ports: - \"9090:9090\" - \"9443:9443\" # HTTPS (optional) volumes: - printmaster-data:/var/lib/printmaster/server - printmaster-logs:/var/log/printmaster/server environment: - ADMIN_PASSWORD=your-secure-password - BIND_ADDRESS=0.0.0.0 - LOG_LEVEL=info - PM_DISABLE_SELFUPDATE=true restart: unless-stopped volumes: printmaster-data: printmaster-logs: Start with:\ndocker compose up -d Environment Variables Essential Variable Default Description ADMIN_PASSWORD printmaster Set before first run! BIND_ADDRESS 127.0.0.1 Set to 0.0.0.0 for external access LOG_LEVEL info debug, info, warn, error Network \u0026 Ports Variable Default Description SERVER_HTTP_PORT 9090 HTTP port SERVER_HTTPS_PORT 9443 HTTPS port BEHIND_PROXY false Set true if behind reverse proxy PROXY_USE_HTTPS false Proxy terminates SSL TLS/HTTPS Variable Default Description TLS_MODE self-signed none, self-signed, acme, manual TLS_CERT_PATH — Certificate path (manual mode) TLS_KEY_PATH — Key path (manual mode) Let’s Encrypt Variable Description LETSENCRYPT_DOMAIN Domain for certificate LETSENCRYPT_EMAIL Notification email LETSENCRYPT_ACCEPT_TOS Accept ToS (true) Agent Management Variable Default Description AUTO_APPROVE_AGENTS false Auto-approve new agents AGENT_TIMEOUT_MINUTES 5 Timeout before marking offline Container Detection Variable Effect PM_DISABLE_SELFUPDATE Disable self-update (recommended for Docker) CONTAINER=docker Auto-detected, disables self-update See Environment Variables Reference for the complete list.\nBehind a Reverse Proxy Nginx Proxy Manager / Traefik / Caddy environment: - BEHIND_PROXY=true - BIND_ADDRESS=0.0.0.0 - PROXY_USE_HTTPS=true # If proxy handles SSL Reverse proxy requirements:\nForward to port 9090 Enable WebSocket support (required for real-time features) Handle SSL termination Nginx Configuration Example server { listen 443 ssl; server_name printmaster.example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://printmaster-server:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } Volumes \u0026 Data Recommended Volume Mounts Container Path Purpose /var/lib/printmaster/server Database and config /var/log/printmaster/server Log files The TimescaleDB PostgreSQL 18 Compose image mounts its database volume at /var/lib/postgresql. Do not change this to /var/lib/postgresql/data; that older mount layout can prevent the PG18 image from finding its data directory.\nFor PostgreSQL major-version migrations and TimescaleDB restore hooks, see PostgreSQL and TimescaleDB Upgrades.\nBackup # Stop container first for consistent backup docker stop printmaster-server # Backup database docker cp printmaster-server:/var/lib/printmaster/server/server.db ./backup-$(date +%Y%m%d).db # Restart docker start printmaster-server Health Check The distroless image doesn’t include curl/wget. Use external monitoring:\n# From host curl -s http://localhost:9090/api/v1/health # Docker health check (compose v3.8+) healthcheck: test: [\"CMD-SHELL\", \"wget -q -O /dev/null http://localhost:9090/api/v1/health || exit 1\"] interval: 30s timeout: 10s retries: 3 Updating # Pull latest image docker pull ghcr.io/printmaster-org/printmaster-server:latest # Recreate container docker compose down docker compose up -d # Check version docker logs printmaster-server | head -5 PostgreSQL and TimescaleDB upgrades The repository Compose examples use timescale/timescaledb:latest-pg18 for new deployments. Changing a PostgreSQL major version while reusing an existing database volume is not a valid upgrade and can make the database refuse to start. Never delete the old volume as a workaround.\nFor the required backup, new-volume migration, and verification steps, see PostgreSQL and TimescaleDB Upgrades.\nAgent in Docker For specialized deployments (not typical):\ndocker run -d \\ --name printmaster-agent \\ --network host \\ -v printmaster-agent-data:/var/lib/printmaster/agent \\ -e SERVER_ENABLED=true \\ -e SERVER_URL=http://your-server:9090 \\ ghcr.io/printmaster-org/printmaster-agent:latest Note: --network host is required for SNMP discovery to work properly.\nSee Also Unraid Deployment Installation Guide Configuration Guide ","title":"Docker Deployment","url":"/deployment/docker/"},{"section":"development","text":"Overview End-to-end tests verify that the agent and server work together correctly. These tests are more expensive than unit tests but provide confidence that the full system functions as expected.\nQuick Start Run E2E Tests Locally # Linux/macOS cd tests ./run-e2e.sh # Windows cd tests .\\run-e2e.ps1 Options Option Description --build / -Build Force rebuild Docker containers --keep-up / -KeepUp Leave containers running after tests --verbose / -Verbose Show detailed output Architecture Docker Compose Environment The E2E tests use Docker Compose to create an isolated test environment:\n┌─────────────────────────────────────────────────────────┐ │ E2E Test Network │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Server │◀───────▶│ Agent │ │ │ │ :8443 │ WS │ :8080 │ │ │ │ │ │ │ │ │ │ SQLite DB │ │ SQLite DB │ │ │ └─────────────┘ └─────────────┘ │ │ ▲ ▲ │ │ │ │ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │ │ │ Seed Data │ │ Seed Data │ │ │ │ server.db │ │ agent.db │ │ │ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────┐ │ Test Runner │ │ (go test -tags=e2e)│ └─────────────────────┘ Test Data Pre-seeded SQLite databases provide predictable test data:\nServer Database:\n1 tenant: “E2E Test Tenant” 1 registered agent: “e2e-test-agent” 5 test devices (HP, Kyocera, Brother, Lexmark, Xerox) Sample metrics for each device Admin user (password: e2e-test-password) Agent Database:\nFixed agent UUID: e2e00000-0000-0000-0000-000000000001 5 test devices (matching server) Scanner configuration (discovery disabled) Server connection settings Regenerating Test Data # Linux/macOS ./seed-testdata.sh # Windows .\\seed-testdata.ps1 Test Categories 1. Health Checks Server /api/health endpoint Agent /api/health endpoint 2. Agent Registration Agent registers with server on startup Agent UUID matches pre-seeded data WebSocket connection establishment 3. Device APIs Server device list includes seeded devices Agent device list includes seeded devices Device metrics queries 4. Integration Flows Full agent-server communication Device data synchronization Error handling CI Integration E2E Docker tests run automatically in GitHub Actions:\n# .github/workflows/ci.yml e2e-docker: name: E2E (Docker) runs-on: ubuntu-latest needs: [agent, server] steps: - uses: actions/checkout@v4 - name: Seed test databases run: ./tests/seed-testdata.sh - name: Start E2E environment run: docker compose -f tests/docker-compose.e2e.yml up -d --build - name: Run E2E tests run: go test -tags=e2e -v ./tests/... File Structure tests/ ├── docker-compose.e2e.yml # Docker Compose for E2E environment ├── e2e_docker_test.go # E2E tests (build tag: e2e) ├── run-e2e.sh # Linux/macOS helper script ├── run-e2e.ps1 # Windows helper script ├── seed-testdata.sh # Database seed script (Linux/macOS) ├── seed-testdata.ps1 # Database seed script (Windows) └── testdata/ ├── server/ │ └── server.db # Generated server database ├── agent/ │ ├── agent.db # Generated agent database │ └── agent_id # Fixed agent UUID └── seed/ ├── server_seed.sql # Server seed SQL └── agent_seed.sql # Agent seed SQL Troubleshooting Containers won’t start # Check container logs docker compose -f tests/docker-compose.e2e.yml logs server docker compose -f tests/docker-compose.e2e.yml logs agent # Clean up and retry docker compose -f tests/docker-compose.e2e.yml down -v ./run-e2e.sh --build Tests fail with connection errors # Verify containers are healthy docker compose -f tests/docker-compose.e2e.yml ps # Check health endpoints manually curl http://localhost:8443/api/health curl http://localhost:8080/api/health Database schema mismatch # Regenerate seed databases ./seed-testdata.sh # Or rebuild containers to trigger migrations ./run-e2e.sh --build Error handling 3. WebSocket Proxy (New Feature) Proxy to agent UI Proxy to device UI Timeout handling Connection loss handling Multiple concurrent requests 4. Failure Scenarios Server restart while agent connected Agent restart Network interruption Invalid authentication Database errors Running Tests # Run all E2E tests cd tests go test -v ./... # Run specific test go test -v -run TestWebSocketProxy_AgentUI # Skip E2E tests (fast unit tests only) cd agent go test -short ./... cd ../server go test -short ./... CI/CD Integration # .github/workflows/test.yml - name: Run Unit Tests run: | cd agent \u0026\u0026 go test -short ./... cd ../server \u0026\u0026 go test -short ./... - name: Build Binaries run: | ./build.ps1 both - name: Run E2E Tests run: | cd tests \u0026\u0026 go test -v -timeout 5m ./... Next Steps Implement process management helpers\nstartServer() - Start server on ephemeral port startAgent() - Start agent connected to test server cleanup() - Ensure processes are killed Add config generation\nMinimal TOML for server Minimal TOML for agent Temp database paths Implement first working test\nTestAgentServerRegistration Verify agent can register and connect Add WebSocket proxy tests\nTestWebSocketProxy_AgentUI TestWebSocketProxy_DeviceUI TestWebSocketProxy_NoConnection Add to CI/CD\nRun after successful build Report test results Save logs on failure Performance Considerations E2E tests are slow (2-10 seconds each) Use t.Parallel() where possible Don’t run E2E tests by default (use -short flag) Keep E2E test count reasonable (\u003c20 tests) Focus on critical paths and new features Debugging Failed Tests When E2E tests fail:\nCheck logs - Server and agent log output Check ports - Ensure no port conflicts Check cleanup - Previous test may have left processes running Increase timeouts - CI may be slower than local Run locally - Reproduce outside CI environment Example: Complete E2E Test func TestWebSocketProxyComplete(t *testing.T) { // 1. Setup tmpDir := t.TempDir() serverPort := getFreePort(t) agentPort := getFreePort(t) // 2. Start server serverCmd := startServer(t, serverPort, tmpDir) defer serverCmd.Process.Kill() // 3. Wait for server ready if !waitForServer(t, serverPort, 5*time.Second) { t.Fatal(\"Server failed to start\") } // 4. Start agent agentCmd := startAgent(t, agentPort, serverPort, tmpDir) defer agentCmd.Process.Kill() // 5. Wait for connection if !waitForAgentActive(t, serverPort, \"test-agent\", 5*time.Second) { t.Fatal(\"Agent failed to connect\") } // 6. Test proxy resp := makeProxyRequest(t, serverPort, \"test-agent\", \"/\") defer resp.Body.Close() // 7. Verify if resp.StatusCode != 200 { t.Errorf(\"Expected 200, got %d\", resp.StatusCode) } // 8. Cleanup (defers handle this) } Resources Go testing: https://go.dev/doc/tutorial/add-a-test Integration testing: https://go.dev/wiki/TableDrivenTests Test fixtures: https://github.com/golang/go/wiki/TestFixtures ","title":"E2E Testing Strategy","url":"/development/testing/e2e-testing/"},{"section":"development","text":"This directory contains integration/E2E tests that test agent and server components together.\nRunning Tests # Run all E2E tests cd tests go test -v ./... # Run with no cache go test -v -count=1 ./... # Skip E2E tests (in other directories) cd ../agent go test -short ./... Test Coverage WebSocket Proxy Tests (websocket_proxy_test.go) ✅ TestWebSocketProxy_BasicFlow - Complete proxy request/response flow\n✅ TestWebSocketProxy_UnreachableTarget - Error handling for unreachable targets\n✅ TestWebSocketProxy_MultipleRequests - Concurrent proxy requests\nHTTP API Tests (http_api_test.go) ✅ TestHTTPAPI_AgentRegistration - Agent registration endpoint\n✅ TestHTTPAPI_Heartbeat - Agent heartbeat with authentication\n✅ TestHTTPAPI_UnauthorizedAccess - Token validation\n✅ TestHTTPAPI_DeviceUpload - Device batch upload endpoint\nTest Organization websocket_proxy_test.go - WebSocket proxy functionality tests http_api_test.go - HTTP API endpoint tests helpers_test.go - Shared test utilities Test Structure E2E tests use:\nhttptest.NewServer() to create mock HTTP servers websocket.DefaultDialer to test WebSocket connections t.Parallel() to run tests concurrently testing.Short() to allow skipping with -short flag Notes Tests use ephemeral ports to avoid conflicts Tests run in parallel where possible (~1.5s total execution) Mock servers simulate agent and server behavior Tests verify both success and error scenarios ","title":"End-to-End Tests","url":"/development/testing/"},{"section":"development","text":"Device: AM-C550 Series (172.52.105.91)\nEnterprise OID Base: 1.3.6.1.4.1.1248.1.2.2.27.*\nValidation Date: 2025-11-03\nStatus: ✅ Fully validated against device web interface\nDirect OID Mappings (No Calculation Needed) Page Count Totals Metric OID Value Status Total Pages .27.1.1.30.1.1 81,563 ✅ Exact match Total B\u0026W Pages .27.1.1.3.1.1 39,797 ✅ Exact match Total Color Pages .27.1.1.4.1.1 41,766 ✅ Exact match Function-Specific Counters (Combined B\u0026W + Color) Metric OID Value Calculation Total Print from Computer .27.6.1.4.1.1.1 49,053 B\u0026W Print (9,619) + Color Print (39,434) = 49,053 ✅ Total Copy .27.6.1.4.1.1.2 32,474 B\u0026W Copy (30,161) + Color Copy (2,313) = 32,474 ✅ Color-Specific Counters Metric OID Value Status Color Print from Computer .27.6.1.5.1.1.1 39,434 ✅ Exact match Color Copy .27.6.1.5.1.1.2 2,313 ✅ Exact match Calculated Metrics (via Subtraction) B\u0026W Function Breakdown B\u0026W Print from Computer = Total Print from Computer - Color Print from Computer = 49,053 - 39,434 = 9,619 ✅ B\u0026W Copy = Total Copy - Color Copy = 32,474 - 2,313 = 30,161 ✅ Color Function Breakdown (if needed) Color Print Other = Color Total - Color Print from Computer - Color Copy = 41,766 - 39,434 - 2,313 = 19 ✅ B\u0026W Function Breakdown (if needed) B\u0026W Print Other = B\u0026W Total - B\u0026W Print from Computer - B\u0026W Copy = 39,797 - 9,619 - 30,161 = 17 ✅ Scan Counters (Not Yet Found in OIDs) From web UI:\nB\u0026W Scan: 2,309 pages Color Scan: 1,101 pages Status: ⚠️ Not yet located in SNMP data. May be:\nIn a different OID subtree (check .27.7.* or .27.8.*) Not exposed via SNMP (web interface only) Requires walking a different branch Additional Counters Found (Unknown Purpose) Possibly Toner/Supply Related OID Value Possible Meaning .27.11.1.3.1.1.1 1,016 Toner/supply counter? .27.11.1.3.1.1.2 615 Toner/supply counter? .27.11.1.3.1.1.3 694 Toner/supply counter? .27.11.1.3.1.1.4 622 Toner/supply counter? .27.11.1.3.1.1.5 371 Toner/supply counter? .27.11.1.3.1.1.6 341 Toner/supply counter? Possibly Maintenance/Service Counters OID Value Possible Meaning .27.18.1.2.1.1 5,000 Service interval? .27.18.1.3.1.1 10,000 Service interval? .27.18.1.5.1.1 81,535 Total impressions? .27.18.1.6.1.1 2,736 Service counter? .27.18.1.7.1.1 5,683 Service counter? .27.18.1.8.1.1 32,879 Service counter? .27.18.1.9.1.1 160 Service counter? Implementation Strategy for Epson Vendor Module Priority 1: Core Metrics (Already Validated) // In GetMetricsOIDs(): return []string{ // Page count totals \"1.3.6.1.4.1.1248.1.2.2.27.1.1.30.1.1\", // Total pages \"1.3.6.1.4.1.1248.1.2.2.27.1.1.3.1.1\", // B\u0026W pages \"1.3.6.1.4.1.1248.1.2.2.27.1.1.4.1.1\", // Color pages // Function counters (combined) \"1.3.6.1.4.1.1248.1.2.2.27.6.1.4.1.1.1\", // Total print from computer \"1.3.6.1.4.1.1248.1.2.2.27.6.1.4.1.1.2\", // Total copy // Color-specific counters \"1.3.6.1.4.1.1248.1.2.2.27.6.1.5.1.1.1\", // Color print from computer \"1.3.6.1.4.1.1248.1.2.2.27.6.1.5.1.1.2\", // Color copy // Standard Printer-MIB toner levels (fallback) \"1.3.6.1.2.1.43.11.1.1.9.1\", \"1.3.6.1.2.1.43.11.1.1.6.1\", } Priority 2: Calculated Fields // In ExtractMetrics(): snapshot.MonoPages = bwTotal snapshot.ColorPages = colorTotal snapshot.PageCount = bwTotal + colorTotal // Calculate B\u0026W breakdowns snapshot.BWPrintComputer = totalPrintComputer - colorPrintComputer snapshot.BWCopy = totalCopy - colorCopy // Calculate Color breakdowns snapshot.ColorPrintComputer = colorPrintComputer // Direct snapshot.ColorCopy = colorCopy // Direct Priority 3: Extended Metrics (Future) Scan counters (need to locate OIDs) Toner/supply counters (.27.11.1.3.*) Service/maintenance counters (.27.18.1.*) Paper size breakdown (.27.3.1.5.* and .27.3.1.6.*) Print language breakdown Validation Results ✅ All calculated values match device web interface exactly:\nTotal pages: 81,563 B\u0026W pages: 39,797 Color pages: 41,766 B\u0026W Copy: 30,161 Color Copy: 2,313 B\u0026W Print Computer: 9,619 Color Print Computer: 39,434 ✅ OID structure is logical and consistent:\n.27.6.1.4.* = Combined (B\u0026W + Color) counters .27.6.1.5.* = Color-only counters Subtraction yields B\u0026W-only values ⚠️ Scan counters not yet located - requires further investigation\nImplementation Status ✅ COMPLETED ✅ Created Epson vendor module (agent/scanner/vendor/epson.go)\nImplements full VendorModule interface Includes all Priority 1 OIDs Calculation-based metrics for B\u0026W breakdown Capability-aware OID filtering for MFP vs printer-only devices ✅ Validated OID consistency across models:\n✅ AM-C550 (172.52.105.91) - Perfect match ✅ WF-C17590 (172.52.105.93) - Confirmed same OID structure (11-page timing difference expected) ✅ Registered in vendor registry\nEnterprise number 1248 mapped to “Epson” Automatic vendor detection via sysObjectID All tests passing 🔄 TODO Test on remaining Epson models to validate OID consistency:\nWF-C20600 (172.52.105.94) WF-M5799 (172.52.105.97) CW-C6000Au (172.52.105.107) CW-C6500Au (172.52.105.153) ST-M3000 (172.52.105.162) ST-M1000 (172.52.105.196) Investigate scan counter OIDs on devices with known scan activity\nCheck .27.7.*, .27.8.*, or other subtrees Web UI shows B\u0026W Scan and Color Scan counters Not yet found in SNMP walk data Document additional counters in .27.11.* and .27.18.* subtrees\nPossible toner/ink supply counters Possible maintenance/service counters 📦 Deployment Ready The Epson vendor module is production-ready and will automatically be used for:\nAny device with sysObjectID starting with .1.3.6.1.4.1.1248 Any device with manufacturer name containing “Epson” Expected to benefit 6 out of 10 devices (60% of production environment) Notes This mapping is based on the AM-C550 Series, which appears to be an Epson-manufactured MFP Enterprise OID base 1.3.6.1.4.1.1248 is Epson’s IANA-assigned number OID structure may vary slightly between Epson model families Standard Printer-MIB OIDs should always be included as fallback Calculation-based approach provides accurate metrics even when direct OIDs aren’t available ","title":"Epson Enterprise OID Mapping","url":"/development/vendor/epson-oid-mapping/"},{"section":"development","text":"This document captures the concrete steps required to land Epson remote-mode support in the agent while keeping the feature isolated behind a flag and reusing the new SNMP batching helper.\nGoals Reuse the remote-mode command surface documented in docs/SNMP_RESEARCH_NOTES.md (commands di, st, ia, ii, ||). Surface reliable ink, maintenance-box, and alert data in PrinterInfo/metrics without relying on sparse Printer-MIB leaves. Limit SNMP chatter by batching OIDs (already covered by batchedGet) and caching EEPROM ranges per device. Building Blocks Batching helper (agent/scanner/snmp_batch.go): already available; use it whenever remote-mode helpers need scalar GETs. Vendor module hook (agent/scanner/vendor/epson.go): entry point for calling the remote-mode helper from MetricOIDs/Parse whenever the feature flag is on. Learned OID cache (agent/agent/types.go::LearnedOIDMap): add an EpsonEEPROMWindows slice so we remember which address ranges returned data. Feature flag plumbing: add epson_remote_mode_enabled to agent config, default false, surfaced through settings UI. Storage + downsampling: extend DeviceMetricsSnapshot to track main_waste, borderless_waste, waste_box_3 so they flow through existing downsamplers. Implementation Steps Remote-mode transport helper\nNew agent/scanner/vendor/epson_remote.go containing: type RemoteCommand string constants for di, st, ia, ii, rw. An EpsonRemoteClient struct that owns gosnmp client + batching helper, constructs OIDs like remoteRoot + commandSuffix + length. Helpers for decoding ST2 frames and EEPROM payloads (reuse parsing notes). Unit tests covering payload decode paths using canned frames from epson_print_conf samples. Feature flag detection\nAdd config flag (agent/config.go) and CLI/env override to enable remote mode. Thread flag through scanner pipeline (e.g., scanner.DetectorConfig or new EpsonOptions) so we only hit remote-mode OIDs when explicitly enabled. Vendor parse integration\nEpsonVendor.Parse should: Call remote client to fetch di and st frames when enabled. Map ST2 ink slots into canonical toner keys via supplies.NormalizeDescription. Extract maintenance box percentages and write them to result[\"main_waste\"], result[\"borderless_waste\"], etc. Capture remote-mode alert text into result[\"status_messages\"] for UI surfacing. EEPROM window caching\nWhen || reads succeed, persist [start,end] ranges to PrinterInfo.LearnedOIDs.VendorSpecificOIDs[\"epson_eeprom\"]. During subsequent metrics runs, skip brute-force ranges and only query cached windows. Storage + metrics flow\nUpdate agent/storage/convert.go and server/storage/types.go to accept the new waste metrics keys. Ensure downsamplers treat them like toner (raw → hourly → daily). Testing \u0026 validation\nUnit tests for EpsonRemoteClient parsing, vendor parse fallback when remote fails, and LearnedOID caching. Integration test (behind build tag) that replays captured SNMP packets to verify the metrics snapshot contains toner + waste data. Manual validation checklist: enable flag, run dev/launch.ps1, confirm logs show remote-mode batching and resulting metrics appear in UI. Outstanding Questions Verify whether clusterVarbinds needs vendor-specific batch size tuning (Epson payloads may require single-command PDUs). Decide whether remote-mode should run during detection or only deep-scan to limit traffic. Determine safe defaults for EEPROM reads to avoid hammering devices that reject remote-mode commands. Next Actions Scaffold agent/scanner/vendor/epson_remote.go with transport + decoding logic. Add feature flag plumbing + settings toggle. Extend EpsonVendor parse to call the helper and populate normalized metrics. Persist EEPROM windows + waste metrics into storage and add regression tests. ","title":"Epson Remote-Mode Integration Plan","url":"/development/epson-remote-mode-plan/"},{"section":"guides","text":"Complete guide to PrintMaster’s features and capabilities.\nTable of Contents Device Discovery Device Monitoring Multi-Site Management WebSocket Proxy Alerts \u0026 Notifications Scheduled Scans Auto-Updates Security Features API Access Device Discovery PrintMaster automatically discovers printers and copiers on your network using SNMP.\nHow Discovery Works Port Scanning: Quick TCP scan to find devices with printer ports open (80, 443, 9100) SNMP Detection: Query each candidate to confirm it’s a printer Deep Scan: Collect detailed device information via SNMP Discovery Methods Method Description When Used IP Range Scan Scan a specified range of IP addresses Manual configuration Subnet Auto-Scan Automatically scan local subnets Default behavior Single Device Add a specific printer by IP Known devices Supported Devices PrintMaster supports most SNMP-enabled printers and copiers, including:\nHP - LaserJet, OfficeJet, PageWide, DesignJet Canon - imageRUNNER, imageCLASS Epson - WorkForce, EcoTank Brother - HL, MFC, DCP series Lexmark - All network models Xerox - VersaLink, AltaLink, WorkCentre Ricoh - IM, MP, SP series Konica Minolta - bizhub series Kyocera - ECOSYS, TASKalfa Sharp - MX series Toshiba - e-STUDIO series Adding Custom IP Ranges Go to Devices → IP Ranges Click Add Range Enter the range in any supported format: Single: 192.168.1.100 Range: 192.168.1.1-254 CIDR: 192.168.1.0/24 Optionally set a label (e.g., “Main Office”) Click Save Discovery Settings Fine-tune discovery behavior in Settings → Discovery:\nSetting Default Description Concurrent Scans 50 Simultaneous SNMP queries SNMP Community public SNMP v1/v2c community string SNMP Timeout 2000ms Query timeout per device SNMP Retries 1 Retry attempts for failed queries Device Monitoring Collected Data For each discovered device, PrintMaster collects:\nDevice Identity Model name and manufacturer Serial number Asset tag (if configured) Firmware version MAC address Page Counters Total page count Black \u0026 white pages Color pages (if applicable) Duplex pages Large format pages Supply Levels Toner/ink levels (percentage) Drum/imaging unit life Fuser life Waste toner capacity Status Information Online/offline status Current errors and alerts Paper tray status Active jobs Historical Data PrintMaster stores historical metrics using a tiered retention system:\nTier Resolution Retention Raw As collected 7 days Hourly 1 hour average 30 days Daily 1 day average 1 year Monthly 1 month average Forever This allows you to track usage trends while keeping database size manageable.\nDevice Groups Organize devices into groups for easier management:\nGo to Devices → Groups Create a new group (e.g., “Finance Department”) Add devices to the group View group-level statistics and reports Multi-Site Management The PrintMaster Server enables centralized management of multiple agents across different locations.\nArchitecture ┌──────────────┐ ┌────────▶│ Server │◀────────┐ │ │ (Central) │ │ │ └──────────────┘ │ │ ▲ │ │ │ │ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │ Agent │ │ Agent │ │ Agent │ │ Site A │ │ Site B │ │ Site C │ └─────────┘ └─────────┘ └─────────┘ Agent Features Each agent:\nRuns independently at its site Maintains its own local database Uploads data to the server periodically Continues working if server connectivity is lost Syncs automatically when connection is restored Server Dashboard The server provides:\nFleet Overview: Aggregate statistics across all sites Agent Status: Real-time health monitoring of all agents Combined Device List: All devices from all sites in one view Cross-Site Reports: Compare usage across locations Centralized Alerts: Single pane for all site alerts Agent Naming Give each agent a meaningful name for easy identification:\n[server] agent_name = \"NYC Office\" Or set via the web UI: Settings → Server Connection → Agent Name\nUpload Frequency Control how often agents send data to the server:\nSetting Default Description Upload Interval 5 minutes Full data sync frequency Heartbeat Interval 60 seconds Status ping frequency WebSocket Proxy Access agent web UIs and printer admin pages remotely through the server.\nHow It Works The server creates secure WebSocket tunnels to agents, allowing you to:\nAccess agent UIs without direct network access Open printer web interfaces from anywhere Manage devices behind NAT/firewalls Using the Proxy Open the server dashboard Go to Agents Click Open UI on any agent card The agent’s web interface opens in a new tab For device access:\nGo to Devices Find the device you want to access Click Open Web Interface The printer’s admin page opens through the proxy Benefits No port forwarding required: Works through existing connections Secure: Traffic encrypted through WebSocket tunnel Firewall-friendly: Uses the same connection agent established Alerts \u0026 Notifications Get notified about important events and issues.\nAlert Types Alert Description Device Offline Printer not responding to SNMP Low Toner Toner/ink below threshold Error State Printer reporting an error Paper Out Paper tray empty Agent Disconnected Server lost contact with agent Configuring Alerts Go to Settings → Alerts Enable/disable specific alert types Set thresholds (e.g., low toner at 10%) Configure notification methods Notification Methods Email: Send alerts via email Webhook: POST alerts to a URL (integrations) Dashboard: Display in web UI Alert Thresholds Supply Default Threshold Toner/Ink 10% Drum 5% Fuser 5% Waste Toner 95% full Scheduled Scans Automate device discovery and data collection.\nCreating a Schedule Go to Settings → Schedules Click New Schedule Configure: Name: Descriptive label Frequency: Hourly, daily, weekly Time: When to run IP Ranges: Which ranges to scan Click Save Schedule Examples Use Case Configuration Continuous monitoring Every 15 minutes, all ranges Daily inventory Daily at 6 AM, all ranges Low-traffic scanning Hourly during business hours Manual Triggers Run any schedule immediately:\nGo to Schedules Click Run Now on the desired schedule Auto-Updates Keep agents automatically updated to the latest version.\nUpdate Modes Mode Description Inherit Follow fleet policy from server (default) Local Use agent’s local update settings Disabled No automatic updates Fleet Policies (Server) Administrators can set update policies for all agents:\nGo to server Settings → Update Policies Configure: Version Strategy: Minor only, or allow major upgrades Maintenance Window: When updates can occur Rollout Control: Staged rollout settings Local Agent Override Agents can override fleet policy if needed:\n[auto_update] mode = \"local\" [auto_update.local_policy] update_check_days = 7 version_pin_strategy = \"minor\" allow_major_upgrade = false Maintenance Windows Prevent updates during critical hours:\n[auto_update.local_policy.maintenance_window] enabled = true timezone = \"America/New_York\" start_hour = 2 end_hour = 5 days_of_week = [\"Saturday\", \"Sunday\"] Security Features Authentication Component Authentication Server Username/password login, session-based Agent Optional, configurable auth modes API Token-based authentication Agent Auth Modes Mode Description local No login required; admin tasks require local access server Defers auth to central server disabled No authentication (not recommended) TLS/HTTPS Enable encrypted connections:\nServer:\ndocker run -d \\ -e USE_HTTPS=true \\ -e HTTPS_PORT=9443 \\ -v /path/to/certs:/certs \\ ghcr.io/printmaster-org/printmaster-server:latest Agent:\n[web] enable_tls = true https_port = 8443 Reverse Proxy Support Both components work behind reverse proxies:\nEnable BEHIND_PROXY=true for proper header handling Configure WebSocket passthrough for real-time features Handle SSL termination at the proxy level API Access PrintMaster provides a REST API for integrations and automation.\nAuthentication # Get auth token curl -X POST http://server:9090/api/v1/auth/login \\ -H \"Content-Type: application/json\" \\ -d '{\"username\": \"admin\", \"password\": \"your-password\"}' Common Endpoints Endpoint Method Description /api/v1/devices GET List all devices /api/v1/devices/{id} GET Get device details /api/v1/agents GET List connected agents /api/v1/agents/{id}/devices GET Get devices for an agent Webhooks Configure webhooks to receive real-time notifications:\nGo to Settings → Integrations Add a webhook URL Select events to receive Test the webhook Integration Examples Monitoring Systems: Send alerts to PagerDuty, OpsGenie Ticketing: Create tickets in ConnectWise, Autotask Reporting: Export data to BI tools Custom Dashboards: Build your own UI See the API Reference for complete documentation.\n","title":"Features Guide","url":"/guides/features/"},{"section":"guides","text":"Common questions about PrintMaster.\nGeneral What is PrintMaster? PrintMaster is a cross-platform printer/copier fleet management system. It automatically discovers network printers, collects device information (model, serial number, page counts, toner levels), and provides a web interface for monitoring your print fleet.\nWho is PrintMaster for? Managed Service Providers (MSPs) - Monitor client print infrastructure Managed Print Services (MPS) providers - Track usage and supplies Copier dealers - Manage devices across customer sites IT departments - Monitor corporate print fleets Is PrintMaster free? Yes, PrintMaster is open source and free to use under the MIT license.\nWhat printers does PrintMaster support? PrintMaster works with any SNMP-enabled network printer or copier. Support levels vary by manufacturer:\nEnhanced Support (Vendor-Specific Modules) These brands have dedicated modules with optimized OID queries and accurate metric parsing:\nManufacturer Page Counters Supplies Special Features HP ✅ ✅ Color/mono breakdown, copy/fax/scan counters Epson ✅ ✅ Remote-mode ink levels, ST2 status parsing Kyocera ✅ ✅ Enterprise OID detection, drum counters Standard Support (Generic SNMP) These brands work via standard Printer-MIB queries. Basic metrics are collected but some vendor-specific features may be unavailable:\nManufacturer Page Counters Supplies Notes Canon ✅ ⚠️ Supply levels may vary by model Brother ✅ ⚠️ Basic toner levels Lexmark ✅ ⚠️ Standard MIB support Xerox ✅ ⚠️ VersaLink/AltaLink tested Ricoh ✅ ⚠️ Basic counters only Konica Minolta ✅ ⚠️ bizhub series Sharp ✅ ⚠️ MX series Toshiba ✅ ⚠️ e-STUDIO series Samsung ✅ ⚠️ Legacy models Legend: ✅ Full support | ⚠️ Basic/partial support | ❌ Not supported\nWant better support for your brand? We’re actively adding vendor modules. Check GitHub Issues or contribute a vendor profile!\nDeployment Do I need both the agent and server? No. You can run the agent standalone if you only need to monitor printers at a single site. The server is only needed for:\nManaging multiple sites from one dashboard Remote access to agents via WebSocket proxy Centralized reporting across all locations Can I run multiple agents? Yes. Deploy one agent per site/network, and connect them all to a central server for unified management.\nWhat are the system requirements? Agent:\n1 CPU core, 256MB RAM minimum 100MB disk space + database growth Network access to printers (SNMP UDP 161) Server:\n1-2 CPU cores, 512MB RAM minimum 500MB disk space + database growth More resources for larger deployments Can I run PrintMaster in Docker? Yes. Docker is the recommended deployment method for the server. See the Installation Guide.\nFeatures What data does PrintMaster collect? For each printer:\nModel name and manufacturer Serial number IP and MAC addresses Page counters (total, color, B\u0026W) Toner/ink levels Drum and fuser life Error status How often does PrintMaster scan for printers? You control the scan frequency. Options include:\nManual scans on-demand Scheduled scans (hourly, daily, weekly) Automatic scans after IP range changes Device metrics (counters, toner) are typically collected every 15-60 minutes depending on your configuration.\nCan PrintMaster send alerts? Yes. PrintMaster can alert you when:\nA device goes offline Toner/ink falls below a threshold A device reports an error An agent disconnects from the server Does PrintMaster support SNMP v3? Currently, PrintMaster supports SNMP v1/v2c. SNMP v3 support is planned for a future release.\nCan I access printers remotely? Yes. The WebSocket proxy feature allows you to access printer admin pages and agent UIs through the central server, even if the devices are behind NAT or firewalls.\nSecurity Is my data secure? PrintMaster stores data locally on the agent and server. Data is not sent to any external services unless you configure integrations.\nFor secure deployments:\nEnable TLS/HTTPS for encrypted connections Use a reverse proxy with SSL termination Configure authentication for web UIs Restrict network access to management ports Can I use my own SSL certificates? Yes. Both the agent and server support custom TLS certificates. You can also use a reverse proxy (Nginx, Traefik) to handle SSL termination.\nIs there user authentication? Yes. The server has built-in user authentication with username/password login. The agent supports multiple authentication modes including server-delegated auth.\nTroubleshooting Why aren’t my printers being discovered? Common causes:\nSNMP is disabled on the printer Wrong SNMP community string (default is “public”) Firewall blocking SNMP (UDP port 161) Incorrect IP range configuration See the Troubleshooting Guide for detailed solutions.\nWhy can’t my agent connect to the server? Check:\nServer URL includes protocol and port (http://server:9090) Firewall allows the connection Server is running and accessible See Connection Issues for more help.\nWhere are the logs? Platform Location Windows Event Viewer → Application Linux journalctl -u printmaster-agent Docker docker logs container-name Updates How do I update PrintMaster? Docker:\ndocker pull ghcr.io/printmaster-org/printmaster-server:latest docker compose down \u0026\u0026 docker compose up -d Linux (APT):\nsudo apt update \u0026\u0026 sudo apt upgrade printmaster-agent Windows: Run the new MSI installer.\nDoes PrintMaster auto-update? Agents can be configured to auto-update. See Auto-Updates for configuration options.\nIntegration Does PrintMaster have an API? Yes. PrintMaster provides a REST API for:\nQuerying device information Managing agents Configuring settings Triggering scans See the API Reference for documentation.\nCan I integrate with my PSA/RMM tool? Yes, via:\nREST API for custom integrations Webhooks for real-time event notifications Export data for import into other systems Can I export data to a spreadsheet? The web UI supports CSV export for device lists and reports.\nContributing How can I contribute? We welcome contributions! See CONTRIBUTING.md for guidelines on:\nReporting bugs Suggesting features Submitting pull requests Where do I report bugs? Create an issue on GitHub Issues with:\nSteps to reproduce Expected vs actual behavior Version information Relevant logs Where can I get help? Documentation: You’re reading it! Discussions: GitHub Discussions Issues: GitHub Issues ","title":"Frequently Asked Questions","url":"/guides/faq/"},{"section":"guides","text":"This guide walks you through your first steps with PrintMaster after installation.\nTable of Contents Overview Standalone Agent Setup Server + Agent Setup Discovering Printers Understanding the Dashboard Next Steps Overview PrintMaster can be used in two modes:\nStandalone Mode: Run just the agent to monitor printers at a single site Server Mode: Run a central server with multiple agents across sites Choose the setup that matches your needs:\nUse Case Recommended Setup Single office, single network Standalone Agent Multiple sites, centralized management Server + Agents MSP managing multiple clients Server + Agents (multi-tenant) Standalone Agent Setup If you’re monitoring printers at a single location, the standalone agent is all you need.\nStep 1: Access the Web UI After installation, open your browser to:\nhttp://localhost:8080 If installed on another machine, replace localhost with that machine’s IP address.\nStep 2: Run Your First Discovery Click the Devices tab Click Add IP Range to add networks to scan Enter the IP range of your network (e.g., 192.168.1.1-254) Click Save Click Scan Now to start discovery Step 3: View Discovered Devices After the scan completes, discovered printers will appear in the Devices list showing:\nModel name Serial number IP address Page counts Toner/ink levels Server + Agent Setup For multi-site deployments or centralized management.\nStep 1: Set Up the Server If you haven’t already, deploy the server using Docker:\ndocker run -d \\ --name printmaster-server \\ -p 9090:9090 \\ -v printmaster-data:/var/lib/printmaster/server \\ -e ADMIN_PASSWORD=your-secure-password \\ ghcr.io/printmaster-org/printmaster-server:latest Step 2: Log Into the Server Open http://your-server-ip:9090 Log in with username admin and your password Step 3: Connect Agents to the Server For each agent you want to connect:\nOption A: Via Web UI\nOpen the agent’s web UI (http://agent-ip:8080) Go to Settings → Server Connection Enable server mode Enter the server URL (e.g., http://your-server-ip:9090) Click Save Option B: Via Config File Edit config.toml on the agent:\n[server] enabled = true url = \"http://your-server-ip:9090\" agent_name = \"Office A\" # Friendly name for this agent Restart the agent after saving.\nStep 4: Verify Connection On the server dashboard, go to Agents You should see your connected agent with a green status indicator The agent will begin uploading device data automatically Discovering Printers Automatic Discovery PrintMaster uses SNMP (Simple Network Management Protocol) to discover and query printers. Most network printers have SNMP enabled by default.\nAdding IP Ranges You can specify which networks to scan:\nSingle IP: 192.168.1.100 IP Range: 192.168.1.1-254 CIDR Notation: 192.168.1.0/24 Multiple Ranges: Add multiple entries for different subnets Supported Formats Format Example Description Single IP 10.0.0.50 Scan one device Range 10.0.0.1-100 Scan IPs 1-100 CIDR 10.0.0.0/24 Scan entire subnet Wildcard 10.0.1.* Scan 10.0.1.1-254 Discovery Settings Fine-tune discovery in Settings → Discovery Settings:\nSetting Description SNMP Community Default: public. Change if your printers use a different community string Concurrent Scans Number of simultaneous SNMP queries (default: 50) Timeout How long to wait for SNMP responses (default: 2000ms) Auto-Scan Automatically scan local subnets Manual Scan To trigger an immediate scan:\nGo to the Devices tab Click Scan Now The scan will run in the background Scheduled Scans Set up automatic periodic scanning:\nGo to Settings → Schedules Create a new schedule Set the frequency (hourly, daily, weekly) Select which IP ranges to include Understanding the Dashboard Agent Dashboard Section Information Overview Total devices, online/offline counts, recent activity Devices List of all discovered printers with status Settings Configuration options Logs Recent scan and system logs Server Dashboard Section Information Fleet Overview Aggregate stats across all sites Agents Connected agents with status Devices All devices from all agents Reports Usage reports and analytics Device Information For each printer, PrintMaster collects:\nData Description Model Printer/copier model name Serial Number Unique device identifier IP Address Network address MAC Address Hardware address Page Counts Total pages printed (B\u0026W, color, etc.) Toner Levels Remaining toner/ink percentages Status Online/offline, errors Location If configured on the device Next Steps Now that you have PrintMaster running:\nConfigure SNMP settings if your printers don’t use the default community string\nSet up scheduled scans to keep device data current\nEnable alerts for low toner or offline devices\nExplore the API for integrations with your existing tools\nConfigure auto-updates to keep agents current\nTroubleshooting First-Time Setup No Printers Found Check network connectivity: Can you ping the printer IPs? Verify SNMP is enabled on your printers Check the community string: Some printers use a custom string Check firewall rules: SNMP uses UDP port 161 Agent Not Connecting to Server Verify server URL: Include the port (e.g., http://server:9090) Check network path: Can the agent reach the server? Check firewall: Server port 9090 must be accessible Review agent logs: Check for connection errors WebSocket Errors If you see WebSocket connection errors:\nThe agent will automatically fall back to HTTP Check if a proxy is blocking WebSocket connections Ensure the server’s WebSocket port is accessible See the full Troubleshooting Guide for more solutions.\n","title":"Getting Started","url":"/guides/getting-started/"},{"section":"development","text":"What Was Added 1. Automated Release Script (release.ps1) Location: c:\\temp\\printmaster\\release.ps1\nUsage:\n# Patch release (bug fixes) .\\release.ps1 agent patch # Minor release (new features) .\\release.ps1 agent minor # Major release (breaking changes) .\\release.ps1 agent major # Release server or both .\\release.ps1 server patch .\\release.ps1 both patch What it does automatically:\n✅ Checks if working directory is clean ✅ Bumps version in VERSION file(s) ✅ Runs all tests ✅ Builds optimized release binary ✅ Commits VERSION change ✅ Creates git tag (e.g., v0.2.0) ✅ Pushes to GitHub Flags:\n--DryRun - Preview what would happen --SkipTests - Skip test execution --SkipPush - Don’t push to GitHub 2. Enhanced VS Code Tasks (tasks.json) Access: Press Ctrl+Shift+B or Terminal → Run Task\nBuild Tasks:\nBuild: Agent (Dev) ← Default task Build: Server (Dev) Build: Both (Dev) Build: Clean artifacts Test Tasks:\nTest: Agent (all) Test: Server (all) Release Tasks:\nRelease: Agent Patch/Minor/Major Release: Server Patch Release: Both Patch Git Tasks:\nGit: Status Git: Commit All Git: Push Git: Pull Utility Tasks:\nKill: PrintMaster processes Show Build Log Show Version 3. Enhanced Debug Configurations (launch.json) Access: Press F5 or Run and Debug panel\nAgent Debugging:\nDebug: Agent (Default Port) - Port 8080 Debug: Agent (Port 9090) Debug: Agent (Custom Config) Run: Agent (No Debug) Server Debugging:\nDebug: Server (Default Port) - Port 3000 Debug: Server (Port 8080) Run: Server (No Debug) Test Debugging:\nDebug: Current Test Function Debug: All Tests in Package Debug: Current File Tests Compound:\nDebug: Agent + Server Together ← Launches both simultaneously 4. Comprehensive Documentation (BUILD_WORKFLOW.md) Location: c:\\temp\\printmaster\\docs\\BUILD_WORKFLOW.md\nContents:\nQuick reference for daily development Release procedures Semantic versioning guide (when to use patch/minor/major) VS Code integration examples Troubleshooting tips Best practices 5. Updated .gitignore Now includes .vscode/ tasks and launch configs for team consistency:\n.vscode/* !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json This ensures everyone on the team has the same build/debug experience.\nQuick Start Daily Development # Build and test .\\build.ps1 agent .\\build.ps1 test-all # Or use VS Code: Ctrl+Shift+B → \"Build: Agent (Dev)\" Making a Release # For bug fixes (0.1.0 → 0.1.1) .\\release.ps1 agent patch # For new features (0.1.0 → 0.2.0) .\\release.ps1 agent minor # Or use VS Code: Terminal → Run Task → \"Release: Agent Patch\" Debugging Press F5 → Select \"Debug: Agent (Default Port)\" Git Commands Still Work All standard git commands work as usual:\ngit status git add -A git commit -m \"message\" git push git pull The automation is additive - it doesn’t replace git, it just makes releases easier!\nVerification Test the release script with a dry run:\n.\\release.ps1 agent patch -DryRun This shows you exactly what would happen without actually doing it.\nWhat’s Tracked in Git ✅ Committed:\nSource code Documentation Build scripts VS Code tasks/launch configs VERSION files ❌ Ignored:\nBinaries (*.exe) Debug binaries (__debug_bin.exe) Logs (logs/) Databases (*.db) Config files (config.ini, keeps .example) GitHub Repository URL: https://github.com/printmaster-org/printmaster Visibility: Private (will go public at v0.9.0) Current Version: v0.1.0\nNext Steps Test the release script:\n.\\release.ps1 agent patch -DryRun Try VS Code tasks:\nPress Ctrl+Shift+B Select “Build: Agent (Dev)” Try debugging:\nPress F5 Select “Debug: Agent (Default Port)” When ready for first real release:\n.\\release.ps1 agent patch Documentation See docs/BUILD_WORKFLOW.md for complete workflow guide.\n","title":"Git \u0026 GitHub Integration - Complete ✅","url":"/development/git-integration-summary/"},{"section":"guides","text":"This guide covers installing PrintMaster on all supported platforms.\nTable of Contents Quick Install Server Installation Docker (Recommended) Unraid Manual Installation Agent Installation Windows Linux (Debian/Ubuntu) Linux (Fedora/RHEL) macOS Docker First-Time Setup Quick Install Server (Docker) docker run -d \\ --name printmaster-server \\ -p 9090:9090 \\ -v printmaster-data:/var/lib/printmaster/server \\ -e ADMIN_PASSWORD=your-secure-password \\ ghcr.io/printmaster-org/printmaster-server:latest Access at http://localhost:9090 with username admin and your chosen password.\nAgent (Windows) Download and run the MSI installer from GitHub Releases.\nAgent (Linux) # Debian/Ubuntu curl -fsSL https://packages.printmaster.work/install.sh | sudo bash Server Installation The server provides centralized management for multiple agents. If you only need to monitor printers at a single site, you can run the agent standalone without a server.\nDocker (Recommended) Docker is the recommended deployment method for the server.\nPrerequisites Docker Engine 20.10 or later Docker Compose (optional but recommended) Using Docker Run # Basic setup docker run -d \\ --name printmaster-server \\ -p 9090:9090 \\ -v printmaster-data:/var/lib/printmaster/server \\ -v printmaster-logs:/var/log/printmaster/server \\ -e ADMIN_PASSWORD=your-secure-password \\ ghcr.io/printmaster-org/printmaster-server:latest Using Docker Compose Create a docker-compose.yml file:\nversion: '3.8' services: printmaster-server: image: ghcr.io/printmaster-org/printmaster-server:latest container_name: printmaster-server ports: - \"9090:9090\" volumes: - printmaster-data:/var/lib/printmaster/server - printmaster-logs:/var/log/printmaster/server environment: - ADMIN_PASSWORD=your-secure-password - LOG_LEVEL=info - BEHIND_PROXY=false restart: unless-stopped volumes: printmaster-data: printmaster-logs: Start with:\ndocker compose up -d Environment Variables Variable Default Description ADMIN_PASSWORD printmaster Admin password (set before first run!) LOG_LEVEL info Logging level: debug, info, warn, error BEHIND_PROXY false Set to true if behind a reverse proxy BIND_ADDRESS 0.0.0.0 Address to bind to HTTP_PORT 9090 HTTP port HTTPS_PORT 9443 HTTPS port (when TLS enabled) Important: Set ADMIN_PASSWORD before the first run. The password can only be set during initial database creation.\nBehind a Reverse Proxy If using Nginx Proxy Manager, Traefik, or another reverse proxy:\nenvironment: - BEHIND_PROXY=true - BIND_ADDRESS=0.0.0.0 Configure your proxy to:\nForward to port 9090 Enable WebSocket support (required for real-time features) Handle SSL termination Unraid Using Community Applications (Easiest):\nInstall the Community Applications plugin Search for “PrintMaster Server” Click Install and configure Manual Docker Setup:\nGo to Docker tab → Add Container Repository: ghcr.io/printmaster-org/printmaster-server:latest Port: 9090 → 9090 Path: /mnt/user/appdata/printmaster-server/data → /var/lib/printmaster/server Path: /mnt/user/appdata/printmaster-server/logs → /var/log/printmaster/server See Unraid Deployment Guide for detailed instructions.\nManual Server Installation Download the server binary from GitHub Releases and run:\n# Linux/macOS ./printmaster-server # Windows .\\printmaster-server.exe Agent Installation Windows MSI Installer (Recommended) Download the latest MSI from GitHub Releases Run the installer The agent will be installed as a Windows service and start automatically Access the web UI at http://localhost:8080 Manual Installation # Download the binary Invoke-WebRequest -Uri \"https://github.com/printmaster-org/printmaster/releases/latest/download/printmaster-agent-windows-amd64.exe\" -OutFile \"printmaster-agent.exe\" # Install as service (requires Administrator) .\\printmaster-agent.exe --service install # Start the service .\\printmaster-agent.exe --service start Service Management # Check status Get-Service PrintMasterAgent # Stop service .\\printmaster-agent.exe --service stop # Uninstall service .\\printmaster-agent.exe --service uninstall Linux (Debian/Ubuntu) APT Repository (Recommended) # The installer validates the repository signing-key fingerprint, # configures APT signature verification, and installs the agent. curl -fsSL https://packages.printmaster.work/install.sh | sudo bash # The service starts automatically systemctl status printmaster-agent Manual APT Repository Setup # Remove old repo sudo rm -f /etc/apt/sources.list.d/printmaster.list /etc/apt/sources.list.d/printmaster.sources # Setup new repo sudo install -d -m 0755 /etc/apt/keyrings curl -fsSL https://packages.printmaster.work/gpg.key | \\ sudo gpg --dearmor --yes -o /etc/apt/keyrings/printmaster.gpg sudo chmod 0644 /etc/apt/keyrings/printmaster.gpg # Configure APT to trust this key only for the PrintMaster repository echo \"deb [signed-by=/etc/apt/keyrings/printmaster.gpg] https://packages.printmaster.work stable main\" | \\ sudo tee /etc/apt/sources.list.d/printmaster.list # Install sudo apt-get update sudo apt-get install -y printmaster-agent Manual Installation # Download wget https://github.com/printmaster-org/printmaster/releases/latest/download/printmaster-agent-linux-amd64 # Make executable chmod +x printmaster-agent-linux-amd64 sudo mv printmaster-agent-linux-amd64 /usr/local/bin/printmaster-agent # Install as service sudo printmaster-agent --service install sudo systemctl start PrintMasterAgent Linux (Fedora/RHEL) DNF Repository (Recommended) # Import GPG key (recommended) sudo rpm --import https://packages.printmaster.work/gpg.key # Add repository sudo dnf config-manager addrepo --from-repofile=https://packages.printmaster.work/printmaster.repo # Install sudo dnf install -y printmaster-agent # The service starts automatically systemctl status printmaster-agent Important Linux Paths Path Description /usr/bin/printmaster-agent Agent binary /etc/printmaster/agent.toml Configuration file /var/lib/printmaster Data directory (SQLite DB) /var/log/printmaster Log files macOS # Download curl -LO https://github.com/printmaster-org/printmaster/releases/latest/download/printmaster-agent-darwin-amd64 # Make executable chmod +x printmaster-agent-darwin-amd64 sudo mv printmaster-agent-darwin-amd64 /usr/local/bin/printmaster-agent # Install as service sudo printmaster-agent --service install # Start service sudo launchctl load /Library/LaunchDaemons/com.printmaster.agent.plist Docker Agent The agent can also run in Docker for specialized deployments:\ndocker run -d \\ --name printmaster-agent \\ --network host \\ -v printmaster-agent-data:/var/lib/printmaster/agent \\ ghcr.io/printmaster-org/printmaster-agent:latest Note: --network host is required for SNMP discovery to work properly.\nFirst-Time Setup Accessing the Web UI Component Default URL Default Port Agent http://localhost:8080 8080 Server http://localhost:9090 9090 Server First Login Open http://your-server:9090 Log in with: Username: admin Password: The password you set via ADMIN_PASSWORD (default: printmaster) Change the default password immediately if you didn’t set one during installation Connecting an Agent to the Server Open the agent’s web UI at http://agent-ip:8080 Go to Settings → Server Connection Enter your server URL: http://your-server:9090 Click Save Or edit the agent’s config file:\n[server] enabled = true url = \"http://your-server:9090\" Next Steps Getting Started Guide - Configure discovery and scan your first printers Features Guide - Learn about all available features Configuration Guide - Fine-tune your setup Upgrading Docker docker pull ghcr.io/printmaster-org/printmaster-server:latest docker compose down docker compose up -d Linux (APT) sudo apt-get update sudo apt-get upgrade printmaster-agent Linux (DNF) sudo dnf upgrade printmaster-agent Windows Run the new MSI installer - it will upgrade the existing installation.\nAuto-Updates Agents support automatic updates. See Configuration Guide for setup instructions.\n","title":"Installation Guide","url":"/guides/install/"},{"section":"development","text":"Device: TASKalfa 6052ci (172.52.105.95)\nSerial: W2D8303202\nEnterprise OID Base: 1.3.6.1.4.1.1347.*\nAnalysis Date: 2025-11-03\nStatus: ✅ VALIDATED - Complete OID structure decoded\nSummary Kyocera devices provide exceptional detail via enterprise OIDs:\nStandard Printer-MIB provides total impressions (432,951) Kyocera .42.3.* tree provides complete function and color breakdown Direct OIDs available for all major metrics (no calculation needed!) Best-in-class metrics compared to other vendors Validated OID Mappings Page Count Totals Metric OID Value Validated Total Impressions 1.3.6.1.2.1.43.10.2.1.4.1.1 432,951 ✅ Standard Printer-MIB Total Printed Pages 1.3.6.1.4.1.1347.43.10.1.1.12.1.1 424,405 ✅ Kyocera Enterprise Total B\u0026W Printed Calculated 241,538 Sum of B\u0026W functions Total Color Printed Calculated 182,867 Sum of Color functions Kyocera Enterprise OID Structure (.42.3.*) Function Totals (.42.3.1.1.1.*) Metric OID Value Validated Printer Total 1.3.6.1.4.1.1347.42.3.1.1.1.1 304,339 ✅ Copy Total 1.3.6.1.4.1.1347.42.3.1.1.1.2 116,254 ✅ Fax Total 1.3.6.1.4.1.1347.42.3.1.1.1.4 3,812 ✅ Print Breakdown by Function and Color (.42.3.1.2.1.1.*) Metric OID Value Validated Printer B\u0026W 1.3.6.1.4.1.1347.42.3.1.2.1.1.1.1 163,915 ✅ Printer Color 1.3.6.1.4.1.1347.42.3.1.2.1.1.1.3 140,424 ✅ Copy B\u0026W 1.3.6.1.4.1.1347.42.3.1.2.1.1.2.1 73,811 ✅ Copy Color 1.3.6.1.4.1.1347.42.3.1.2.1.1.2.3 42,443 ✅ Fax B\u0026W 1.3.6.1.4.1.1347.42.3.1.2.1.1.4.1 3,812 ✅ Scan Counters (.42.3.1.3.1.1.*) Metric OID Value Validated Copy Scans 1.3.6.1.4.1.1347.42.3.1.3.1.1.2 42,138 ✅ Fax Scans 1.3.6.1.4.1.1347.42.3.1.3.1.1.4 3,057 ✅ Other Scans 1.3.6.1.4.1.1347.42.3.1.4.1.1.1 43,441 ✅ Calculated Totals (Validation) Total B\u0026W Printed: Printer B\u0026W (163,915) + Copy B\u0026W (73,811) + Fax B\u0026W (3,812) = 241,538 ✅ Total Color Printed: Printer Color (140,424) + Copy Color (42,443) + Fax Color (0) = 182,867 ✅ Total Printed: Printer (304,339) + Copy (116,254) + Fax (3,812) = 424,405 ✅ Total Scans: Copy Scans (42,138) + Fax Scans (3,057) + Other Scans (43,441) = 88,636 ✅ Previously Found OIDs (Still Investigating) OID Pair 1: B\u0026W vs Color Split OID Value Likely Meaning .43.10.1.1.16.1.1 221,912 B\u0026W Total or Color Total? Calculated 211,039 Complementary (Total - 221,912) Math Check: 221,912 + 211,039 = 432,951 ✅\nOID Pair 2: Function Split (Copy/Print?) OID Value Likely Meaning .43.8.1.1.8.1.4 170,078 Copy or Print? Calculated 262,873 Complementary (Total - 170,078) Math Check: 170,078 + 262,873 = 432,951 ✅\nOID Pair 3: Another Function Split OID Value Likely Meaning .43.8.1.1.8.1.2 125,196 Function counter? Calculated 307,755 Complementary (Total - 125,196) Math Check: 125,196 + 307,755 = 432,951 ✅\nAdditional Counter (Not Adding to Total) OID Value Likely Meaning .43.10.1.1.12.1.1 424,405 Total minus something (432,951 - 8,546) Web UI Validation ✅ Counters from TASKalfa 6052ci web interface (serial W2D8303202):\nPrinted Pages:\nTotal: 424,405 ✅ B\u0026W: 241,538 ✅ Color: 182,867 ✅ Copy: 116,254 ✅ Printer: 304,339 ✅ Fax: 3,812 ✅ Scanned Pages:\nCopy: 42,138 ✅ Fax: 3,057 ✅ Other: 43,441 ✅ Total: 88,636 ✅ All values perfectly match SNMP OID data!\nImplementation Strategy for Kyocera Vendor Module GetMetricsOIDs() - Core Counters return []string{ // Standard Printer-MIB (fallback) \"1.3.6.1.2.1.43.10.2.1.4.1.1\", // Total impressions \"1.3.6.1.2.1.43.11.1.1.9.1\", // Toner levels \"1.3.6.1.2.1.43.11.1.1.6.1\", // Toner descriptions // Kyocera enterprise - Total printed \"1.3.6.1.4.1.1347.43.10.1.1.12.1.1\", // Total printed pages // Function totals (.42.3.1.1.1.*) \"1.3.6.1.4.1.1347.42.3.1.1.1.1\", // Printer total \"1.3.6.1.4.1.1347.42.3.1.1.1.2\", // Copy total \"1.3.6.1.4.1.1347.42.3.1.1.1.4\", // Fax total // Print breakdown by function and color (.42.3.1.2.1.1.*) \"1.3.6.1.4.1.1347.42.3.1.2.1.1.1.1\", // Printer B\u0026W \"1.3.6.1.4.1.1347.42.3.1.2.1.1.1.3\", // Printer Color \"1.3.6.1.4.1.1347.42.3.1.2.1.1.2.1\", // Copy B\u0026W \"1.3.6.1.4.1.1347.42.3.1.2.1.1.2.3\", // Copy Color \"1.3.6.1.4.1.1347.42.3.1.2.1.1.4.1\", // Fax B\u0026W // Scan counters (.42.3.1.3.* and .42.3.1.4.*) \"1.3.6.1.4.1.1347.42.3.1.3.1.1.2\", // Copy scans \"1.3.6.1.4.1.1347.42.3.1.3.1.1.4\", // Fax scans \"1.3.6.1.4.1.1347.42.3.1.4.1.1.1\", // Other scans } ExtractMetrics() - Parsing Logic // Direct assignments (no calculation needed!) printerBW := getOIDValue(\"1.3.6.1.4.1.1347.42.3.1.2.1.1.1.1\") printerColor := getOIDValue(\"1.3.6.1.4.1.1347.42.3.1.2.1.1.1.3\") copyBW := getOIDValue(\"1.3.6.1.4.1.1347.42.3.1.2.1.1.2.1\") copyColor := getOIDValue(\"1.3.6.1.4.1.1347.42.3.1.2.1.1.2.3\") faxBW := getOIDValue(\"1.3.6.1.4.1.1347.42.3.1.2.1.1.4.1\") // Calculate totals snapshot.MonoPages = printerBW + copyBW + faxBW snapshot.ColorPages = printerColor + copyColor snapshot.PageCount = snapshot.MonoPages + snapshot.ColorPages // Function-specific snapshot.CopyPages = copyBW + copyColor snapshot.FaxPages = faxBW snapshot.OtherPages = printerBW + printerColor // Print pages // Scan counters snapshot.ScanCount = copyScan + faxScan + otherScan Capability-Aware Filtering func (v *KyoceraVendor) GetCapabilityAwareMetricsOIDs(caps *capabilities.DeviceCapabilities) []string { oids := baseOIDs // Always include page counts and toner if caps.IsCopier { oids = append(oids, copyOIDs...) } if caps.IsFax { oids = append(oids, faxOIDs...) } if caps.IsScanner { oids = append(oids, scanOIDs...) } return oids } Comparison: Kyocera vs Epson vs HP Kyocera (Best Metrics) 🏆 ✅ Direct OIDs for EVERYTHING: - Print B\u0026W, Print Color - Copy B\u0026W, Copy Color - Fax B\u0026W, Fax Color (if available) - Copy Scans, Fax Scans, Other Scans ✅ No calculation needed ✅ Most detailed metrics of any vendor ✅ Function separation (Print vs Copy vs Fax) ✅ Scan counters available Epson (Good Metrics) ✅ Direct OIDs for: - Total, B\u0026W, Color - Total Print Computer, Color Print Computer - Total Copy, Color Copy ⚠️ Calculate: - B\u0026W Print = Total Print - Color Print - B\u0026W Copy = Total Copy - Color Copy ❌ No scan counters found ❌ No fax counters HP (Moderate Metrics) ✅ Direct OIDs for: - Total pages (multiple marker indices) - Fax, duplex sheets - Scan to host (ADF/Flatbed) - Jam events ❌ No B\u0026W vs Color breakdown ❌ No function separation (Print vs Copy) Generic (Basic Metrics) ✅ Standard Printer-MIB only: - Total pages - Toner levels - Serial number ❌ No B\u0026W/Color breakdown ❌ No function counters ❌ No scan counters Next Steps ✅ COMPLETED ✅ Web UI validation - All counters match SNMP data perfectly ✅ OID structure decoded - Complete .42.3.* tree mapped 🔄 TODO Create Kyocera vendor module (agent/scanner/vendor/kyocera.go)\nImplement VendorModule interface Add all validated OIDs from .42.3.* tree Capability-aware filtering (copy/fax/scan) Register in vendor registry with enterprise 1347 Validate on second Kyocera device\nTest on ECOSYS PA4000wx (172.52.105.114) Verify OID structure is consistent Check if mono-only device uses same OIDs (without color indices) Deploy and test\nBuild agent with Kyocera vendor module Verify enhanced metrics appear in UI Monitor for any OID inconsistencies 📊 Expected Impact 20% additional coverage (2 out of 10 devices) Best metrics in the industry for Kyocera devices Complete function breakdown (Print/Copy/Fax/Scan) Full B\u0026W/Color separation for all functions Notes Kyocera’s enterprise number is 1347 OID structure is more complex than Epson Multiple subtrees (.42.*, .43.*, .47.*) Standard Printer-MIB total (432,951) is reliable Need web UI to decode enterprise OID meanings Single Color prints are ignored (focus on B\u0026W, Full Color, Total only) ","title":"Kyocera Enterprise OID Mapping","url":"/development/vendor/kyocera-oid-mapping/"},{"section":"components","text":"Location: agent/logger/\nThe logger module provides structured, level-based logging with multiple output targets including files, console, and Server-Sent Events (SSE) for real-time UI streaming.\nArchitecture Overview logger/ ├── logger.go # Core logger implementation └── logger_test.go # Comprehensive test suite (13 tests) Key Features Structured Logging: Key-value pairs for rich context Log Levels: ERROR, WARN, INFO, DEBUG, TRACE Multiple Outputs: File, console, SSE callback Thread-Safe: Safe for concurrent use Rate Limiting: Prevent log spam from noisy sources Ring Buffer: In-memory log storage (1000 entries) Real-Time Streaming: SSE integration for live UI updates File Rotation: Time and size-based rotation Log Levels const ( LevelError LogLevel = 0 // Critical errors requiring attention LevelWarn LogLevel = 1 // Warnings, degraded functionality LevelInfo LogLevel = 2 // General informational messages LevelDebug LogLevel = 3 // Detailed debugging information LevelTrace LogLevel = 4 // Very verbose tracing (not yet used) ) Level Filtering: Only logs at or above the configured level are output.\nExample:\nLogger set to INFO: ERROR, WARN, INFO logged; DEBUG, TRACE dropped Logger set to DEBUG: ERROR, WARN, INFO, DEBUG logged; TRACE dropped Core Types Logger Struct type Logger struct { level LogLevel file *os.File mu sync.RWMutex buffer []LogEntry // Ring buffer (last 1000 entries) bufferSize int onLogCallback func(LogEntry) // SSE callback rateLimitMap map[string]time.Time // Rate limiting state } LogEntry Struct type LogEntry struct { Timestamp time.Time `json:\"timestamp\"` Level string `json:\"level\"` Message string `json:\"message\"` Context map[string]interface{} `json:\"context,omitempty\"` } Creating a Logger Basic Logger (File Only) logger, err := logger.New(\"logs/app.log\", logger.LevelInfo) if err != nil { log.Fatal(err) } defer logger.Close() Logger with SSE Callback logger, _ := logger.New(\"logs/app.log\", logger.LevelInfo) // Set callback for real-time UI updates logger.SetOnLogCallback(func(entry logger.LogEntry) { // Broadcast to SSE clients sseManager.Broadcast(\"log_entry\", entry) }) Logging Methods Error logger.Error(\"Database connection failed\", \"error\", err, \"retries\", 3) Output:\n{ \"timestamp\": \"2025-11-02T14:30:45Z\", \"level\": \"ERROR\", \"message\": \"Database connection failed\", \"context\": { \"error\": \"connection refused\", \"retries\": 3 } } Warn logger.Warn(\"SNMP timeout\", \"ip\", \"192.168.1.100\", \"timeout\", \"2s\") Info logger.Info(\"Device discovered\", \"ip\", \"10.0.0.50\", \"vendor\", \"HP\") Debug logger.Debug(\"SNMP PDU received\", \"oid\", \".1.3.6.1.2.1.1.5.0\", \"value\", \"Printer-01\") Trace logger.Trace(\"Entering function\", \"function\", \"QueryDevice\", \"ip\", \"10.0.0.1\") Rate Limited Logging Purpose: Prevent log spam from repetitive errors or warnings.\nFunction:\nlogger.WarnRateLimited(key string, interval time.Duration, message string, keysAndValues ...interface{}) Example:\n// Only log this warning once per 5 minutes per IP logger.WarnRateLimited( \"snmp_timeout_\"+ip, 5*time.Minute, \"SNMP query timeout\", \"ip\", ip, \"attempts\", 3, ) Behavior:\nFirst call: Logs immediately Subsequent calls within interval: Silently dropped After interval expires: Next call logs and resets timer Use Cases:\nSNMP timeouts (same IP failing repeatedly) Discovery method failures (mDNS/SSDP errors) Network connectivity issues Parsing warnings for malformed data In-Memory Buffer The logger maintains a ring buffer of recent log entries:\nentries := logger.GetRecentLogs() for _, entry := range entries { fmt.Printf(\"[%s] %s: %s\\n\", entry.Level, entry.Timestamp, entry.Message) } Buffer Size: 1000 entries (configurable)\nBehavior: When full, oldest entries are overwritten (FIFO)\nUse Cases:\nUI log display (last N entries) Debugging recent events Crash dump inclusion SSE Integration The logger supports real-time log streaming to the web UI via Server-Sent Events:\nSetup (in main.go) // Create logger appLogger, _ := logger.New(\"logs/agent.log\", logger.LevelInfo) // Set SSE callback appLogger.SetOnLogCallback(func(entry logger.LogEntry) { sseManager.Broadcast(\"log_entry\", entry) }) UI Consumption (JavaScript) const eventSource = new EventSource('/sse'); eventSource.addEventListener('log_entry', (event) =\u003e { const entry = JSON.parse(event.data); console.log(`[${entry.level}] ${entry.message}`); // Display in UI appendToLogWindow(entry); }); Benefits:\nNo polling required (vs old 1-second interval) Instant log delivery to UI Efficient (only pushes when logs occur) Automatic reconnection on disconnect File Rotation Current Status: ⏳ Planned (not yet implemented)\nPlanned Features:\nSize-based rotation (e.g., 10MB per file) Time-based rotation (daily, weekly) Backup retention (keep last N files) Compression of old logs (gzip) Configuration (future):\n{ \"logging\": { \"rotation\": { \"max_size_mb\": 10, \"max_age_days\": 30, \"max_backups\": 5, \"compress\": true } } } Structured Context All logging methods accept key-value pairs for structured context:\nlogger.Info(\"Device discovered\", \"ip\", \"192.168.1.100\", \"vendor\", \"HP\", \"model\", \"LaserJet Pro M404n\", \"serial\", \"JPBHM12345\", \"page_count\", 12543, \"discovered_by\", \"mDNS\", ) Output:\n{ \"timestamp\": \"2025-11-02T14:30:45Z\", \"level\": \"INFO\", \"message\": \"Device discovered\", \"context\": { \"ip\": \"192.168.1.100\", \"vendor\": \"HP\", \"model\": \"LaserJet Pro M404n\", \"serial\": \"JPBHM12345\", \"page_count\": 12543, \"discovered_by\": \"mDNS\" } } Benefits:\nMachine-parseable logs Easy filtering/searching Rich debugging context JSON export for log aggregators Thread Safety The logger is fully thread-safe:\n// Safe to call from multiple goroutines go logger.Info(\"Worker 1 log\") go logger.Info(\"Worker 2 log\") go logger.Info(\"Worker 3 log\") Synchronization: Uses sync.RWMutex for safe concurrent access\nLock Behavior:\nWrite operations (logging): Acquires exclusive lock Read operations (GetRecentLogs): Acquires shared lock Testing Test File: logger_test.go\nTest Coverage: 13 tests, all passing\nTest Cases:\nBasic logging at each level Level filtering (logs below threshold dropped) Structured context encoding Rate limiting behavior Buffer management (FIFO eviction) SSE callback invocation Thread safety (concurrent logging) Run Tests:\ncd agent/logger go test -v Run with Coverage:\ngo test -cover Usage Patterns Application Startup func main() { // Initialize logger appLogger, err := logger.New(\"logs/agent.log\", logger.LevelInfo) if err != nil { log.Fatalf(\"Failed to create logger: %v\", err) } defer appLogger.Close() appLogger.Info(\"Application started\", \"version\", \"1.0.0\") // ... application logic ... } Error Handling func queryDevice(ip string) error { pi, err := scanner.QueryDevice(ctx, ip, \"public\", 5) if err != nil { appLogger.Error(\"SNMP query failed\", \"ip\", ip, \"error\", err, \"function\", \"queryDevice\", ) return err } appLogger.Info(\"Device queried successfully\", \"ip\", ip, \"vendor\", pi.Vendor, \"model\", pi.Model, ) return nil } Discovery Logging func discoverNetwork() { appLogger.Info(\"Starting network discovery\", \"range\", \"192.168.1.0/24\") results, err := Discover(ctx, ranges, \"full\", config, db, 50, 10) if err != nil { appLogger.Error(\"Discovery failed\", \"error\", err) return } appLogger.Info(\"Discovery complete\", \"devices_found\", len(results), \"duration\", time.Since(start), ) } Debug Logging // Only logged if level \u003e= DEBUG logger.Debug(\"Parsing SNMP PDU\", \"oid\", \".1.3.6.1.2.1.1.5.0\", \"type\", \"OctetString\", \"value\", \"Printer-01\", \"length\", len(value), ) Performance Characteristics Timing Operation Duration Notes Log write (file) ~100μs Buffered I/O Log write (SSE) ~50μs In-memory callback GetRecentLogs ~10μs Read from memory Rate limit check ~1μs Map lookup Memory Usage Component Size Notes Buffer (1000 entries) ~100KB Ring buffer Rate limit map ~1KB per key Grows with unique keys Logger struct ~1KB Fixed overhead Configuration Logger behavior is controlled via:\nInitialization: New(filepath, level) Runtime: SetLevel(level), SetOnLogCallback(callback) Environment: Future config file support Current Defaults:\nLevel: INFO Buffer: 1000 entries File: logs/agent.log Rotation: Not implemented (TODO) Integration Points With Main Application (main.go) // Create global logger var appLogger *logger.Logger func init() { appLogger, _ = logger.New(\"logs/agent.log\", logger.LevelInfo) } // Use throughout application appLogger.Info(\"Starting HTTP server\", \"port\", 8080) With Agent Package (agent/agent/) // Agent functions use global logger func Discover(...) { appLogger.Info(\"Discovery started\", \"ranges\", len(ranges)) // ... discovery logic ... } With Scanner Package (agent/scanner/) // Scanner uses logger for SNMP operations func QueryDevice(...) { appLogger.Debug(\"SNMP query\", \"ip\", ip, \"oids\", len(oids)) // ... query logic ... } Migration from Old System Old System (removed):\nSingle global log buffer (logBuffer) Mutex-protected append (logMutex) 1-second polling via /api/logs endpoint logMsg() callback function passed to discovery methods New System (current):\nStructured logger package (logger.Logger) SSE-based real-time streaming Level-based filtering Rate limiting support No callbacks needed (logger is global) Migration Steps (completed):\n✅ Created logger package with SSE callback ✅ Removed logMsg(), logBuffer, logMutex from main.go ✅ Removed logFn parameters from discovery methods ✅ Updated UI to use SSE instead of polling ✅ Updated all log calls to use appLogger.Info() etc. Future Enhancements Planned Features Log rotation (size and time-based) Console output (stdout/stderr) JSON format output Log compression (gzip old files) External log forwarding (syslog, webhook) Dynamic level adjustment (runtime via API) Sampling (log 1% of high-frequency events) Configuration Improvements Load settings from config file Per-module log levels (e.g., scanner=DEBUG, agent=INFO) Custom log formatters Log filtering by context keys Troubleshooting Logs Not Appearing in UI Check SSE connection: Open browser DevTools → Network → SSE Verify callback set: appLogger.SetOnLogCallback(...) called Check log level: UI may filter by level Test with appLogger.Info(\"test\") directly Log File Not Created Check permissions: Ensure logs/ directory writable Check path: Verify absolute path or relative from binary location Check disk space: Ensure sufficient space available Review error from logger.New(): Should return error if failed Rate Limiting Not Working Ensure unique keys per rate-limited log Check interval: 5*time.Minute = 300 seconds Verify using WarnRateLimited not Warn High Memory Usage Reduce buffer size (default 1000 entries) Check for memory leaks in callback Enable log rotation to prevent unbounded file growth Clear rate limit map periodically (currently unbounded) Related Documentation Agent Module - Uses logger for discovery logging Scanner Module - Uses logger for SNMP operations API Reference - SSE endpoint documentation Settings TODO (legacy document unavailable) - Future logging configuration options ","title":"Logger Module Documentation","url":"/components/logger/"},{"section":"components","text":"This document describes the lightweight JSON schema used to store manufacturer MIB profiles, recommended workflow for generating profiles from collected MIB walks, and how profiles can be distributed to agents.\nSchema (example keys)\nvendor (string): short vendor id, e.g. “hp” enterprise_oid (string): vendor enterprise prefix, e.g. “1.3.6.1.4.1.11” version (string): profile version canonical (map): semantic name -\u003e OID (page_count, serial, supplies roots, etc.) probes (array): recommended quick probe OIDs that are safe/cheap to GET during discovery sample_values (map): example values to help human review and tests notes (string): freeform notes Storage and runtime plan\nProfiles live in agent/mib_profiles/ in JSON form. Curated profiles should be committed to the repo for review. Agents should optionally load mib_profiles/*.json at startup (opt-in via config). Loaded probes merge into the runtime quick-probe lists so you can add vendors without code changes. Operators can add a file via the mib_profiles_local/ directory to keep environment-specific or server-pushed profiles. Profile generation workflow\nCollect many bounded MIB walks for a vendor (the agent already writes logs/mib_walk_\u003cip\u003e_\u003cts\u003e.json). Aggregate walks and extract candidate OIDs (Counter32 for counters, OctetString for names, Integer for levels). Produce a short summary (see logs/hp_oid_summary.json). Create a draft mib_profiles/\u003cvendor\u003e.json with canonical OIDs and a small probes list (3–8 OIDs). Add sample_values and notes. Validate profile against a small test set of devices by running targeted GETs using the candidate OIDs. Publish profile to a central repo or server UI. Agents can fetch published profiles or receive them via server push. Design notes and versioning\nKeep probes small. The goal is to be lean: probe only a few OIDs to prove printer-ness. Only run larger enterprise walks for confirmed printers. Version profiles when you change canonical mappings to allow rollbacks. Include sample_values and device_examples in the profile to make review easier. Next steps to implement in the agent (low-risk):\nAdd a small loader that reads mib_profiles/*.json and merges probes into the vendorProbes map at startup. Add a CLI command agent import-profile \u003cfile\u003e to validate and add a local profile. Add a server-side endpoint to host curated profiles and an agent-side opt-in to fetch them. See mib_profiles/hp.json for a first HP draft derived from local walks.\n","title":"MIB profiles (mib_profiles/) and automation","url":"/components/agent/docs/mib-profiles/"},{"section":"components","text":"The capability detection system uses a pluggable architecture where each capability is a self-contained module implementing the CapabilityDetector interface.\nArchitecture Core Interface type CapabilityDetector interface { Name() string // \"color\", \"copier\", \"duplex\", etc. Detect(evidence) float64 // Returns confidence 0.0-1.0 Threshold() float64 // Minimum confidence (default 0.7) } Registry Pattern registry := NewCapabilityRegistry() // Built-in detectors auto-registered: // - PrinterDetector // - ColorDetector // - MonoDetector // - CopierDetector // - ScannerDetector // - FaxDetector // - DuplexDetector // Add custom detector registry.Register(\u0026CustomDetector{}) // Detect all capabilities caps := registry.DetectAll(evidence) Capability Modules Each capability is in its own file: capability_\u003cname\u003e.go\n1. Printer (capability_printer.go) Evidence: Serial number, Printer-MIB OIDs, printer ports, vendor Strong: Serial (0.5), Printer-MIB (0.3) Weak: Open ports (0.1), vendor match (0.1) 2. Color (capability_color.go) Evidence: Colorant names, color page counter, model keywords, consumable count Strong: CMY colorants (0.9), color pages \u003e 0 (0.8) Medium: Model has “color” (0.6) Weak: 4+ consumables (0.3) 3. Mono (capability_mono.go) Evidence: Only black colorants, model keywords, low consumable count Strong: Only black colorant (0.9) Medium: Model has “mono” (0.7) Weak: 1-2 consumables (0.3) Note: Mutually exclusive with color 4. Copier (capability_copier.go) Evidence: Copy page counter, scan counters, MFP keywords, ADF Strong: Copy counter \u003e 0 (0.9), counter exists (0.7) Medium: Has scan counters (0.5), “MFP” in model (0.6) Weak: Has ADF (0.3) 5. Scanner (capability_scanner.go) Evidence: Scan counters, scanner OID, model keywords, ADF Strong: Scan counter \u003e 0 (0.9), counter exists (0.6) Medium: Scanner OID (0.5), “scanner” in model (0.6) Special: Boost confidence if printer_confidence \u003c 0.3 (standalone scanner) 6. Fax (capability_fax.go) Evidence: Fax page counter, fax scan counters, model keywords, modem interface Strong: Fax counter \u003e 0 (0.9), counter exists (0.6) Medium: Fax scan counters (0.5), “fax” in model (0.4) Weak: Modem/PSTN interface (0.3) 7. Duplex (capability_duplex.go) Evidence: Duplex counter, duplex unit, model suffix, keywords Strong: Duplex counter \u003e 0 (0.9), counter exists (0.6) Medium: Duplex unit (0.7), “dn”/“dw” suffix (0.6) Weak: “duplex” keyword (0.5) Usage Example // Prepare evidence evidence := \u0026DetectionEvidence{ PDUs: snmpResults, SysDescr: \"HP LaserJet Pro M479fdw\", SysOID: \"1.3.6.1.4.1.11.2.3.9.1\", Vendor: \"HP\", Model: \"LaserJet Pro M479fdw\", Serial: \"JPBHM12345\", OpenPorts: []int{9100, 80, 443, 631}, } // Detect capabilities registry := NewCapabilityRegistry() caps := registry.DetectAll(evidence) // Results fmt.Printf(\"Printer: %.2f (%v)\\n\", caps.Scores[\"printer\"], caps.IsPrinter) fmt.Printf(\"Color: %.2f (%v)\\n\", caps.Scores[\"color\"], caps.IsColor) fmt.Printf(\"Copier: %.2f (%v)\\n\", caps.Scores[\"copier\"], caps.IsCopier) fmt.Printf(\"Device Type: %s\\n\", caps.DeviceType) // Output: // Printer: 1.00 (true) // Color: 0.95 (true) // Copier: 0.90 (true) // Device Type: Color MFP Adding Custom Detectors Example: Network Fax Detector type NetworkFaxDetector struct{} func (d *NetworkFaxDetector) Name() string { return \"network_fax\" } func (d *NetworkFaxDetector) Threshold() float64 { return 0.6 // Lower threshold } func (d *NetworkFaxDetector) Detect(evidence *DetectionEvidence) float64 { score := 0.0 // Check if regular fax capability exists if faxScore, exists := evidence.Capabilities[\"fax\"]; exists \u0026\u0026 faxScore \u003e 0.5 { score += 0.5 } // Check for network fax OIDs (HP Network Fax) networkFaxOIDs := []string{ \"1.3.6.1.4.1.11.2.4.3.3.0\", // HP Network Fax enabled } if HasAnyOID(evidence.PDUs, networkFaxOIDs) { score += 0.7 } // Check model for \"network fax\" keyword if ContainsAny(evidence.Model, []string{\"network fax\", \"lan fax\"}) { score += 0.4 } return Min(score, 1.0) } // Register custom detector registry := NewCapabilityRegistry() registry.Register(\u0026NetworkFaxDetector{}) Benefits of Modular Design 1. Testability Each detector can be unit tested independently:\nfunc TestColorDetector(t *testing.T) { detector := \u0026ColorDetector{} evidence := \u0026DetectionEvidence{ Model: \"HP Color LaserJet Pro M479fdw\", PDUs: mockCMYKColorants(), } score := detector.Detect(evidence) if score \u003c 0.9 { t.Errorf(\"Expected high confidence for color device\") } } 2. Extensibility Add new capabilities without modifying core code:\nWirelessDetector - WiFi capability NFC - Near-field communication CloudPrintDetector - Cloud printing support SecurePrintDetector - PIN/badge release printing 3. Maintainability Each file is focused on one capability (~100 lines):\nEasy to understand Clear responsibility Independent changes No side effects 4. Customization Users can override thresholds or add vendor-specific detectors:\n// Lower threshold for duplex (more lenient) type CustomDuplexDetector struct { DuplexDetector } func (d *CustomDuplexDetector) Threshold() float64 { return 0.5 // Lower than default 0.7 } registry.Register(\u0026CustomDuplexDetector{}) 5. Cross-Referencing Detectors can use results from other detectors:\n// In ScannerDetector: if printerScore, exists := evidence.Capabilities[\"printer\"]; exists { if printerScore \u003c 0.3 \u0026\u0026 score \u003e 0.5 { score += 0.2 // Likely standalone scanner } } Testing Strategy Unit Tests (per detector) // capability_color_test.go func TestColorDetector_CMYKColorants(t *testing.T) { } func TestColorDetector_ColorPageCounter(t *testing.T) { } func TestColorDetector_ModelKeywords(t *testing.T) { } func TestColorDetector_MonoDevice(t *testing.T) { } // Should score low Integration Tests // capabilities_test.go func TestCapabilityRegistry_HPColorMFP(t *testing.T) { evidence := loadRealDeviceData(\"hp_m479fdw.json\") registry := NewCapabilityRegistry() caps := registry.DetectAll(evidence) assert.True(t, caps.IsPrinter) assert.True(t, caps.IsColor) assert.True(t, caps.IsCopier) assert.Equal(t, \"Color MFP\", caps.DeviceType) } Regression Tests func TestCapabilityDetection_BackwardCompatibility(t *testing.T) { // Ensure existing printer detection still works // after capability system addition } File Structure scanner/ ├── capabilities.go # Core interface \u0026 registry ├── capability_printer.go # Printer detection ├── capability_color.go # Color detection ├── capability_mono.go # Monochrome detection ├── capability_copier.go # Copier detection ├── capability_scanner.go # Scanner detection ├── capability_fax.go # Fax detection ├── capability_duplex.go # Duplex detection ├── capabilities_test.go # Integration tests ├── capability_printer_test.go # Printer unit tests ├── capability_color_test.go # Color unit tests └── ... (one test file per detector) Performance Detection Cost Per detector: 10-50μs (mostly map lookups) All 7 detectors: \u003c 500μs per device Overhead: Negligible compared to SNMP query time (100ms-2s) Optimization Detectors run sequentially, allowing cross-referencing. Could parallelize if needed:\nfunc (r *CapabilityRegistry) DetectAllParallel(evidence) DeviceCapabilities { results := make(chan result, len(r.detectors)) for _, detector := range r.detectors { go func(d CapabilityDetector) { results \u003c- result{d.Name(), d.Detect(evidence)} }(detector) } // Collect results... } Next Steps Create unit tests for each detector Gather real-world data from various devices Tune confidence scores based on test results Add vendor-specific detectors (HP-specific, Canon-specific, etc.) Integrate with storage (add capabilities column to devices table) Update UI (show capability badges, conditional metrics) Metrics Filtering System The capability detection system integrates with metrics filtering to control which metrics are queried, parsed, and displayed based on device capabilities.\nThree-Layer Optimization ┌─────────────────────────────────────────────────────────┐ │ Layer 1: Scanner OID Selection │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ QueryDeviceWithCapabilities() │ │ │ │ • Detects capabilities during QueryFull │ │ │ │ • Filters OIDs using GetCapabilityAwareMetricsOIDs()│ │ │ │ • 30-71% fewer SNMP queries on targeted devices │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ Layer 2: Metrics Parsing (GetRelevantMetrics) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ GetRelevantMetrics(caps) │ │ │ │ • Filters 40+ metric definitions │ │ │ │ • Checks RequiresAll/RequiresAny/ExcludesAny │ │ │ │ • Returns only metrics applicable to device │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ Layer 3: UI Display (GetRelevantMetricsByCategory) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ GetRelevantMetricsByCategory(caps, category) │ │ │ │ • Groups metrics by category (Page, Supplies, etc.) │ │ │ │ • Hides irrelevant metrics from user interface │ │ │ │ • Shows capability-appropriate data only │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ Metric Definition Structure Each metric is defined with capability requirements:\ntype MetricDefinition struct { Name string // \"color_pages\", \"toner_cyan\", etc. Category string // \"PageCounters\", \"Supplies\", etc. RequiresAll []string // All must be true: [\"printer\", \"color\"] RequiresAny []string // At least one true: [\"copier\", \"scanner\"] ExcludesAny []string // None can be true: [\"mono\"] } Filtering Logic // Example: Color-specific metrics { Name: \"color_pages\", Category: \"PageCounters\", RequiresAll: []string{\"printer\", \"color\"}, // Must be color printer ExcludesAny: []string{\"mono\"}, // NOT mono } // Example: MFP-specific metrics { Name: \"copy_total\", Category: \"PageCounters\", RequiresAny: []string{\"copier\"}, // Must have copier } // Example: Supply metrics { Name: \"toner_cyan\", Category: \"Supplies\", RequiresAll: []string{\"color\"}, // Must be color ExcludesAny: []string{\"mono\"}, // NOT mono } Usage Example Basic Filtering // After capability detection caps := registry.DetectAll(evidence) // Get all relevant metrics relevant := GetRelevantMetrics(caps) fmt.Printf(\"Device has %d relevant metrics\\n\", len(relevant)) // Filter by category pageMetrics := GetRelevantMetricsByCategory(caps, \"PageCounters\") supplyMetrics := GetRelevantMetricsByCategory(caps, \"Supplies\") // Check individual metric if IsMetricRelevant(\"color_pages\", caps) { // Parse and display color page counter } Mono Printer Example caps := DeviceCapabilities{ IsPrinter: true, IsMono: true, IsColor: false, HasDuplex: true, } relevant := GetRelevantMetrics(caps) // Returns: total_pages, mono_pages, duplex_pages, toner_black, drum_black // Excludes: color_pages, toner_cyan, toner_magenta, toner_yellow Color MFP Example caps := DeviceCapabilities{ IsPrinter: true, IsColor: true, IsCopier: true, IsScanner: true, } relevant := GetRelevantMetrics(caps) // Returns: total_pages, color_pages, copy_total, scan_total, // toner_cyan, toner_magenta, toner_yellow, toner_black Metric Categories The system defines metrics across 4 categories:\n1. PageCounters (20 metrics) Total: total_pages, total_impressions Color/Mono: color_pages, mono_pages Function: copy_total, scan_total, fax_total Sided: simplex_pages, duplex_pages Color Detail: color_copy, mono_copy, color_print, mono_print 2. Supplies (9 metrics) Toner: toner_black, toner_cyan, toner_magenta, toner_yellow Drums: drum_black, drum_cyan, drum_magenta, drum_yellow Maintenance: maintenance_kit 3. Usage (3 metrics) Utilization: uptime_hours, energy_kwh, duty_cycle_percent 4. Status (2 metrics) State: device_status, alert_count Benefits 1. Performance // Without capabilities: oids := []string{...100 OIDs...} // Query all, parse all, store all // With capabilities: oids := vendor.GetCapabilityAwareMetricsOIDs(caps) // Query 30-40 OIDs (30-71% reduction) // Parse only relevant metrics // Store only applicable data 2. User Experience // UI displays only relevant metrics if IsMetricRelevant(\"color_pages\", caps) { renderMetric(\"Color Pages\", colorPages) } // Group by category for clean layout pageMetrics := GetRelevantMetricsByCategory(caps, \"PageCounters\") for _, metric := range pageMetrics { renderMetric(metric.Name, values[metric.Name]) } 3. Data Quality No confusing zero values for non-existent features Accurate device representation Cleaner database (only store applicable metrics) Integration Example // In vendor's ExtractMetrics method: func (h *HPModule) ExtractMetrics(snmpData, caps) Metrics { relevant := GetRelevantMetrics(caps) metrics := Metrics{} for _, metric := range relevant { switch metric.Name { case \"color_pages\": if caps.IsColor { metrics.ColorPages = extractColorPages(snmpData) } case \"toner_cyan\": if caps.IsColor { metrics.TonerCyan = extractTonerLevel(snmpData, \"cyan\") } // ... only parse relevant metrics } } return metrics } Testing All metric filtering logic is tested in capabilities_test.go:\n// Test mono printer filtering func TestGetRelevantMetrics_MonoPrinter(t *testing.T) { caps := DeviceCapabilities{IsPrinter: true, IsMono: true} metrics := GetRelevantMetrics(caps) // Should have mono metrics assertContains(t, metrics, \"mono_pages\", \"toner_black\") // Should NOT have color metrics assertNotContains(t, metrics, \"color_pages\", \"toner_cyan\") } // Test color MFP filtering func TestGetRelevantMetrics_ColorMFP(t *testing.T) { caps := DeviceCapabilities{IsPrinter: true, IsColor: true, IsCopier: true} metrics := GetRelevantMetrics(caps) // Should have printer, color, and copier metrics assertContains(t, metrics, \"total_pages\", \"color_pages\", \"copy_total\") } Related Documentation Scanner Module - SNMP querying and vendor profiles Capability Integration Guide (legacy document unavailable) - Usage examples Storage Module - Database persistence ","title":"Modular Capability Detection System","url":"/components/agent/scanner/capabilities/"},{"section":"deployment","text":"PrintMaster’s Compose examples use timescale/timescaledb:latest-pg18 for new deployments. The server supports PostgreSQL 18 and TimescaleDB through the standard pgx driver and TimescaleDB APIs.\nImportant warning Changing latest-pg15 to latest-pg18 while keeping the existing /var/lib/postgresql volume will not perform a PostgreSQL major upgrade. PostgreSQL 15 and 18 use incompatible data-directory formats. The container will normally refuse to start, and deleting the volume would destroy data.\nFor PG18, mount the volume at /var/lib/postgresql, not /var/lib/postgresql/data. The PG18 image manages its version-specific data directory below that root.\nMake a tested backup before starting. Keep the old volume until the new database has been verified.\nExisting Compose database: dump and restore The commands below assume the database service is named db, the database is named printmaster, and the credentials match your Compose file.\nCheck the current database and create logical backups while the old service is running. Record the TimescaleDB extension version: docker compose exec -T db psql -U printmaster -d printmaster \\ -c \"SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';\" docker compose exec -T db pg_dump -U printmaster -d printmaster -Fc \u003e printmaster-pg15.dump docker compose exec -T db pg_dumpall -U printmaster --globals-only \u003e printmaster-globals.sql The source and target must use the same TimescaleDB extension version during the dump/restore. For example, if the source reports 2.28.1 but the PG18 image reports 2.30.1, do not restore yet. First update the old PG15 image to the image containing the target extension version, then update the extension on the PG15 database and take fresh dumps:\n# Run these against the original PG15 data directory, not database-18. docker compose pull db docker compose up -d db docker compose exec -T db psql -U printmaster -d printmaster \\ -c \"ALTER EXTENSION timescaledb UPDATE;\" docker compose exec -T db psql -U printmaster -d printmaster \\ -c \"SELECT extversion FROM pg_extension WHERE extname = 'timescaledb';\" Proceed only when the PG15 source version matches the PG18 target version. Take new dump files after the extension update; do not reuse a dump created with the older extension catalog.\nStop the application and database, but do not remove volumes: docker compose stop server db Change the database image to timescale/timescaledb:latest-pg18 and change the database volume or host directory to a new, empty location. For the named-volume example, use a new volume name such as printmaster_db_data_pg18. Mount it at /var/lib/postgresql, not /var/lib/postgresql/data.\nStart only the new database and wait for it to become healthy:\ndocker compose up -d db docker compose ps db Prepare a clean database and enable TimescaleDB. If a previous restore was attempted, do not retry over it: discard the failed database-18 directory, create a new empty one, and start PG18 again. The Compose-created printmaster role already exists. docker compose exec -T db psql -U printmaster -d postgres \\ -c \"DROP DATABASE IF EXISTS printmaster;\" docker compose exec -T db psql -U printmaster -d postgres \\ -c \"CREATE DATABASE printmaster OWNER printmaster;\" docker compose exec -T db psql -U printmaster -d printmaster \\ -c \"CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;\" Put TimescaleDB into restore mode, restore the complete custom-format dump, then leave restore mode. These hooks are important when the dump contains compressed hypertable chunks. Do not use --data-only and do not ignore restore errors. docker compose exec -T db psql -U printmaster -d printmaster \\ -c \"SELECT timescaledb_pre_restore();\" docker compose exec -T db pg_restore --no-owner --no-privileges \\ -U printmaster -d printmaster \u003c printmaster-pg15.dump docker compose exec -T db psql -U printmaster -d printmaster \\ -c \"SELECT timescaledb_post_restore();\" The restore command must finish without errors ignored on restore. Errors such as chunk not found indicate an incomplete restore; drop and recreate the target database and repeat the sequence above. Never proceed to production verification after a non-zero restore result.\nIf timescaledb_post_restore() reports a catalog version mismatch, the source dump and target image use different TimescaleDB versions. Upgrade the source extension, create a fresh dump, reset the target, and repeat the restore. Changing only the PostgreSQL image is not sufficient.\nIf additional global objects are needed, restore them separately as a database superuser and resolve any already-exists messages for the Compose-created role:\ndocker compose exec -T db psql -U printmaster -d postgres \u003c printmaster-globals.sql Start PrintMaster and verify login, agents, devices, metrics, hypertables, and scheduled jobs before retiring the old volume: docker compose up -d server docker compose logs --tail=100 server Do not run docker compose down -v during this procedure. Remove the old volume only after a separate restore test and application verification.\nImage updates within PostgreSQL 18 Refreshing latest-pg18 can update the TimescaleDB patch release without a PostgreSQL major-version migration. Still back up first, pull the image, and recreate the database container:\ndocker compose pull db docker compose up -d db For production, replace the mutable latest-pg18 tag with a tested, date-reviewed TimescaleDB release tag and let Dependabot propose Docker image updates through .github/dependabot.yml.\n","title":"PostgreSQL and TimescaleDB Upgrades","url":"/deployment/database-upgrade/"},{"section":"components","text":"Cross-platform printer discovery and monitoring agent\nThe PrintMaster Agent is a standalone Go application that discovers printers on local networks, collects metrics via SNMP, and provides a web UI for management. It runs on Windows, macOS, and Linux without external dependencies.\nQuick Start Build \u0026 Run # Build (development - with debug info) .\\build.ps1 agent # Build (production - optimized \u0026 stripped) .\\build.ps1 release # Build with verbose output .\\build.ps1 release -VerboseBuild # Check build log Get-Content logs\\build.log -Tail 50 # Run cd agent .\\printmaster-agent.exe -port 8080 Web UI: http://localhost:8080\nBuild Targets:\nagent - Development build with debug symbols (default, version: x.y.z-dev) release - Production build: optimized, stripped (~30% smaller) bump - Release build with auto-increment version (1.0.0 → 1.0.1) test - Run storage tests test-all - Run all tests clean - Remove build artifacts Build Flags:\n-VerboseBuild - Show detailed compilation output -IncrementVersion - Auto-increment patch version (can combine with release) Version Management:\n# Check current version .\\version.ps1 # Build with current version .\\build.ps1 release # Build and increment version .\\build.ps1 bump # OR .\\build.ps1 release -IncrementVersion # Check version in running agent curl http://localhost:8080/api/version Build Logs: All build output is saved to logs\\build.log with rotation\nBasic Usage Configure IP Ranges: Settings → Network → IP Ranges (e.g., 192.168.1.0/24) Start Discovery: Click “Scan Network” button View Results: Devices appear in “Discovered” tab Save Devices: Click “Save” to move to “Saved” tab Monitor: View metrics, page counts, supply levels Architecture Overview agent/ ├── main.go # HTTP server, API endpoints, embedded UI ├── agent/ # Discovery protocols \u0026 device detection ├── scanner/ # SNMP querying \u0026 vendor profiles ├── logger/ # Structured logging with SSE streaming ├── storage/ # SQLite persistence layer ├── util/ # Shared utilities └── tools/ # Development tools Module Summary Agent - Discovery \u0026 Detection mDNS/Bonjour: Passive discovery of IPP/AirPrint printers SSDP/UPnP: Universal Plug and Play device discovery WS-Discovery: Windows/enterprise printer discovery SNMP Traps: Event-driven discovery (printers send notifications) LLMNR: Link-local name resolution (Windows networks) ARP Table: Extract known devices from OS cache Active Scanning: TCP port probes + ICMP ping IP Enumeration: CIDR range parsing and subnet scanning Scanner - SNMP Queries \u0026 Parsing Multi-Stage Pipeline: Liveness → Detection → Deep Scan Vendor Profiles: HP, Canon, Brother, Epson, Kyocera, Lexmark, Ricoh, Samsung, Xerox Device Detection: Confidence scoring (is this a printer?) SNMP Wrapper: Query/Walk/BulkWalk with retries Metrics Extraction: Page counts, supply levels, status messages OID Resolution: Vendor-specific + standard Printer-MIB Logger - Structured Logging Log Levels: ERROR, WARN, INFO, DEBUG, TRACE SSE Streaming: Real-time logs to web UI (no polling) Rate Limiting: Prevent log spam from repetitive errors Ring Buffer: Last 1000 log entries in memory Structured Context: Key-value pairs for rich logging Thread-Safe: Concurrent logging from multiple goroutines Storage - SQLite Persistence Device CRUD: Create, Read, Update, Delete, Upsert Scan History: Track device changes over time Metrics History: Time-series page counts and supply levels Field Locking: Protect manually-edited fields from auto-update Configuration: Store settings (IP ranges, SNMP community, etc.) Migrations: Automatic schema upgrades Key Features Discovery Methods Method Type Platform Best For mDNS Passive All macOS, modern printers SSDP Passive All Consumer devices WS-Discovery Passive All Windows, enterprise SNMP Traps Passive All Event-driven monitoring LLMNR Passive Windows Workgroup networks Active Scan Active All Complete coverage SNMP Capabilities Protocols: SNMPv1, SNMPv2c (SNMPv3 planned) Community: Configurable (default: “public”) Timeout: Configurable (default: 2000ms) Retries: Configurable (default: 1) Concurrency: Configurable worker pools (default: 50) Vendor Detection: Automatic via sysObjectID enterprise OID Metrics Collected Device Info:\nManufacturer, model, serial number Hostname, IP, MAC address Firmware version Network config (subnet, gateway, DNS) Counters:\nTotal pages printed Color pages printed (vendor-specific) Duplex pages, fax pages, scan pages (HP) Drum life, maintenance counters (various vendors) Supplies:\nToner/ink levels (percentage) Supply names (Black Toner, Cyan Ink, etc.) Supply status (OK, Low, Empty) Status:\nDevice status (idle, printing, error) Status messages (paper jam, cover open, etc.) Error codes and warnings Configuration Config File (config.json) { \"port\": 8080, \"snmp_community\": \"public\", \"snmp_timeout_ms\": 2000, \"snmp_retries\": 1, \"discover_concurrency\": 50, \"ip_ranges\": [ \"192.168.1.0/24\", \"10.0.0.0/24\" ], \"discovery_methods\": { \"arp\": true, \"icmp\": true, \"tcp\": true, \"snmp\": true, \"mdns\": true, \"ssdp\": true, \"wsd\": true, \"traps\": false } } Database Settings Settings configured via Web UI are stored in SQLite and override config.json.\nLocation:\nWindows: %APPDATA%\\printmaster\\devices.db Linux: ~/.local/share/printmaster/devices.db macOS: ~/Library/Application Support/printmaster/devices.db API Endpoints Devices GET /api/devices - List all devices GET /api/devices/{serial} - Get single device DELETE /api/devices/{serial} - Delete device POST /api/devices/{serial}/save - Mark device as saved POST /api/devices/save-all - Save all discovered devices POST /api/devices/{serial}/refresh - Re-scan single device Discovery POST /api/discover - Start network scan POST /api/live-discovery/start - Enable passive discovery POST /api/live-discovery/stop - Disable passive discovery GET /api/live-discovery/status - Check discovery status Settings GET /api/settings - Get all settings POST /api/settings - Update settings GET /api/ranges - Get IP ranges POST /api/ranges - Update IP ranges Monitoring GET /sse - Server-Sent Events stream (logs, metrics) GET /api/scan-history/{serial} - Device scan history GET /api/metrics-history/{serial} - Metrics time-series Full API Documentation: docs/API_REFERENCE.md\nDevelopment Prerequisites Go 1.27+ (uses modern Go features) No CGO required (pure Go SQLite driver) Cross-platform (Windows, macOS, Linux) Build # Standard build go build -o printmaster-agent.exe # Or use build script .\\build.ps1 agent # Build for specific platform $env:GOOS=\"linux\"; $env:GOARCH=\"amd64\"; go build -o printmaster-agent-linux Run Tests # All tests go test ./... -v # Specific package go test ./agent/... -v go test ./scanner/... -v go test ./logger/... -v go test ./storage/... -v # With coverage go test ./... -cover Project Structure agent/ ├── main.go # 8000+ lines (HTTP server + embedded UI) ├── discover.go # Legacy wrapper (deprecated) ├── scanner_api.go # Bridge to scanner package ├── config.json # Configuration file ├── agent/ # Discovery package │ ├── detect.go # Main Discover() function │ ├── probe.go # TCP/ICMP probing │ ├── parse.go # SNMP parsing │ ├── mdns.go # mDNS/Bonjour │ ├── ssdp.go # SSDP/UPnP │ ├── wsdiscovery.go # WS-Discovery │ ├── snmptraps.go # SNMP trap listener │ ├── llmnr.go # LLMNR │ ├── arp.go # ARP table │ ├── merge.go # Device merging │ ├── types.go # Data structures │ └── ... ├── scanner/ # SNMP querying │ ├── detector.go # Printer detection │ ├── pipeline.go # Multi-stage scanning │ ├── query.go # SNMP queries │ ├── snmp.go # SNMP wrapper │ └── vendor/ # Vendor profiles │ ├── hp.go │ ├── canon.go │ └── ... ├── logger/ # Logging system │ ├── logger.go │ └── logger_test.go ├── storage/ # Persistence │ ├── sqlite.go │ ├── device.go │ ├── interface.go │ ├── agent_config.go │ └── ... ├── util/ # Utilities │ ├── helpers.go │ └── secret.go └── tools/ # Dev tools ├── aggregate_mib_walks.go ├── scan_mib_walks.go └── ... Deployment Standalone Executable # Build release binary go build -ldflags=\"-s -w\" -o printmaster-agent.exe # Run on server .\\printmaster-agent.exe -port 8080 Windows Service # Install as service (requires NSSM or similar) nssm install PrintMasterAgent \"C:\\path\\to\\printmaster-agent.exe\" nssm set PrintMasterAgent AppParameters \"-port 8080\" nssm start PrintMasterAgent Linux Systemd # Create service file: /etc/systemd/system/printmaster.service [Unit] Description=PrintMaster Agent After=network.target [Service] Type=simple User=printmaster ExecStart=/usr/local/bin/printmaster-agent -port 8080 Restart=on-failure [Install] WantedBy=multi-user.target # Enable and start sudo systemctl enable printmaster sudo systemctl start printmaster Docker (Future) FROM golang:1.21-alpine AS builder WORKDIR /build COPY . . RUN go build -o printmaster-agent FROM alpine:latest COPY --from=builder /build/printmaster-agent /usr/local/bin/ EXPOSE 8080 CMD [\"printmaster-agent\", \"-port\", \"8080\"] Performance Resource Usage Metric Idle Scanning (50 workers) CPU \u003c1% 5-15% Memory ~50MB ~150MB Network Minimal 1-5 Mbps Disk I/O Minimal SQLite writes Scan Performance Network Size Time Notes /24 (254 IPs) 10-30s With 50 workers /22 (1024 IPs) 60-120s Adjust concurrency /16 (65536 IPs) Hours Not recommended Optimization Tips:\nIncrease concurrency for faster scans (risk: network saturation) Reduce SNMP timeout for faster failures (risk: miss slow devices) Use ARP-based discovery to pre-filter live IPs Enable only needed discovery methods (disable traps if not configured) Troubleshooting No Devices Found Check network connectivity: Ensure agent can reach printers Verify SNMP enabled: Test with snmpwalk -v2c -c public \u003cip\u003e .1.3.6 Check firewall: Allow outbound UDP 161, TCP 9100/631 Review logs: Look for timeout or permission errors Try different discovery methods: Some printers only respond to certain protocols Permission Errors SNMP Traps (UDP 162): Requires admin/root (privileged port) ICMP Ping: Requires raw sockets (admin/root) or use system ping Low Port Binding (\u003c1024): Run as admin/root or use higher port Slow Scans Reduce IP range: Scan smaller subnets Increase timeout: Some devices respond slowly Adjust concurrency: Balance speed vs network load Check network: Congestion, packet loss, or slow switches Missing Data Incomplete SNMP support: Not all devices implement all OIDs Vendor detection failed: Check if vendor profile exists SNMP community mismatch: Verify community string Field locked: Check if field is locked from manual edit Security Considerations SNMP Community Strings Default “public” is world-readable (low security) Use unique community strings in production SNMPv3 recommended (planned feature) for encryption Network Exposure Agent listens on the configured HTTP/HTTPS ports (default 8080/8443) Web UI authentication is driven by [web.auth] (see config.example.toml). Use mode = \"server\" to force central logins or keep mode = \"local\" with allow_local_admin = true for loopback-only access. Restrict access with firewall rules when running in local mode. Use the built-in TLS support or terminate behind a reverse proxy (nginx, Caddy). Database SQLite file contains all device data No encryption at rest (use disk encryption) File permissions: User-only read/write Roadmap Short-Term (Planned) SNMPv3 support (authentication + encryption) Authentication for web UI TLS/HTTPS support Webhook notifications Prometheus metrics export CSV/JSON export Medium-Term Docker container Multi-agent support (central server) Device groups and tagging Alert rules (low toner, offline devices) Email notifications Mobile app (view-only) Long-Term Machine learning for anomaly detection Print job tracking (requires print server integration) Cost tracking (supply costs, page costs) Vendor-specific features (secure printing, pull print) Full Feature Tracking: docs/SETTINGS_TODO.md (legacy document unavailable)\nLicense (Add license information here)\nSupport Issues: GitHub Issues Documentation: docs/ API Reference: docs/API_REFERENCE.md Contributing Fork the repository Create a feature branch Write tests for new features Ensure all tests pass: go test ./... Submit pull request Coding Standards:\nFollow Go conventions (gofmt, golint) Write tests for major features Document exported functions Update README when adding features Module Documentation Agent - Discovery protocols and device detection Scanner - SNMP querying and vendor profiles Logger - Structured logging with SSE Storage - SQLite persistence layer ","title":"PrintMaster Agent","url":"/components/agent/"},{"section":"project","text":" PrintMaster automatically discovers and monitors network printers and copiers. Built for MSPs, MPS providers, copier dealers, and IT departments managing print fleets.\nWhy PrintMaster? PrintMaster was born from real-world frustration managing multi-vendor print fleets. Existing solutions meant juggling multiple tools—PrintAudit for metering, Epson Remote Services for Epson devices, Kyocera Net Manager for Kyocera, Epson Device Admin for local management—each with their own quirks, agents that randomly disconnect, and per-device licensing fees.\nThe goal: combine the best of fleet monitoring and vendor-specific tools into one open-source solution that actually tells you when something goes wrong.\nTransparency This project uses AI-assisted development. Initial development relied significantly on AI tools, and this is disclosed in the spirit of transparency. As the project matures and gains users, development will slow down to focus on stability and correctness over feature velocity.\nThe maintainer is not a professional developer by trade, but has real-world experience in the copier/MPS industry and has contributed to other open-source projects (including OIDC SSO support for MeshCentral).\nContributions, feedback, and vendor-specific SNMP knowledge are welcome—printer MIBs are complex and vendor quirks are endless.\nFeatures Automated Discovery — SNMP-based network scanning finds printers automatically Fleet Monitoring — Track page counts, toner levels, and device status Multi-Site Support — Central server aggregates data from distributed agents Remote Access — WebSocket proxy to access agent UIs and printer admin pages Real-Time Updates — WebSocket heartbeat with automatic HTTP fallback Cross-Platform — Windows, Linux, macOS, Docker Screenshots Dashboard — Hierarchical view of tenants, sites, agents, and devices Fleet Agents — Monitor agent connectivity, versions, and status Fleet Devices — Track printers, consumables, and health status Fleet Metrics — Netdata-style time-series charts for throughput and usage System Logs — Real-time log streaming with search and filtering Quick Start Server (Docker) docker run -d \\ --name printmaster-server \\ -p 9090:9090 \\ -v printmaster-data:/var/lib/printmaster/server \\ -e ADMIN_PASSWORD=your-password \\ ghcr.io/printmaster-org/printmaster-server:latest Access at http://localhost:9090 — Login: admin / your password\nAgent (Windows) Download the MSI from Releases, or:\n# Install as service .\\printmaster-agent.exe --service install .\\printmaster-agent.exe --service start Access at http://localhost:8080\nAgent (Linux) # Debian/Ubuntu curl -fsSL https://packages.printmaster.work/install.sh | sudo bash Architecture ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Agent │────────▶│ Server │◀────────│ Agent │ │ Site A │ API │ (Central) │ API │ Site B │ └─────────────┘ └─────────────┘ └─────────────┘ ↓ ↓ ↓ Printers Web Dashboard Printers Agent: Runs at each site, discovers printers via SNMP, stores data locally, optionally reports to server. Can run standalone.\nServer: Central hub for multi-site management. Aggregates device data, provides fleet dashboard, enables remote access via WebSocket proxy.\nDocumentation Guide Description Installation Complete setup instructions for all platforms Getting Started First steps after installation Features Detailed feature documentation Configuration All configuration options Troubleshooting Common issues and solutions FAQ Frequently asked questions Developer documentation is in docs/dev/.\nConfiguration Connect agent to server — edit config.toml:\n[server] enabled = true url = \"http://your-server:9090\" agent_name = \"Office A\" Customize SNMP settings:\n[snmp] community = \"public\" timeout_ms = 2000 retries = 1 See Configuration Guide for all options.\nContributing Fork the repository Create a feature branch Make your changes with tests Submit a pull request See CONTRIBUTING.md for guidelines.\nLicense MIT License — see LICENSE\nLinks Releases Issues Discussions ","title":"PrintMaster Overview","url":"/project/overview/"},{"section":"components","text":"Central management hub for PrintMaster fleet management\nThe PrintMaster Server aggregates data from multiple PrintMaster agents deployed across networks, providing centralized monitoring, reporting, and alerting.\nArchitecture ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Agent 1 │────────▶│ │◀────────│ Agent 2 │ │ Site A │ HTTP │ Server │ HTTP │ Site B │ │ (Local) │ │ (Central) │ │ (Remote) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ SQLite/PostgreSQL ▼ ┌─────────────┐ │ Database │ │ (All data) │ └─────────────┘ Features Multi-Agent Management - Register and monitor multiple agents Centralized Storage - All device data and metrics in one place Real-time Monitoring - Live status from all connected agents Reporting - Cross-site fleet reports and analytics Alerting - Notifications for toner low, errors, offline devices Web UI - Manage entire fleet from browser Quick Start Build # From project root cd server go build -o printmaster-server.exe . Run # Default ports: HTTP 9090, HTTPS 9443 .\\printmaster-server.exe # Custom port .\\printmaster-server.exe -port 8080 # Custom database path .\\printmaster-server.exe -db C:\\data\\printmaster\\server.db Configure Agents Point agents to server in their config:\n# agent/config.ini [server] url = http://your-server:9090 agent_id = agent-site-a upload_interval = 60 API Endpoints Agent API (v1) Protocol Version: 1\nAgents communicate with server using these endpoints:\nRegister Agent POST /api/v1/agents/register Content-Type: application/json { \"agent_id\": \"agent-001\", \"agent_version\": \"1.0.0\", \"protocol_version\": \"1\", \"hostname\": \"agent-host\", \"ip\": \"192.168.1.100\", \"platform\": \"windows\" } Heartbeat POST /api/v1/agents/heartbeat Content-Type: application/json { \"agent_id\": \"agent-001\", \"timestamp\": \"2025-11-03T18:00:00Z\", \"status\": \"active\" } Upload Devices POST /api/v1/devices/batch Content-Type: application/json { \"agent_id\": \"agent-001\", \"timestamp\": \"2025-11-03T18:00:00Z\", \"devices\": [ { \"serial\": \"ABC123\", \"manufacturer\": \"HP\", \"model\": \"LaserJet Pro 400\", \"ip\": \"192.168.1.50\", ... } ] } Upload Metrics POST /api/v1/metrics/batch Content-Type: application/json { \"agent_id\": \"agent-001\", \"timestamp\": \"2025-11-03T18:00:00Z\", \"metrics\": [ { \"serial\": \"ABC123\", \"page_count\": 12345, \"toner_black\": 45, \"toner_cyan\": 78, ... } ] } Development Status Current Version: 0.1.0 (Early Development)\nImplemented ✅ Basic HTTP server ✅ Protocol v1 endpoint scaffolding ✅ Version management ✅ Health checks TODO Database schema (agents, devices, metrics) Agent authentication/authorization Data storage and retrieval Web UI for management Reporting engine Alert system HTTPS with cert management Database Schema (Planned) agents id (PK) agent_id (unique) hostname ip platform version protocol_version registered_at last_seen status devices serial (PK) agent_id (FK) manufacturer model ip hostname … (all device fields) first_seen last_seen updated_at metrics_history id (PK) serial (FK) agent_id (FK) timestamp page_count toner_levels (JSON) … (all metrics) Version Strategy Server and Agent versions:\nShare same Protocol Version for compatibility Can have different component versions Protocol v1 = Server 0.x-1.x + Agent 0.x-1.x Breaking protocol change = bump both to 2.0 See docs/ROADMAP_TO_1.0.md for full versioning strategy.\nConfiguration Environment Variables SERVER_PORT=9090 # HTTP port SERVER_HTTPS_PORT=9443 # HTTPS port SERVER_DB_PATH=/path/to/db # Database location SERVER_LOG_LEVEL=info # Log level config.ini (future) [server] port = 9090 https_port = 9443 db_path = /var/lib/printmaster/server.db [security] require_agent_auth = true api_key_header = X-Agent-API-Key [alerts] email_enabled = true smtp_server = smtp.example.com smtp_from = alerts@example.com License Same as PrintMaster Agent (see root LICENSE file)\n","title":"PrintMaster Server","url":"/components/server/"},{"section":"development","text":"Last Updated: December 27, 2025\nThis document provides a comprehensive analysis of test coverage across the PrintMaster codebase, identifying well-tested areas, gaps, and recommendations for improvement.\nExecutive Summary Component Test Files Coverage Level Priority for Improvement Server Storage 20+ ⭐⭐⭐⭐⭐ Excellent Low Agent Scanner 8 ⭐⭐⭐⭐ Good Medium Common Libraries 12 ⭐⭐⭐⭐ Good Low Server API/Handlers 3 ⭐⭐⭐ Moderate Medium Agent Storage 6 ⭐⭐⭐⭐ Good Low Reports System 3 ⭐⭐⭐ Moderate Medium WebSocket/Proxy 2 ⭐⭐⭐ Moderate Medium E2E Tests 3 ⭐⭐ Basic HIGH Email Templates 0 ⭐ None HIGH Spooler (Windows) 0 ⭐ None HIGH Metrics Collector 0 ⭐ None HIGH Report Scheduler 0 ⭐ None HIGH OIDC Handlers 0 ⭐ None Medium JS/Frontend 14 ⭐⭐⭐ Moderate Medium 1. Go Unit Tests (92 test files) 1.1 Server Package (server/) ✅ Excellent Coverage File/Package Tests Description storage/*_test.go 20+ files Most comprehensive testing - Covers all CRUD operations, agents, devices, metrics, alerts, reports, users, sessions, tenants, settings, OIDC, dialects main_test.go 30+ tests Health endpoints, auth, heartbeat, batch uploads, token handling, device proxy parsing config_test.go Multiple Configuration loading/parsing websocket_test.go 8+ tests WebSocket connection, heartbeat, proxy requests Key storage tests:\nbase_store_test.go - Agent lifecycle, device lifecycle, metrics, tenants, sites, audit log alerts_test.go / alerts_extended_test.go - Alert lifecycle, filters, notifications reports_test.go / reports_extended_test.go - Report definitions, schedules, runs, cleanup users_test.go - User CRUD, authentication, password updates settings_test.go / settings_extended_test.go - Global/tenant settings, fleet policies dialect_test.go - SQLite and Postgres SQL generation ⚠️ Moderate Coverage File/Package Tests Gaps alerts/ 2 files evaluator_test.go, notifier_test.go - Good but no integration tests reports/ 2 files formatter_test.go, generator_test.go - Missing scheduler tests releases/ 3 files API, manager, intake worker selfupdate/ 2 files detect, manager settings/ 2 files API, agent payload tenancy/ 2 files handlers, store authz/ 1 file Authorization logic updatepolicy/ 1 file Update policy API ❌ No Test Coverage File/Package Lines Priority Notes email/templates.go 1153 HIGH Email generation for alerts, reports, password reset - zero tests metrics/collector.go 419 HIGH Server metrics collection worker - zero tests reports/scheduler.go 409 HIGH Report scheduling - zero tests websocket.go 602 Medium Some coverage in websocket_test.go but many untested paths oidc_handlers.go ~300+ Medium OIDC SSO handlers tls.go ~100 Low TLS configuration device_auth.go ~150 Medium Device authentication logging_helpers.go ~50 Low Helper functions api_reports.go ~200 Medium Report API endpoints 1.2 Agent Package (agent/) ✅ Excellent Coverage File/Package Tests Description scanner/ 8 files Pipeline, detector, query, SNMP batch, capabilities scanner/vendor/ 4 files Epson, HP, Kyocera parsing, vendor detection scanner/capabilities/ 1 file Device capability detection (color, mono, MFP) storage/ 6 files SQLite operations, rotation, downsampling, scan history, paths agent/ (subpkg) 5 files Range parser, probe, parse, server client, WebSocket client Key scanner tests:\npipeline_test.go - Liveness/detection pool orchestration detector_test.go - Saved device bypass, SNMP detection, deep scan, enrichment query_test.go - Query profiles (minimal, essential, metrics, full), vendor walks vendor_test.go - Vendor detection, enterprise number extraction, supply colors capabilities_test.go - 14+ tests for printer/color/mono detection, metric relevance Key storage tests:\nsqlite_test.go - 14+ tests for CRUD, atomic transactions, metrics rules rotation_test.go - Database rotation, backup cleanup scan_history_test.go - Scan history tracking, visibility filtering downsample_test.go - Metrics downsampling ⚠️ Moderate Coverage File/Package Tests Gaps config_test.go Yes Basic config loading config_store_test.go Yes Config store operations upload_worker_test.go 2 tests Only heartbeat settings handling tested settings_manager_test.go Yes Settings management update_policy_test.go 5 tests Policy precedence logic service_test.go Yes Service control server_probe_test.go Yes Server connectivity probing server_config_test.go Yes Server configuration autoupdate/manager_test.go Yes Auto-update manager featureflags/ 1 file Feature flag parsing proxy/vendor_login_test.go Yes Vendor-specific login handling supplies/normalize_test.go Yes Supply description normalization ❌ No Test Coverage File/Package Lines Priority Notes spooler/* ~200+ HIGH Windows print spooler monitoring - zero tests autoupdate_worker.go ~300 Medium Auto-update orchestration spooler_worker.go ~200 Medium Spooler background worker discover.go ~200 Medium Main discovery orchestration (tested indirectly) main.go ~800+ Medium Application bootstrap (difficult to unit test) scanner_api.go ~150 Low Scanner HTTP API handlers 1.3 Common Package (common/) ✅ Good Coverage File/Package Tests Description logger/logger_test.go 12+ tests Log levels, context, circular buffer, file output, rate limiting, rotation, concurrency ws/ 3 files Message serialization, hub register/unregister/broadcast, connection nil-safety settings/types_test.go 3 tests Default settings, sanitization, schema validation storage/types_test.go 5 tests Device/metrics JSON round-trip, field locks, filters updatepolicy/types_test.go 8 tests Version pin, agent override, policy JSON, maintenance windows config/config_test.go Yes Configuration parsing snmp/oids/oids_test.go 3 tests OID format validation, uniqueness, MIB prefixes util/helpers_test.go 4 tests Octet string decoding, integer coercion util/secret_test.go 2 tests Encryption round-trip, key validation 2. JavaScript Tests (14 test files) 2.1 Unit Tests (common/web/__tests__/) Test File Coverage Description formatters.test.js ⭐⭐⭐ Date/number formatting utilities debounce.test.js ⭐⭐⭐ Debounce function clipboard.test.js ⭐⭐⭐ Clipboard operations dom-shims.test.js ⭐⭐⭐ DOM compatibility shims rbac.test.js ⭐⭐⭐ Role-based access control save-device.test.js ⭐⭐⭐ Device save operations settings-save.test.js ⭐⭐⭐ Settings persistence server/web/__tests__/login-page.test.js ⭐⭐ Login page functionality 2.2 Playwright E2E Tests (common/web/__tests__/playwright/) Test File Coverage Description smoke.test.js ⭐⭐ Toast notifications, confirm modals login.test.js ⭐⭐ Login flow sites-api.test.js ⭐⭐ Sites API reports-api.test.js ⭐⭐ Reports API alerting-api.test.js ⭐⭐ Alerting API tenancy-tabs.test.js ⭐⭐ Multi-tenancy UI 3. Integration/E2E Tests (tests/) Test File Status Description websocket_proxy_test.go ⭐⭐⭐ WebSocket proxy flow, unreachable targets - 432 lines http_api_test.go ⭐⭐⭐ Agent registration, heartbeat - 314 lines E2E_TESTING.md 📄 Strategy document (tests WIP) Current E2E Limitations:\nTests use mock servers, not actual binaries No real agent-server integration tests running in CI Process management helpers not fully implemented 4. Critical Gaps Analysis 🔴 HIGH Priority (No Tests) 1. Email Templates (server/email/templates.go - 1153 lines) Risk: Email rendering bugs affect user experience for alerts, reports, password resets Recommendation:\n// Suggested tests: - TestRenderAlertEmail_AllSeverities - TestRenderReportEmail_AllFormats - TestRenderPasswordResetEmail - TestRenderInvitationEmail - TestThemeVariants_DarkLightAuto - TestEmailHTMLValidation 2. Metrics Collector (server/metrics/collector.go - 419 lines) Risk: Fleet monitoring data could be incorrect or missing Recommendation:\n// Suggested tests: - TestCollector_FleetDataCollection - TestCollector_AggregationCycle - TestCollector_PruneCycle - TestCollector_ConcurrentAccess - TestCollector_ErrorRecovery 3. Report Scheduler (server/reports/scheduler.go - 409 lines) Risk: Scheduled reports might not run or fail silently Recommendation:\n// Suggested tests: - TestScheduler_RunsDueReports - TestScheduler_HandlesFailures - TestScheduler_CalculatesNextRun - TestScheduler_StartStop - TestScheduler_ConcurrentSchedules 4. Windows Spooler (agent/spooler/ - ~200+ lines) Risk: USB printer discovery broken on Windows Recommendation:\n// Suggested tests (with mock Windows APIs): - TestDiscoverLocalPrinters_Windows - TestParsePortName - TestClassifyPrinterType - TestJobWatcher_Events 🟡 MEDIUM Priority 5. Upload Worker (agent/upload_worker.go) Current: Only 2 tests for heartbeat settings Missing:\nDevice batch upload logic Metrics batch upload logic Retry/backoff behavior Error handling paths 6. WebSocket Full Coverage (server/websocket.go - 602 lines) Current: 8 tests cover basic flows Missing:\nProxy timeout handling Connection loss recovery Concurrent request handling Agent reconnection logic 7. OIDC Handlers (server/oidc_handlers.go) Risk: SSO authentication failures Missing:\nOAuth flow tests Token validation Provider configuration Session linking 5. Test Quality Observations ✅ Strengths Storage layer is exemplary - Comprehensive CRUD tests, edge cases, multiple dialects Scanner uses dependency injection - MockSNMP pattern enables thorough unit testing Table-driven tests - Many tests use idiomatic Go patterns Parallel execution - Most tests use t.Parallel() for speed In-memory databases - Storage tests use :memory: for isolation ⚠️ Areas for Improvement Missing negative tests - Many packages lack error-path testing No fuzz testing - Parser/deserializer code could benefit from fuzzing Limited concurrency tests - Race conditions not systematically tested No benchmark tests - Performance baselines not established E2E tests incomplete - tests/ directory has skeleton but not CI-integrated 6. Recommendations Immediate Actions (Next Sprint) Add email template tests (1153 lines untested)\nCreate server/email/templates_test.go Test each email type renders without error Validate HTML structure Add metrics collector tests (419 lines untested)\nCreate server/metrics/collector_test.go Mock store interface Test collection/aggregation cycles Add report scheduler tests (409 lines untested)\nCreate server/reports/scheduler_test.go Test schedule processing Test failure handling Short-term (Next Month) Expand upload worker tests\nAdd batch upload tests Test retry logic Test error scenarios Complete E2E test infrastructure\nImplement process management helpers Add to CI pipeline Test critical paths (registration → heartbeat → upload) Add OIDC handler tests\nMock OAuth providers Test authentication flows Long-term (Quarterly) Add benchmark tests for performance-critical paths Add fuzz tests for parsers (SNMP, vendor-specific) Improve coverage metrics - Set up coverage reporting in CI 7. Test Commands Reference # Run all agent tests cd agent \u0026\u0026 go test ./... -v # Run all server tests cd server \u0026\u0026 go test ./... -v # Run specific package tests cd agent/scanner \u0026\u0026 go test -v # Run tests with coverage go test ./... -coverprofile=coverage.out go tool cover -html=coverage.out # Run short tests only (skip E2E) go test -short ./... # Run E2E tests cd tests \u0026\u0026 go test -v ./... # Run JavaScript tests npm test # Run Playwright E2E tests npm run test:e2e 8. Coverage by Lines of Code (Estimated) Component Total LOC Tested LOC Coverage % Server Storage ~8,000 ~7,500 ~94% Agent Scanner ~3,000 ~2,500 ~83% Agent Storage ~2,000 ~1,800 ~90% Common Libs ~2,000 ~1,600 ~80% Server Main/API ~3,000 ~1,500 ~50% Untested Files - email/templates 1,153 0 0% - metrics/collector 419 0 0% - reports/scheduler 409 0 0% - spooler/* ~400 0 0% Appendix: Test File Inventory All 92 Go Test Files Agent (33 files) agent/config_store_test.go agent/config_test.go agent/health_test.go agent/main_settings_test.go agent/server_config_test.go agent/server_probe_test.go agent/service_test.go agent/settings_manager_test.go agent/update_policy_test.go agent/upload_worker_test.go agent/agent/parse_test.go agent/agent/probe_test.go agent/agent/rangeparser_test.go agent/agent/server_client_test.go agent/agent/snmp_performance_test.go agent/agent/ws_client_test.go agent/autoupdate/manager_test.go agent/featureflags/featureflags_test.go agent/proxy/vendor_login_test.go agent/scanner/capabilities/capabilities_test.go agent/scanner/detector_test.go agent/scanner/pipeline_test.go agent/scanner/query_test.go agent/scanner/snmp_batch_test.go agent/scanner/vendor/epson_remote_test.go agent/scanner/vendor/epson_st2_parser_test.go agent/scanner/vendor/vendor_test.go agent/storage/downsample_test.go agent/storage/paths_test.go agent/storage/rotation_integration_test.go agent/storage/rotation_test.go agent/storage/scan_history_test.go agent/storage/sqlite_test.go agent/supplies/normalize_test.go Server (37 files) server/config_test.go server/main_test.go server/injection_test.go server/static_test.go server/testutil_test.go server/websocket_test.go server/auth_ratelimit_test.go server/rbac_handlers_test.go server/alerts/evaluator_test.go server/alerts/notifier_test.go server/authz/authz_test.go server/internal/db/driver_test.go server/logger/logger_test.go server/releases/api_test.go server/releases/intake_worker_test.go server/releases/manager_test.go server/reports/formatter_test.go server/reports/generator_test.go server/selfupdate/detect_test.go server/selfupdate/manager_test.go server/settings/agent_payload_test.go server/settings/api_test.go server/storage/aggregated_metrics_test.go server/storage/alerts_extended_test.go server/storage/alerts_test.go server/storage/base_store_extended_test.go server/storage/base_store_test.go server/storage/dialect_extended_test.go server/storage/dialect_test.go server/storage/helpers_test.go server/storage/oidc_test.go server/storage/postgres_integration_test.go server/storage/release_artifacts_test.go server/storage/reports_extended_test.go server/storage/reports_test.go server/storage/selfupdate_runs_test.go server/storage/settings_extended_test.go server/storage/settings_test.go server/storage/sqlite_sessions_test.go server/storage/store_test.go server/storage/types_test.go server/storage/users_test.go server/tenancy/handlers_test.go server/tenancy/store_test.go server/updatepolicy/api_test.go Common (12 files) common/config/config_test.go common/logger/logger_test.go common/settings/types_test.go common/snmp/oids/oids_test.go common/storage/types_test.go common/updatepolicy/types_test.go common/util/helpers_test.go common/util/secret_test.go common/ws/conn_test.go common/ws/hub_test.go common/ws/message_test.go Integration/E2E (2 files) tests/http_api_test.go tests/websocket_proxy_test.go All 14 JavaScript Test Files Unit Tests common/web/__tests__/clipboard.test.js common/web/__tests__/debounce.test.js common/web/__tests__/dom-shims.test.js common/web/__tests__/formatters.test.js common/web/__tests__/rbac.test.js common/web/__tests__/save-device.test.js common/web/__tests__/settings-save.test.js server/web/__tests__/login-page.test.js Playwright E2E Tests common/web/__tests__/playwright/alerting-api.test.js common/web/__tests__/playwright/login.test.js common/web/__tests__/playwright/reports-api.test.js common/web/__tests__/playwright/sites-api.test.js common/web/__tests__/playwright/smoke.test.js common/web/__tests__/playwright/tenancy-tabs.test.js ","title":"PrintMaster Test Coverage Analysis","url":"/development/test-coverage-analysis/"},{"section":"development","text":"Consolidated pending features and improvements from across the codebase.\nCurrent Version: Agent v0.23.6, Server v0.23.6\n🔴 High Priority (Pre-1.0) USB Printer Support Implemented via an IPP-USB HTTP proxy (Windows only) — see USB_IMPLEMENTATION.md. The earlier pure-Go SNMP-over-USB plan (gousbsnmp) was abandoned and never built.\nIPP-USB proxy + web-scraped metrics (Windows) Linux/macOS USB device enumeration + proxy support Same metrics as network printers (page counts, toner, supplies) - USB scraping is best-effort per vendor, not at parity yet USB printer configuration UI USB/network printer differentiation in main devices table (currently tracked separately) SNMPv3 Support Security enhancement for enterprise deployments.\nSNMPv3 authentication (MD5, SHA) SNMPv3 privacy/encryption (DES, AES) Context engine ID support Credentials storage (encrypted at rest) Per-device SNMPv3 configuration UI for SNMPv3 credential management Installer Repackaging (Auto-Update Phase 3) Enable fleet-customized installers.\nBuild packager: unpack release → inject fleet config → repack Authenticated download endpoints (/api/v1/installers/{fleet}/{platform}) “Download installer” button in server UI Support for ZIP/TAR/MSI wrapper formats 🟡 Medium Priority Analytics \u0026 Metrics Supply Predictions Toner depletion forecasting based on historical usage Drum/imaging unit lifecycle tracking Fuser lifecycle tracking “Days remaining” estimates per supply Prediction confidence intervals Cost Tracking Per-page cost configuration (mono/color) Per-device cost overrides Monthly cost aggregation Cost-per-department (if location/tags supported) Cost trending reports Utilization Analytics Capacity utilization (% of duty cycle used) Peak usage hours detection Idle time tracking Utilization score (0-100) Performance Optimization Load Testing Test with 100+ agents reporting to server Test with 1000+ printers tracked Database performance under load Memory leak detection CPU profiling and optimization Caching SNMP result cache (configurable TTL) Device info cache (LRU, 1000 entries) HTTP response caching where appropriate Security Hardening TLS/HTTPS HTTPS enforced by default Auto-generate self-signed certs on first run Certificate renewal/rotation Support for custom certificates TLS 1.2+ only Additional Security Two-factor auth (TOTP) API rate limiting middleware Encrypt SNMP v3 credentials at rest Database encryption option (SQLCipher) 🟢 Lower Priority (Post-1.0) Fleet Dashboard Metrics Total fleet page count trends Fleet-wide supply levels overview Geographic distribution map Alert summary dashboard Device health score aggregation Job Analytics Job size distribution Duplex vs simplex ratio Color vs mono ratio Peak job times User/department job breakdown (if print accounting available) Environmental Metrics Paper usage tracking (total sheets) Energy consumption estimation Carbon footprint calculation Sustainability score Environmental report generation Reporting PDF/Excel export Scheduled reports (email digest) Custom report builder Multi-site reports Integrations Webhook notifications (device discovery, alerts) MQTT publishing (IoT integration) Prometheus metrics endpoint Syslog forwarding 🔧 Technical Debt Documentation Complete API reference for new endpoints Document all environment variables Video walkthroughs (YouTube) Migration guide (0.x → 1.0) Testing Browser compatibility (Chrome, Firefox, Edge, Safari) Accessibility testing (web UI) Integration test coverage for WebSocket proxy Vendor-specific SNMP response mocking Code Quality Config validation for all settings Standardize error handling patterns Database index optimization audit ✅ Recently Completed These items were completed and can be referenced in their implementation:\n✅ Multi-agent server architecture (v0.2.0) ✅ WebSocket proxy for remote agent/device access ✅ Database rotation and recovery system ✅ Metrics tiering (raw/hourly/daily/monthly) ✅ Linux package repositories (APT/DNF) ✅ Docker multi-arch builds ✅ Paper tray status tracking (December 2025) ✅ Shared web assets via go:embed ✅ Auto-update policy framework (Phase 1-2) ✅ Release manifest signing (Ed25519) Notes USB support (IPP-USB proxy) works on Windows; Linux/macOS parity is the main gap Security features (SNMPv3, TOTP) needed before enterprise adoption Analytics features can ship incrementally post-1.0 Avoid scope creep on reporting - MVP first Last Updated: December 2025\n","title":"PrintMaster TODO","url":"/development/todo/"},{"section":"development","text":"Project Structure (Overview) printmaster/ ├── agent/ # Agent service and embedded UI (Go) │ ├── main.go # Agent entrypoint │ ├── config.go / config.toml # Agent configuration loader + sample │ ├── agent/ # Discovery + protocol workers (mDNS, SSDP, etc.) │ ├── scanner/ # SNMP pipeline, vendor registry, metrics extraction │ ├── storage/ # Embedded SQLite schema + migrations │ ├── proxy/ / supplies/ # Proxy tunnel + consumables helpers │ ├── featureflags/ # Toggle definitions used by agent UI │ ├── web/ # Embedded UI assets (built into the binary) │ └── docs/ # Agent-specific reference material ├── server/ # Multi-agent server/API hub (Go) │ ├── main.go # Server entrypoint │ ├── config.go / config.toml # Server configuration + defaults │ ├── websocket.go # Agent tunnel / live proxy handling │ ├── storage/ # Server persistence + tenancy data │ ├── authz/, logger/, tls.go # Auxiliary subsystems │ └── web/ # Server UI + static assets ├── common/ # Shared Go modules (config, logger, snmp, util, web) ├── docs/ # Architecture + operations documentation ├── dev/ # Local developer scripts (e.g., launch.ps1) ├── scripts/ # Operational PowerShell helpers (kill, update, etc.) ├── tools/ # Standalone utilities and generators ├── tests/ # Integration / e2e test harnesses ├── static/ # Third-party front-end assets (e.g., flatpickr) ├── ui/ # Legacy UI experiments (kept for reference) ├── logs/, test-results/ # Output folders (ignored in git) ├── build.ps1 / release.ps1 # Build + release orchestration └── README.md # High-level overview Module Descriptions Agent (agent/) Single-binary agent that discovers printers, collects metrics, persists to SQLite, and serves the embedded UI. Key subpackages:\nagent/agent/: discovery workers (TCP probing, mDNS, SSDP, WS-Discovery, SNMP traps, range parsing). agent/scanner/: multi-stage SNMP scan pipeline and vendor-specific profiles. agent/storage/: schema v8+, metrics tiering, configuration/state persistence. agent/web/: React/HTMX UI bundled via //go:embed. Server (server/) Central service coordinating multiple agents, providing RBAC, WebSocket tunneling, and consolidated UI/APIs. Uses its own SQLite database and mirrors the agent’s config model via config.toml.\nCommon (common/) Shared Go modules consumed by both binaries (config loader, logger, SNMP abstractions, settings helpers, shared web components, WebSocket utilities). This keeps cross-cutting concerns in one place.\nDocumentation (docs/ and agent/docs/) docs/ holds architecture, roadmap, deployment, and operator guides. agent/docs/ contains deep dives (MIB profiles, SNMP research) that are specific to the agent runtime.\nTooling \u0026 Automation build.ps1, release.ps1, status.ps1, version.ps1: primary automation entrypoints. dev/launch.ps1: developer convenience launcher (tests + run agent). package.json + playwright.config.js: UI tests (Jest/Playwright). .vscode/tasks.json (generated) + VS Code tasks listed in BUILD_WORKFLOW.md. Tests \u0026 Fixtures Go unit tests live beside their packages. tests/ (with supporting test-results/) will hold cross-component or UI regression suites. common/web/__tests__ contains Playwright specs invoked via npm run test:playwright. Architecture Principles (Current) Separation of Concerns: discovery vs. SNMP pipeline vs. persistence vs. UI. Shared foundations: anything reusable lives in common/ to keep agent/server parity. Context-aware operations: network calls propagate context.Context for cancellation. Embedded assets: both binaries embed their UI/static files to stay single-file deployable. Data flow (agent):\nDiscovery (agent/agent) → Candidate hosts → SNMP pipeline (agent/scanner) → Vendor parsers → Metrics/device records → SQLite (agent/storage) → UI/API Development Workflow (Quick Reference) Build: ./build.ps1 agent, ./build.ps1 server, or ./build.ps1 both. Test: cd agent \u0026\u0026 go test ./..., cd server \u0026\u0026 go test ./..., or run VS Code “Test: Agent/Server”. Release: ./release.ps1 agent|server|both \u003cpatch|minor|major\u003e (handles VERSION bumping, tagging, artifacts). Debug: ./dev/launch.ps1 for the agent; server tasks defined in .vscode/launch.json. Related Documentation Agent README Server README Configuration Guide Build Workflow Roadmap (legacy document unavailable) Deployment Guides Storage Schema Notes (legacy document unavailable) Need a document not listed here? Check docs/README.md (coming soon) for a living index of authoritative references.\n","title":"PROJECT STRUCTURE","url":"/development/project-structure/"},{"section":"development","text":"This file documents what the server accepts in the range editor textarea and how the ParseRangeText behavior works.\nSupported formats\nSingle IPv4 address: e.g. 192.168.1.42 CIDR notation: e.g. 192.168.1.0/24 Full range (start-end): e.g. 192.168.1.10-192.168.1.20 Short-hand range (right side maps to last N octets): 192.168.1.10-20 expands to 192.168.1.10 through 192.168.1.20. 10-20 is interpreted relative to the default/prepended subnet only when configured in the UI as Add/Override behavior. Last-octet wildcard: 192.168.1.x or 192.168.1.* (expands the last octet 0..255) Behaviors and constraints\nOnly IPv4 is supported by the parser. IPv6 lines are ignored with an error. The parser deduplicates addresses and enforces a default expansion limit of 4096 addresses to avoid accidental large scans. Empty and comment lines starting with # are ignored. Error reporting\nThe server returns a parse error which includes the offending line and a short message. The UI shows an alert for parse failures. Persistence\nSaved ranges are stored in the agent’s SQLite agent_config table and loaded automatically on startup; edits flow through the web UI or API rather than flat files. ","title":"Range syntax and parsing rules","url":"/development/range-syntax/"},{"section":"components","text":"Remaining tasks for Printer MIB pairing \u0026 candidate workflows\nOverview\nThis document lists remaining work items, priorities, acceptance criteria, and testing notes for the features implemented during the recent changes: pairing of list columns (name/level) from saved MIB walks, UI flow to inspect saved walks, and persisting candidate mappings.\nHigh-level goals\nLet the agent detect and present column-based consumable information (e.g. toner names and levels) from saved MIB walks. Allow users to persist discovered column mappings into vendor candidate profiles quickly and reliably. Provide robust normalization and tests for edge-cases across vendors. Priority tasks\nPer-pair quick-add persistence (high) What: Add a direct “Add” button in the printer details modal to POST the chosen column OID to /vendor/add_oid with column_type set (e.g. toner_names or toner_levels), optionally with vendor selection. Why: Speed up the mapping workflow so users can persist mappings found during walk inspection without opening the full editor. Files: main.go (client JS), mib_suggestions_api.go (server handler already accepts column_type but tests/behavior should be added/verified). Acceptance: Clicking the button persists the mapping into the appropriate candidate file and shows a confirmation message. Unit tests mock the handler and assert payload and file contents. Store probe_columns in candidate JSON (high) What: Add a top-level probe_columns object to candidate JSON (e.g. {\"probe_columns\": {\"1.3.6.1.2.1.43.11.1.1.6.1\": \"toner_names\"}}). Separate column mappings from single-value probe_properties for clarity. Why: Keeps column hints separate and makes UI editing simpler. Files: mib_suggestions_api.go (read/write), candidate schema docs. Acceptance: After adding a column via /vendor/add_oid with column_type, the candidate file contains probe_columns with normalized OID -\u003e canonical token. UI: Candidate editor to show/edit probe_columns (medium) What: Modify the candidate editor UI to list probe_columns, allow edit/remove, and save back to candidate JSON. Files: main.go (client JS UI), server vendor/update remains compatible. Acceptance: Users can edit and save probe_columns and changes persist to disk. Unit tests: multi-ink pairing and edge cases (high) What: Expand pairing tests to include: Multiple-black instances (e.g. BK1, BK2), ensure they map correctly. Missing level column (name only), expect level=-1. IP-style instance suffixes (e.g. .10.20.30.40), ensure normalization matches by suffix. Non-numeric level values (e.g. “85%”, hex-encoded), ensure CoerceToInt handles them or fallback to -1. Files: agent/paired_toner_test.go, additional tests under agent/agent. Acceptance: Tests assert correct pairing and are deterministic. Unit test: vendorAddOid normalization (medium) What: Ensure /vendor/add_oid strips instance suffixes and stores normalized OIDs in candidate JSON. Test for both numeric- and IP-suffixed instance forms. Files: vendor_handlers_test.go. Acceptance: Candidate JSON contains normalized OID keys. Add sample saved-walk fixtures (low) What: Add a small, representative saved-walk JSON under tests/fixtures/ used by UI and server tests. Why: Gives developers deterministic data for tests and manual QA. Acceptance: Tests referencing these fixtures should pass. Docs: candidate schema and UI flows (done) What: Documented in docs/REMAINING_TASKS.md and propose update to docs/MIB_PROFILES.md. Acceptance: Developers can read the doc to understand the schema and UI flow. Polish UX (low) Heuristics to auto-select vendor in the Add modal, undo/remove mappings, and small styling/labels. Testing notes\nRun unit tests frequently: cd c:\\temp\\printmaster\\agent go test ./... Manual UI test: Start the agent web UI (or go run main.go) and open the MIB Walk tab. Click “View Details” on a saved walk. Confirm the modal shows PrinterInfo and the Detected column pairs section. Click “Add Name Column” / “Add Level Column” on a pair to add it to a candidate; verify the candidate file is updated under mib_profiles/candidates/. Implementation notes and suggestions\nUse an explicit probe_columns map in candidate JSON to avoid overloading probe_properties. Normalize OIDs by stripping the last instance suffix when writing candidates; add a small test harness for different suffix patterns. Consider adding a simple client-side confirmation toast when mappings are successfully persisted. If you want, I can implement task #1 (direct POST quick-add from the details modal) next — it’s a small change and gives immediate UX wins. Otherwise tell me which task to start and I’ll implement it and run tests.\n","title":"REMAINING TASKS","url":"/components/agent/docs/remaining-tasks/"},{"section":"components","text":"Location: agent/scanner/\nThe scanner module is responsible for device detection, SNMP querying, and printer information extraction. It provides a vendor-aware, configurable scanning system.\nArchitecture Overview scanner/ ├── detector.go # Device type detection (IsPrinter? confidence scoring) ├── pipeline.go # Multi-stage scan orchestration (liveness → detection → deep scan) ├── query.go # SNMP query execution and vendor-specific data collection ├── snmp.go # Low-level SNMP communication wrapper ├── enumerator.go # IP range enumeration and subnet handling └── vendor/ # Vendor-specific OID profiles and parsers ├── hp.go ├── canon.go ├── brother.go ├── epson.go ├── kyocera.go ├── lexmark.go ├── ricoh.go ├── samsung.go ├── xerox.go ├── generic.go # Fallback standard Printer-MIB └── registry.go # Vendor detection and module selection Core Components Detector (detector.go) Purpose: Determine if a device is a printer and calculate confidence score.\nKey Functions:\nIsPrinterDevice(ctx, ip, timeout) (bool, float64, error): Fast printer detection Checks multiple signals: Open printer ports (9100 JetDirect, 631 IPP, 515 LPD) SNMP sysObjectID matches known printer enterprise IDs Presence of Printer-MIB OIDs HP/Canon/Brother-specific OIDs Returns confidence score 0.0-1.0 Example:\nisPrinter, confidence, err := IsPrinterDevice(ctx, \"192.168.1.100\", 5) if isPrinter \u0026\u0026 confidence \u003e 0.7 { // High confidence this is a printer } Query System (query.go) Purpose: Execute SNMP queries and extract structured printer information.\nKey Functions:\nQueryDevice(ctx, ip, community, timeout) (*PrinterInfo, error): Main query function Vendor auto-detection via sysObjectID Builds OID list from vendor module + generic Printer-MIB Executes SNMP WALK/GET operations Parses results using vendor-specific parsers Query Flow:\nInitial SNMP GET for sysDescr + sysObjectID Detect vendor from enterprise OID (e.g., HP = 1.3.6.1.4.1.11) Load vendor module (hp.go, canon.go, etc.) Build combined OID list (vendor + generic) Execute SNMP WALK for all OIDs Parse results with vendor parser Fallback to generic parser for unknown vendors Pipeline (pipeline.go) Purpose: Orchestrate multi-stage scanning with worker pools.\nStages:\nLiveness: Fast TCP/ICMP checks (100-200 workers) Detection: SNMP-based printer identification (20-50 workers) Deep Scan: Full SNMP walks + metrics (3-10 workers) Key Functions:\nScanRange(ctx, cidr, config) ([]PrinterInfo, error): Scan entire subnet ScanIPs(ctx, ips, config) ([]PrinterInfo, error): Scan specific IPs Benefits:\nReduces wasted work (don’t deep-scan non-printers) Configurable concurrency per stage Bounded resource usage Context-based cancellation Vendor System (vendor/) Purpose: Vendor-specific OID profiles and data parsing.\nInterface (VendorModule):\ntype VendorModule interface { Name() string GetOIDs() []string ParseMetrics(pdus []gosnmp.SnmpPDU) map[string]interface{} } Vendor Modules:\nHP (hp.go): Extended metrics (job accounting, fax pages, ADF scans) Canon (canon.go): Canon-specific counters and status Brother (brother.go): Brother drum life, toner levels Epson (epson.go): Epson ink tank levels Kyocera (kyocera.go): Kyocera maintenance counters Lexmark (lexmark.go): Lexmark enterprise OIDs Ricoh (ricoh.go): Ricoh/Savin/Lanier shared OIDs Samsung (samsung.go): Samsung-specific metrics Xerox (xerox.go): Xerox FreeFlow OIDs Generic (generic.go): Standard Printer-MIB fallback Vendor Detection (registry.go):\nfunc DetectVendor(sysObjectID string) VendorModule Maps enterprise OIDs to vendors:\n1.3.6.1.4.1.11.* → HP 1.3.6.1.4.1.1602.* → Canon 1.3.6.1.4.1.2435.* → Brother etc. SNMP Wrapper (snmp.go) Purpose: Low-level SNMP communication abstraction.\nKey Functions:\nSNMPGet(target, community, oids, timeout) ([]gosnmp.SnmpPDU, error) SNMPWalk(target, community, oid, timeout) ([]gosnmp.SnmpPDU, error) SNMPBulkWalk(target, community, oid, maxReps, timeout) ([]gosnmp.SnmpPDU, error) Features:\nRetry logic with exponential backoff Configurable timeouts Error handling and logging Thread-safe connection pooling Configuration Scanner behavior is controlled via ScannerConfig struct in main.go:\ntype scannerConfigStruct struct { SNMPTimeoutMs int // SNMP timeout in milliseconds (default: 2000) SNMPRetries int // SNMP retry count (default: 1) DiscoverConcurrency int // Max concurrent scan workers (default: 50) sync.RWMutex // Thread-safe access } Settings are loaded from:\nconfig.json (static config file) SQLite database (via Settings UI) Defaults if not specified Usage Examples Simple Single Device Query ctx := context.Background() pi, err := scanner.QueryDevice(ctx, \"192.168.1.100\", \"public\", 5) if err != nil { log.Fatal(err) } fmt.Printf(\"Found printer: %s %s (Serial: %s)\\n\", pi.Vendor, pi.Model, pi.Serial) Range Scan with Pipeline config := \u0026scanner.ScanConfig{ SNMPCommunity: \"public\", Timeout: 5, Concurrency: 50, } printers, err := scanner.ScanRange(ctx, \"192.168.1.0/24\", config) for _, p := range printers { fmt.Printf(\"%s: %s %s\\n\", p.IP, p.Vendor, p.Model) } Vendor-Specific Query // Auto-detects vendor from sysObjectID pi, _ := scanner.QueryDevice(ctx, \"10.0.0.50\", \"public\", 5) // Access vendor-specific metrics if pi.Vendor == \"HP\" { fmt.Printf(\"Job Accounting Pages: %d\\n\", pi.ExtendedMetrics[\"JobAccountingPages\"]) fmt.Printf(\"Fax Pages: %d\\n\", pi.ExtendedMetrics[\"FaxPages\"]) } Testing Test Files:\ndetector_test.go: Device detection and confidence scoring (8 tests) query_test.go: SNMP query and vendor parsing (21 tests) pipeline_test.go: Multi-stage pipeline orchestration (5 tests) Run Tests:\ncd agent go test ./scanner/... -v Test Coverage:\ngo test ./scanner/... -cover Performance Characteristics Timing (per device) Stage Duration Notes Liveness (TCP) 100-500ms Fast port checks Detection (SNMP) 500ms-2s Limited OID query Deep Scan (SNMP) 2-10s Full walk with retries Concurrency Limits Stage Default Workers Tuning Notes Liveness 100-200 I/O bound, can be high Detection 20-50 Network + SNMP processing Deep Scan 3-10 Expensive, limit to prevent overwhelming devices Memory Usage ~1KB per discovered device (in-memory PrinterInfo) ~5-10MB for SNMP library overhead Scales linearly with concurrent workers Error Handling Common Errors:\ncontext.DeadlineExceeded: SNMP timeout no such host: Invalid IP or DNS failure connection refused: SNMP disabled on device no response: Firewall blocking UDP 161 Retry Strategy:\nSNMP queries: Retry once with exponential backoff TCP probes: No retry (fail fast) Pipeline stages: Continue on individual device failures Integration Points With Agent Package (agent/agent/) Scanner is called by:\nDiscover() in detect.go: Full network scan LiveDiscoveryDetect() in scanner_api.go: Single device enrichment Live discovery handlers (mDNS, SSDP, WS-Discovery) With Storage (agent/storage/) Results are persisted via:\nUpsertDevice(): Save/update discovered printer GetDevices(): Retrieve all stored printers DeleteDevice(): Remove printer from database With Logger (agent/logger/) Scanner logs to structured logger:\nInfo: Discovery progress, device found Warn: SNMP failures, timeout warnings Error: Critical failures, configuration errors Debug: Detailed SNMP PDU parsing Future Enhancements Planned Features SNMPv3 support with authentication/encryption Bulk GET for improved performance Result caching with configurable TTL Dynamic OID discovery via MIB walking Parallel vendor module querying Performance Improvements Connection pooling for SNMP clients Adaptive timeout based on network latency Progressive OID querying (core → extended) Background re-scan of changed devices Troubleshooting No Devices Found Check network connectivity: ping \u003cdevice_ip\u003e Verify SNMP enabled on printer Test SNMP manually: snmpwalk -v2c -c public \u003cdevice_ip\u003e .1.3.6 Check firewall allows UDP 161 outbound Incomplete Data Increase SNMP timeout in settings Check vendor module supports printer model Review logs for parsing errors Try generic fallback (disables vendor detection) Slow Scans Reduce concurrency if network is saturated Lower SNMP timeout for faster failures Use liveness pre-filtering to skip dead IPs Limit scan range to active subnets Related Documentation Agent Module - Discovery protocols and detection logic Logger Module - Logging system Settings TODO (legacy document unavailable) - Unimplemented scanner features API Reference - HTTP endpoints using scanner ","title":"Scanner Module Documentation","url":"/components/agent/scanner/"},{"section":"development","text":"Overview This document outlines the security architecture for when the PrintMaster agent communicates with a central server, particularly for the reverse proxy feature where the server may relay connections between users and printers through the agent.\nThreat Model Agent → Server: Agent authenticates to server, sends discovery data, receives config User → Server → Agent → Printer: User accesses printer web UI through server proxy that routes through agent Key Risks: Unauthorized agent access, credential interception, session hijacking, MITM attacks, unauthorized printer access Security Layers 1. Agent Authentication (MVP) Purpose: Verify agent identity before accepting connections or data uploads.\nImplementation:\nBearer token authentication for agent API calls Long-lived tokens generated per agent on first registration Token stored securely in agent config (encrypted with machine key) All agent→server requests include Authorization: Bearer \u003ctoken\u003e Server Code Sketch:\nfunc agentAuthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get(\"Authorization\") if !strings.HasPrefix(token, \"Bearer \") { http.Error(w, \"Unauthorized\", 401) return } agentID, err := validateAgentToken(token[7:]) if err != nil { http.Error(w, \"Invalid token\", 401) return } ctx := context.WithValue(r.Context(), \"agentID\", agentID) next.ServeHTTP(w, r.WithContext(ctx)) }) } Agent Code Sketch:\nfunc (c *Client) uploadDevices(devices []Device) error { token, _ := c.cfg.GetConfigValue(\"agent_token\") req, _ := http.NewRequest(\"POST\", c.serverURL+\"/agent/devices/batch\", body) req.Header.Set(\"Authorization\", \"Bearer \"+token) req.Header.Set(\"Content-Type\", \"application/json\") resp, err := c.httpClient.Do(req) // ... handle response } 2. Mutual TLS (Production) Purpose: Cryptographically verify both agent and server identity; prevent MITM.\nImplementation:\nServer presents valid TLS certificate (Let’s Encrypt or internal CA) Agent presents client certificate signed by organization CA Both sides verify certificates during handshake Certificate rotation every 90 days Configuration:\n// Server side tlsConfig := \u0026tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: caCertPool, MinVersion: tls.VersionTLS13, CipherSuites: []uint16{tls.TLS_AES_256_GCM_SHA384}, } // Agent side cert, _ := tls.LoadX509KeyPair(\"agent-cert.pem\", \"agent-key.pem\") tlsConfig := \u0026tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caCertPool, MinVersion: tls.VersionTLS13, } 3. Request Signing (Production) Purpose: Ensure message integrity; prevent replay attacks.\nImplementation:\nHMAC-SHA256 signature of request body + timestamp Shared secret per agent (rotated monthly) Server validates signature and timestamp freshness (\u003c5 min) Example:\nfunc signRequest(body []byte, secret []byte) string { timestamp := time.Now().Unix() message := append([]byte(strconv.FormatInt(timestamp, 10)), body...) h := hmac.New(sha256.New, secret) h.Write(message) sig := base64.StdEncoding.EncodeToString(h.Sum(nil)) return fmt.Sprintf(\"%d.%s\", timestamp, sig) } // Header: X-Signature: 1730505123.a3f2b9c... 4. Proxy Session Isolation (Production) Purpose: Prevent one user’s session from accessing another user’s printer connections.\nImplementation:\nServer generates unique session tokens for each user proxy request Agent validates session token before establishing printer connection Session tokens expire after 15 minutes or on explicit disconnect Rate limiting per user (10 concurrent proxy sessions max) Flow:\n1. User requests printer proxy → Server validates user auth 2. Server generates session token → POST /agent/proxy/session/create 3. Agent returns WebSocket URL with session token 4. User connects via WebSocket → Agent validates token 5. Agent proxies user ↔ printer until disconnect or timeout 5. Credential Encryption in Transit (Production) Purpose: Protect stored printer credentials when syncing with server.\nImplementation:\nPrinter web UI credentials encrypted with agent-specific key before upload Server stores encrypted blobs, never sees plaintext passwords Agent decrypts locally when needed for auto-login Key rotation via secure channel (mTLS) Schema:\ntype DeviceCredentials struct { Serial string `json:\"serial\"` EncryptedCreds string `json:\"encrypted_creds\"` // AES-GCM(username:password) KeyID string `json:\"key_id\"` // For key rotation AuthType string `json:\"auth_type\"` AutoLogin bool `json:\"auto_login\"` } 6. IP Whitelisting (Production) Purpose: Restrict agent connections to known networks.\nImplementation:\nServer maintains per-agent IP whitelist Agents behind NAT report external IP on registration Dynamic IP updates via authenticated endpoint Fail2ban integration for abuse detection 7. Audit Logging (MVP) Purpose: Track all security-relevant events for compliance and forensics.\nImplementation:\nLog all agent auth attempts (success/failure) Log all proxy session creation/destruction Log credential access/updates Structured logging with correlation IDs Retention: 90 days hot, 1 year cold storage Example Entry:\n{ \"timestamp\": \"2025-11-01T22:45:32Z\", \"event\": \"proxy_session_created\", \"agent_id\": \"agent-001\", \"user_id\": \"user@example.com\", \"device_serial\": \"X59F000014\", \"session_id\": \"sess-a3f2b9c\", \"source_ip\": \"203.0.113.42\" } 8. Rate Limiting (MVP) Purpose: Prevent abuse and DoS attacks.\nImplementation:\nPer-agent: 100 device uploads/hour, 1000 metrics/hour Per-user: 10 proxy sessions, 100 requests/min Per-IP: 1000 requests/hour (global) Token bucket algorithm with burst allowance HTTP 429 with Retry-After header 9. Secure Communication Channels (MVP) Purpose: Encrypt all data in transit.\nImplementation:\nTLS 1.3 minimum for all connections HSTS headers (max-age=31536000, includeSubDomains) Certificate pinning for agent→server (optional) Disable weak ciphers and protocols Headers:\nStrict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff X-Frame-Options: DENY Content-Security-Policy: default-src 'self' 10. Zero-Trust Proxy Architecture (Enterprise) Purpose: Minimize trust assumptions; assume breach.\nImplementation:\nServer never directly proxies to printers All printer connections originate from agent (inside network) Server only routes authenticated user ↔ agent WebSocket Agent performs final authorization check before printer connection No credential storage on server (only encrypted blobs) Architecture:\nUser Browser ←→ Server (Auth/Route) ←→ Agent (Inside Network) ←→ Printer TLS 1.3 mTLS/WSS HTTPS Implementation Phases Phase 1: MVP Security (Required for Launch) Agent bearer token authentication TLS 1.3 for all connections Audit logging (auth, proxy, credentials) Basic rate limiting (per-agent/per-user) Secure headers (HSTS, CSP, X-Frame-Options) Phase 2: Production Hardening Request signing (HMAC-SHA256) Proxy session isolation with expiring tokens Credential encryption in transit (agent-specific keys) IP whitelisting with dynamic updates Enhanced rate limiting with token bucket Phase 3: Enterprise Features Mutual TLS (client certificates) Zero-trust proxy architecture Certificate rotation automation SIEM integration for audit logs Compliance reporting (SOC2, HIPAA) Configuration Example Agent Config (agent_settings.json):\n{ \"server_url\": \"https://printmaster.example.com\", \"agent_token\": \"encrypted:AgentTokenHere\", \"tls_cert_path\": \"/etc/printmaster/agent-cert.pem\", \"tls_key_path\": \"/etc/printmaster/agent-key.pem\", \"ca_cert_path\": \"/etc/printmaster/ca-cert.pem\", \"request_signing_enabled\": true, \"max_proxy_sessions\": 10 } Server Config (server.yaml):\nsecurity: tls: cert: /etc/printmaster/server-cert.pem key: /etc/printmaster/server-key.pem ca: /etc/printmaster/ca-cert.pem min_version: \"1.3\" require_client_cert: true auth: agent_token_expiry: 90d user_session_expiry: 24h proxy_session_expiry: 15m rate_limits: agent_uploads_per_hour: 100 user_proxy_sessions: 10 requests_per_minute: 100 audit: enabled: true retention_days: 90 log_path: /var/log/printmaster/audit.log Security Best Practices Secrets Management: Use environment variables or secret management service (Vault, AWS Secrets Manager) for tokens and keys Key Rotation: Rotate agent tokens every 90 days, TLS certificates every 90 days, signing secrets monthly Least Privilege: Agents only access devices in their network segment; users only access authorized devices Defense in Depth: Multiple overlapping security controls; assume any single layer can fail Monitoring: Alert on auth failures, unusual traffic patterns, rate limit hits, session anomalies Incident Response: Document breach response plan; include agent revocation, credential rotation, forensic logging Threat Mitigation Summary Threat Mitigated By Phase Unauthorized agent access Bearer token auth, mTLS MVP, Production Credential interception TLS 1.3, encrypted creds MVP, Production MITM attacks TLS 1.3, cert pinning, mTLS MVP, Production Session hijacking Expiring tokens, session isolation Production Replay attacks Request signing, timestamps Production DoS/abuse Rate limiting, IP whitelist MVP, Production Insider threat Audit logging, least privilege MVP Data breach Credential encryption, zero-trust Production, Enterprise Future Considerations Multi-tenancy: Isolate agents and devices by organization/tenant Role-Based Access: Fine-grained permissions (view-only, manage, admin) SSO Integration: SAML/OIDC for user authentication Compliance: GDPR, CCPA data handling for printer logs and credentials Hardware Security: TPM-backed key storage for agent certificates Last Updated: November 1, 2025\n","title":"Security Architecture for Agent-Server Communication","url":"/development/security-architecture/"},{"section":"project","text":"Supported Versions We release security patches for the following versions:\nComponent Version Supported Agent latest :white_check_mark: Server latest :white_check_mark: We recommend always running the latest version for the best security.\nReporting a Vulnerability Please do not report security vulnerabilities through public GitHub issues.\nInstead, please report them privately via one of these methods:\nOption 1: GitHub Security Advisories (Preferred) Go to the Security tab Click “Report a vulnerability” Fill out the form with details Option 2: Email Send details to the repository owner via GitHub (check profile for contact info).\nWhat to Include Please include as much of the following information as possible:\nType of vulnerability (e.g., SQL injection, XSS, authentication bypass) Affected component (Agent, Server, Web UI, API) Version(s) affected Step-by-step instructions to reproduce Proof-of-concept or exploit code (if available) Impact assessment Any suggested fixes Response Timeline Initial Response: Within 72 hours Status Update: Within 7 days Fix Timeline: Depends on severity Critical: 1-7 days High: 7-14 days Medium: 14-30 days Low: Next regular release Security Best Practices When deploying PrintMaster:\nNetwork Security Run agents on isolated management VLANs when possible Use firewall rules to restrict agent-server communication Don’t expose the server directly to the internet without a reverse proxy Authentication Change the default admin password immediately Use strong, unique passwords Enable TLS for agent-server communication in production Server Configuration # Recommended: Enable TLS [server] tls_cert = \"/path/to/cert.pem\" tls_key = \"/path/to/key.pem\" # Use token authentication for agents [agents] require_token = true Docker Deployment Don’t run containers as root when possible Use read-only file systems where feasible Keep images updated SNMP Security Use SNMPv2c with non-default community strings Consider SNMPv3 for sensitive environments (future feature) Restrict SNMP access at the printer level Known Security Considerations SNMP Community Strings SNMP community strings are stored in the configuration file. Protect this file with appropriate permissions:\n# Linux chmod 600 /etc/printmaster/config.toml # Or use environment variables export SNMP_COMMUNITY=\"your-community-string\" Web UI Sessions Sessions expire after inactivity Cookies are HTTP-only and secure (when using TLS) CSRF protection is enabled API Authentication Agent-to-server communication uses token authentication API endpoints require authentication Rate limiting is recommended via reverse proxy Security Updates Security updates are announced via:\nGitHub Releases (tagged with security label when applicable) Release notes in CHANGELOG Subscribe to releases to stay informed:\nClick “Watch” on the repository Select “Custom” → “Releases” Acknowledgments We appreciate responsible disclosure. Contributors who report valid security issues will be acknowledged (unless they prefer to remain anonymous).\nThank you for helping keep PrintMaster secure! 🔐\n","title":"Security Policy","url":"/project/security/"},{"section":"components","text":"Overview PrintMaster Agent can run as a system service for production deployments. This ensures the agent starts automatically on boot and runs continuously in the background.\nCommands # Install service (requires admin/root) printmaster-agent --service install # Start service printmaster-agent --service start # Stop service printmaster-agent --service stop # Uninstall service printmaster-agent --service uninstall # Run in foreground (testing) printmaster-agent --service run # Interactive mode (default) printmaster-agent Platform-Specific Details Windows Requirements: Administrator privileges\nData Directory: C:\\ProgramData\\PrintMaster\\\nInstallation:\n# Open PowerShell as Administrator cd C:\\Path\\To\\PrintMaster .\\printmaster-agent.exe --service install .\\printmaster-agent.exe --service start # Verify service is running Get-Service PrintMasterAgent # Check logs Get-Content \"C:\\ProgramData\\PrintMaster\\logs\\agent.log\" -Tail 50 Access Web UI: http://localhost:8080 (or https://localhost:8443)\nLinux Requirements: Root privileges\nData Directories:\nConfig: /etc/printmaster/ Data: /var/lib/printmaster/ Logs: /var/log/printmaster/ Installation:\n# Install service sudo ./printmaster-agent --service install # OR manually with systemd unit file: sudo cp agent/printmaster-agent.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable printmaster-agent sudo systemctl start printmaster-agent # Check status sudo systemctl status printmaster-agent # View logs sudo journalctl -u printmaster-agent -f macOS Requirements: Root privileges\nData Directory: /Library/Application Support/PrintMaster/\nInstallation:\n# Install service sudo ./printmaster-agent --service install # Start service sudo launchctl load /Library/LaunchDaemons/com.printmaster.agent.plist # Stop service sudo launchctl unload /Library/LaunchDaemons/com.printmaster.agent.plist # View logs log show --predicate 'process == \"printmaster-agent\"' --last 1h Troubleshooting Service won’t start Windows:\n# Check Windows Event Viewer eventvwr.msc # Navigate to: Applications and Services Logs \u003e PrintMasterAgent Linux:\n# Check service status sudo systemctl status printmaster-agent # View recent logs sudo journalctl -u printmaster-agent -n 100 --no-pager # Check for permission issues sudo ls -la /var/lib/printmaster /var/log/printmaster Cannot access web UI Check if service is running Verify firewall allows port 8080/8443 Check settings in agent config database Review logs for port binding errors Service fails to install Windows: Run PowerShell as Administrator Linux/macOS: Use sudo for installation Verify binary has execute permissions (chmod +x printmaster-agent) Configuration Service uses the same configuration as interactive mode:\nSettings stored in agent database (agent.db) Web UI accessible at configured ports All discovery and proxy features available Security When running as service:\nWindows: Runs as Local System (or configure specific service account) Linux: Runs as dedicated printmaster user (create with useradd -r printmaster) macOS: Runs as root (can be configured to run as specific user) For production deployments:\nUse dedicated service account with minimal privileges Configure firewall rules appropriately Enable HTTPS for web UI access Review security settings in Web UI \u003e Settings \u003e Security See Also Full Service Deployment Guide Configuration Guide Security Architecture ","title":"Service Mode - Quick Reference","url":"/components/agent/service/"},{"section":"development","text":"This document covers SNMP discovery, Printer-MIB OIDs, vendor detection, metrics collection, and meter history tracking in PrintMaster.\nOverview PrintMaster uses SNMP (Simple Network Management Protocol) to discover and monitor network printers. The implementation follows a two-phase scan approach:\nPhase 1 (Quick Probe) - Small number of SNMP GETs to quickly verify device is a printer Phase 2 (Deep Scan) - Targeted walk for confirmed printers to gather detailed metrics This approach keeps discovery fast on large networks while still capturing rich vendor-specific data.\nCore Printer-MIB OIDs Standard Detection OIDs System Information:\nsysObjectID - 1.3.6.1.2.1.1.2.0 - Vendor enterprise OID (identifies manufacturer) sysDescr - 1.3.6.1.2.1.1.1.0 - System description (fallback for vendor/model detection) sysName - 1.3.6.1.2.1.1.5.0 - Device hostname Printer-MIB (RFC 3805):\nprtGeneralSerialNumber - 1.3.6.1.2.1.43.5.1.1.17.1 - Device serial number prtMarkerLifeCount.1 - 1.3.6.1.2.1.43.10.2.1.4.1.1 - Total impressions (marker 1, typically black/mono) prtMarkerSuppliesDescr.1 - 1.3.6.1.2.1.43.11.1.1.6.1.1 - Supply description (e.g., “Black Toner”) prtMarkerSuppliesLevel.1 - 1.3.6.1.2.1.43.11.1.1.9.1.1 - Supply level/remaining prtMarkerSuppliesMaxCapacity.1 - 1.3.6.1.2.1.43.11.1.1.8.1.1 - Supply max capacity Additional Markers (for color printers):\nMarker 2: .1.3.6.1.2.1.43.10.2.1.4.1.2 - Combined color impressions Marker 3: .1.3.6.1.2.1.43.10.2.1.4.1.3 - Cyan impressions Marker 4: .1.3.6.1.2.1.43.10.2.1.4.1.4 - Magenta impressions Marker 5: .1.3.6.1.2.1.43.10.2.1.4.1.5 - Yellow impressions Marker 6: .1.3.6.1.2.1.43.10.2.1.4.1.6 - Additional vendor-specific marker Vendor Detection Vendor Enterprise OIDs Supported Vendors:\nHP: 1.3.6.1.4.1.11.* Brother: 1.3.6.1.4.1.2435.* Canon: 1.3.6.1.4.1.1602.* Lexmark: 1.3.6.1.4.1.641.* Epson: 1.3.6.1.4.1.231.* Kyocera: Detected via sysDescr containing “kyocera” Xerox: Detected via sysDescr or enterprise OID Ricoh: Detected via sysDescr or enterprise OID Unknown Manufacturers: When a device cannot be confidently identified, the agent logs to logs/unknown_mfg.log:\n2025-11-06T10:30:00Z | 10.0.0.100 | sysObjectID: 1.3.6.1.4.1.9999.x.x | sysDescr: \"Unknown Printer Model\" Use this file to identify new vendor enterprise OIDs and add them to vendor detection.\nVendor-Specific Modules PrintMaster includes vendor-specific modules for enhanced metrics:\nagent/scanner/vendor/hp.go - HP-specific OIDs and parsing agent/scanner/vendor/canon.go - Canon-specific OIDs agent/scanner/vendor/brother.go - Brother-specific OIDs agent/scanner/vendor/generic.go - Fallback for unknown vendors Each module provides:\nAdditional vendor OIDs for richer metrics Vendor-specific parsing logic Workarounds for vendor quirks Discovery Process Phase 1: Quick Probe (Fast Path) Goal: Determine if device is a printer with minimal SNMP queries\nOIDs Queried (5-10 queries):\nsysObjectID - Identify vendor sysDescr - Fallback vendor/model detection prtGeneralSerialNumber - Confirms printer (Printer-MIB support) prtMarkerLifeCount.1 - Page count (marker 1) prtMarkerSuppliesLevel.1 - Toner level (marker 1) Vendor-specific probe (if vendor detected) Decision: If device responds to Printer-MIB OIDs → Proceed to Phase 2\nPhase 2: Deep Scan (Confirmed Printers) Goal: Gather comprehensive metrics and capabilities\nWalks Performed:\nPrinter-MIB Walk (bounded, ~100-200 OIDs):\nAll markers (mono + color impressions) All supplies (toner, drum, fuser levels) Input trays (paper levels, media types) Device capabilities Vendor Enterprise Walk (if vendor detected, bounded):\nHP: Walk 1.3.6.1.4.1.11.2.3.9.4.2.* (HP MIB subtree) Canon/Brother/etc: Walk appropriate vendor subtree Capped at 200 OIDs to keep discovery fast Walk Bounds: All walks are intentionally capped (max 200 entries) to prevent network slowdowns on large deployments.\nMetrics Collection Normalized Meters PrintMaster normalizes vendor-specific counters into a standard meters map:\n{ \"total_pages\": 123456, // Lifetime total pages \"mono_pages\": 100000, // Monochrome impressions \"black\": 100000, // Alias for mono_pages \"color_pages\": 23456, // Combined color impressions \"cyan\": 8000, // Cyan marker count \"magenta\": 7800, // Magenta marker count \"yellow\": 7656, // Yellow marker count \"copier_pages\": 5000, // Copier function pages \"printer_pages\": 118456, // Printer function pages \"fax_pages\": 0, // Fax function pages \"scan_pages\": 0, // Scanner function pages \"local_pages\": 0, // Local copy pages \"banner_pages\": 0 // Banner/poster pages } Source Priority:\nExplicit page count OID (if available) Sum of marker counters (prtMarkerLifeCount) Vendor-specific counters with keyword matching Keyword Heuristics: Parser scans PDU descriptions for keywords like “copier”, “printer”, “fax”, “scan” to map to PrintAudit-like categories.\nMeter History \u0026 Time-Series Data Design Goal: Track impressions over time for:\n“Impressions today” metric Time-window deltas (1 day, 7 day, 30 day) Charts and trend analysis Implementation: Timestamped full snapshots with computed deltas\n{ \"printer_info\": { \"meters\": { \"total_pages\": 123456, ... }, \"meter_history\": [ { \"ts\": \"2025-11-01T10:00:00Z\", \"source\": \"mib_walk_10_2_106_72_20251101T100000.json\", \"meters\": { \"total_pages\": 120000, ... }, \"deltas\": { \"total_pages\": 500 } // vs previous snapshot }, { \"ts\": \"2025-11-02T10:00:00Z\", \"source\": \"refresh\", \"meters\": { \"total_pages\": 121000, ... }, \"deltas\": { \"total_pages\": 1000 } // 1000 pages in 24h } ] } } Retention Policy Default Configuration:\nHourly snapshots: Last 48 hours (high resolution) Daily snapshots: Last 365 days (one per day) Older data: Pruned automatically Configurable: Adjust retention windows via developer settings\nWhy Snapshots + Deltas? Advantages:\nResilient: Can recompute deltas if normalization changes Flexible: Lifetime counters preserved for long-term analysis Fast reads: Deltas pre-computed for common “impressions today” queries Alternative (deltas-only): Would lose lifetime counters, limiting flexibility\nDevice Storage File Structure Devices stored in logs/devices/\u003cserial\u003e.json:\n{ \"serial\": \"JPBCD12345\", \"properties\": { \"created_at\": \"2025-10-30T12:00:00Z\", \"modified_at\": \"2025-11-01T15:30:00Z\", \"last_walk\": \"logs/mib_walk_10_2_106_72_20251101T153000.json\", \"oid_count\": 832, \"missing_fields\": 1 }, \"printer_info\": { \"ip\": \"10.2.106.72\", \"manufacturer\": \"HP\", \"model\": \"LaserJet Pro M404n\", \"serial\": \"JPBCD12345\", \"hostname\": \"printer-office-1\", \"meters\": { \"total_pages\": 123456, ... } }, \"changelog\": [ { \"timestamp\": \"2025-11-01T15:30:00Z\", \"changes\": { \"total_pages\": { \"old\": 123000, \"new\": 123456 } } } ], \"reference_walk\": [...], \"reference_walk_files\": [\"mib_walk_10_2_106_72_20251030T120000.json\"] } Database Storage (Current) Since v0.3.x: Devices stored in SQLite database (devices.db)\nKey Tables:\ndevices - Device inventory (27 fields, NO page_count/toner_levels) metrics_raw - 5-minute snapshots (page counts, toner levels) metrics_hourly - 1-hour aggregates metrics_daily - 1-day aggregates metrics_monthly - 1-month aggregates See: docs/API.md for complete database schema\nSNMP Configuration Settings (Configurable via UI) Connection:\nSNMP Port: 161 (default) SNMP Community: \"public\" (default) SNMP Version: v2c (default) Timing:\nSNMP Timeout: 2000ms (default, configurable) SNMP Retries: 1 (default, configurable) SNMP Delay Between Queries: Not implemented yet Advanced:\nEnable SNMP Bulk GET: Not implemented yet SNMP Result Cache + TTL: Not implemented yet SNMPv3 Support: Not implemented yet (planned for v0.5.0) See: docs/ROADMAP.md for implementation status\nAPI Endpoints Get Device Meters Planned: GET /devices/{serial}/meters\nResponse:\n{ \"current\": { \"total_pages\": 123456, \"mono_pages\": 100000, \"color_pages\": 23456 }, \"history\": [...], \"aggregates\": { \"last_24h\": 500, \"last_7d\": 3500, \"last_30d\": 15000 } } Status: Endpoint not yet implemented (planned for v0.5.0)\nCurrent Alternative: Use GET /devices/get?serial=XXX and parse raw_data\nMerge Behavior Concurrent Merge Protection Challenge: Multiple scans/refreshes could corrupt device files\nSolution: Merge queue with per-serial locks\nAll merges for a device are serialized Concurrent scans for different devices run in parallel Prevents race conditions and corruption Merge Process Steps:\nParse PDUs into PrinterInfo (including Meters) Load existing device profile (if any) Compute changelog diff Append changelog entry Append meter_history snapshot with deltas Persist device file atomically (write to temp, rename) Database Merge (Current):\nUpdates device record in devices table Creates snapshot in metrics_raw table Updates last_seen timestamp Creates scan_history entry UI Display Current Device Card Displays:\nManufacturer, Model, Serial IP Address, Hostname Firmware version Current toner levels (from latest metrics_raw) Last seen timestamp Planned Metrics Display Will add:\nImpressions Today: Delta from last 24h Weekly Impressions: Delta from last 7 days Chart: Line chart of daily impressions (Chart.js or similar) Endpoint: Will use /devices/{serial}/meters when implemented\nTesting Notes Mock SNMP Client Recommendation: Use SNMPClient interface with mock implementation for tests\nExample:\ntype MockSNMPClient struct { responses map[string]gosnmp.SnmpPDU } func (m *MockSNMPClient) Get(oids []string) (*gosnmp.SnmpPacket, error) { // Return mock PDUs without network access } Benefits:\nTests run without network Deterministic results Faster CI/CD Can simulate vendor-specific quirks Adding New Vendor Support Steps Identify Vendor Enterprise OID:\nCheck logs/unknown_mfg.log for sysObjectID Research vendor’s SNMP MIB documentation Add to Vendor Detection:\n// agent/scanner/vendor/registry.go case strings.Contains(sysOID, \"1.3.6.1.4.1.XXXX\"): return \"VendorName\" Create Vendor Module (optional):\n// agent/scanner/vendor/vendorname.go func GetVendorNameOIDs() []string { return []string{ \"1.3.6.1.4.1.XXXX.specific.oid\", // Vendor-specific OIDs } } Test with Real Device:\nRun discovery against actual printer Verify metrics collected correctly Document any vendor quirks Add Unit Tests:\nMock SNMP responses Test parsing logic Verify normalization Deprecated Features ❌ On-Demand MIB Walk Endpoint Status: Removed\nFormer Endpoint: POST /mib_walk\nRationale:\nEncouraged unbounded walks (network performance issues) Replaced by targeted walks in discovery pipeline Migration: Use discovery flow and “Walk All” device action in UI (triggers bounded, targeted walks)\n❌ External MIB File Ingestion Status: Never implemented / explicitly avoided\nRationale: Keep agent lean, avoid tight coupling to vendor-specific files\nApproach: Self-contained probe OIDs in code, prefer Printer-MIB, minimal vendor-specific additions\nNext Steps \u0026 Improvements Planned Enhancements (v0.4-v0.6) SNMPv3 Support - Auth/priv encryption (v0.5.0) SNMP Bulk GET - Performance improvement for walks (v0.5.0) SNMP Result Cache - Reduce redundant queries (v0.5.0) Meter History API - /devices/{serial}/meters endpoint (v0.6.0) Chart UI - Impressions over time visualization (v0.6.0) More Vendor Modules - Expand Kyocera, Xerox, Ricoh support Development Best Practices Keep walks bounded - Max 200 OIDs per walk Prefer Printer-MIB - Standard OIDs over vendor-specific Mock SNMP in tests - No network in CI Log unknown vendors - Feed into vendor detection improvements Document quirks - Note vendor-specific behaviors Reference Links RFCs:\nRFC 3805 - Printer MIB v2 RFC 1213 - MIB-II (System Group) Internal Documentation:\ndocs/API.md - API endpoints and database schema docs/ROADMAP.md - Feature implementation timeline docs/vendor/ - Vendor-specific OID mappings (Epson, Kyocera, etc.) Code Locations:\nagent/scanner/ - SNMP scanner implementation agent/scanner/vendor/ - Vendor-specific modules agent/storage/ - Database and device storage Last Updated: November 6, 2025\nCurrent Version: 0.3.3\n","title":"SNMP Reference Guide","url":"/development/snmp-reference/"},{"section":"development","text":"This file captures actionable takeaways from inspecting other open-source SNMP printer discovery/management tools so we can fold the best ideas into PrintMaster without blindly copying code.\nSources Consulted Ircama/epson_print_conf (Python) — exhaustive remote-mode OIDs plus EEPROM decoding for dozens of Epson models (epson_print_conf.py). CUPS SNMP backend (backend/snmp.c) — hardened discovery logic with vendor-specific fallbacks (OpenPrinting/cups). Standard OIDs We Should Poll Everywhere These came up repeatedly in both projects and map cleanly to our existing data model:\nPurpose OID Notes Human-readable device description 1.3.6.1.2.1.25.3.2.1.3.x (hrDeviceDescr) Use as early sanity check before we walk vendor trees. Printer status 1.3.6.1.2.1.25.3.5.1.1 (hrPrinterStatus) Map to our device health enums. Serial number 1.3.6.1.2.1.43.5.1.1.17.1 (prtGeneralSerialNumber) Works on HP/Canon/Kyocera in addition to Epson. Marker supplies level 1.3.6.1.2.1.43.11.1.1.9.X Already stored, but ensure we handle negative “unknown” sentinel values. Marker life count 1.3.6.1.2.1.43.11.1.1.6.X Lets us compute remaining capacity deltas instead of only percentages. Input/output tray status 1.3.6.1.2.1.43.8.2.1.10 / 1.3.6.1.2.1.43.9.2.1.10 Feed jam telemetry. Alert text buffer 1.3.6.1.2.1.43.18.1.1.8 Provide user-facing fault text without parsing vendor payloads first. Epson Remote-Mode Command Surface Epson exposes a remote-control endpoint rooted at 1.3.6.1.4.1.1248.1.2.2.44.1.1.2.1.\u003ccmd bytes…\u003e where the two ASCII bytes identify the command and the next two bytes encode payload length. The Python client simply reuses this helper for several high-value calls we can mirror:\nCommand Encoded suffix example Data Returned Why It Matters di (device identification) .100.105.1.0.1 ST2/IEEE-1284 style key/value pairs (MFG, CMD, MDL, CLS, DES). Gives us stable make/model text even when standard Printer-MIB is sparse. st (status) .115.116.1.0.1 Binary @BDC ST2 frame containing live status, ink levels, tray state, error/warning codes, maintenance box counters, etc. One request replaces dozens of separate OIDs for Epson devices and surfaces alerts the generic MIB often hides. ia (ink actuator list) .105.97.1.0.0 Comma-separated cartridge SKUs. Lets us map installed cartridge types to color/capacity definitions. ii (ink slot detail) .105.105.2.0.1.\u003cslot\u003e Per-slot metadata (ink color ID, production date, quantity, manufacturer code). Enables high-fidelity consumable tracking with minimal SNMP chatter. ` ` (EEPROM read) .124.124.\u003clen_lo\u003e.\u003clen_hi\u003e.\u003cpayload…\u003e Implementation tips pulled from epson_print_conf.py:\nRead operations use read_key (two-byte secret per model) plus opcodes 65/190/160 before the address pair; writes use opcode 66/189/33 followed by the Caesar-shifted write_key. The helper already batches up to three OIDs per PDU via cluster_varbinds; we should copy the idea by grouping EEPROM reads so we do not stall the scanner. Several commands (st, rw, vi) return framed ASCII strings containing multiple values; building a lightweight parser (similar to their status_parser) would let us translate Epson-specific alerts into our unified telemetry stream. Epson EEPROM Windows Worth Mirroring The configuration table shows consistent address blocks we can codify in agent/scanner/vendor/epson.go once we add EEPROM support:\nWaste/maintenance counters: most EcoTank/XP devices store the first box in addresses 24/25/30, second box in 26/27/34, and flag thresholds at 46/47 (or 54/55 for newer models). Large-format or tri-box units add a third counter at 252/253/254 with threshold 255. Cleaning \u0026 usage stats: sequences such as [147,149,148] (manual/timer/power cleaning counts) and [171-168] (total print passes) recur across L-series. Rear-feed totals ([755-752]) and scan counters ([1843-1840]) exist on every ET-27xx/28xx/48xx variant. Serial/MAC blocks: legacy models keep serial ASCII in range(192,202) while Wi-Fi MACs live at range(130,136) or (newer) range(1920,1926). These ranges match what we already scrape via standard MIBs, so they make excellent fallbacks when the printer restricts host MIB access. Reset operations: raw_waste_reset dictionaries show the exact EEPROM values Epson utilities write during a maintenance reset. We should not expose writes in the agent, but understanding the pattern helps us detect when a third-party reset happened (sudden drop to zero combined with unchanged counters). Discovery and Vendor Fallbacks from CUPS The CUPS backend reinforces a few best practices we should adopt inside agent/scanner/pipeline.go:\nAlways begin with hrDeviceType (1.3.6.1.2.1.25.3.2.1.2) probes to confirm the target reports itself as Printer(3) before issuing heavier walks. After the initial response, immediately parallelize GETs for description, IEEE-1284 device ID, location, and URI (ppmPortServiceNameOrURI). This gives enough data to decide whether to keep probing or move on. Maintain a table of vendor-specific device-ID OIDs for common manufacturers: e.g., 1.3.6.1.4.1.11.2.3.9.1.1.7.0 (HP), 1.3.6.1.4.1.641.2.1.2.1.3.1 (Lexmark), 1.3.6.1.4.1.367.3.2.1.1.1.11.0 (Ricoh), 1.3.6.1.4.1.128.2.1.3.1.2.0 (Xerox). CUPS hits those opportunistically whenever it sees a response from the matching enterprise OID. If the device never returns a URI, CUPS still attempts TCP probes on 9100 (AppSocket) and 515 (LPD) before giving up. We can reuse that idea inside our liveness stage to classify “unknown but listening” devices. Action Items for PrintMaster Add a vendor plug-in for Epson remote mode: reuse the command table above behind a feature flag, deserialize ST2 payloads, and surface ink/waste metrics in the agent database. Extend the discovery stage with vendor-specific ID OIDs: add a snmpTargets slice similar to CUPS so we can learn make/model even when printers neuter the Printer-MIB tree. Batch EEPROM/SNMP reads: adopt the cluster_varbinds idea so we cap PDUs at three OIDs but still parallelize multiple PDUs; this will keep slow printers from starving an entire worker pool. Persist learned EEPROM ranges: cache which address blocks responded per device so future scans do not brute-force every model-specific range. Map Epson waste counters into our metrics: once we trust the data we can store normalized percentages for main_waste, borderless_waste, and any third maintenance box inside the metrics table with the same downsampling policy as toner levels. ","title":"SNMP Research Notes","url":"/development/snmp-research-notes/"},{"section":"components","text":"Location: agent/storage/\nThe storage module provides SQLite-based persistence for devices, metrics, configuration, and scan history. It uses a pure Go SQLite driver (modernc.org/sqlite) for cross-platform compatibility without CGO dependencies.\nArchitecture Overview storage/ ├── sqlite.go # Main SQLite store implementation ├── device.go # Device data structures ├── interface.go # Storage interface definitions ├── agent_config.go # Configuration storage ├── migrations.go # Schema migrations ├── convert.go # Data type conversions ├── paths.go # Database file path helpers └── *_test.go # Test files Core Components Device Store (sqlite.go, interface.go) Purpose: CRUD operations for printer devices with history tracking.\nInterface:\ntype DeviceStore interface { Create(ctx, *Device) error Get(ctx, serial) (*Device, error) Update(ctx, *Device) error Upsert(ctx, *Device) error Delete(ctx, serial) error List(ctx, filter) ([]*Device, error) MarkSaved(ctx, serial) error MarkDiscovered(ctx, serial) error AddScanHistory(ctx, *ScanSnapshot) error GetScanHistory(ctx, serial, limit) ([]*ScanSnapshot, error) // ... more methods } Key Features:\nUpsert: Insert or update in single operation (handles duplicate scans) Soft Delete: Devices marked visible=false instead of hard delete Field Locking: Prevent auto-update of manually-edited fields Scan History: Track device changes over time Device Structure (device.go) type Device struct { Serial string // Primary key IP string Manufacturer string Model string Hostname string Firmware string MACAddress string SubnetMask string Gateway string DNSServers []string DHCPServer string Consumables []string // Supply names (not levels) StatusMessages []string LastSeen time.Time CreatedAt time.Time FirstSeen time.Time IsSaved bool // User saved vs auto-discovered Visible bool // Soft delete flag DiscoveryMethod string AssetNumber string // User-defined asset tag Location string // Physical location Description string // Notes/UUID WebUIURL string // Device web interface LockedFields []FieldLock // Protected fields RawData map[string]interface{} // Extended data } Important Notes:\nPageCount and TonerLevels removed from Device struct (moved to metrics history) Time-series data belongs in metrics_history table, not device record Consumables stores supply names only (e.g., “Black Toner”, “Cyan Ink”) Scan History (interface.go) type ScanSnapshot struct { ID int64 Serial string CreatedAt time.Time IP string Hostname string Firmware string Consumables []string StatusMessages []string DiscoveryMethod string WalkFilename string RawData json.RawMessage // Full scan data } Use Cases:\nTrack device state changes over time (IP, hostname, firmware updates) Audit trail of device configuration changes Discovery method tracking Note: Metrics data (page counts, toner levels) are stored separately in the tiered metrics system (metrics_raw, metrics_hourly, metrics_daily, metrics_monthly tables) for efficient time-series analysis.\nAudit trail for device modifications Rollback/compare historical states Agent Configuration (agent_config.go) Purpose: Store agent settings separate from device data.\nInterface:\ntype AgentConfigStore interface { GetRanges() (string, error) SetRanges(text string) error GetRangesList() ([]string, error) SetConfigValue(key string, value interface{}) error GetConfigValue(key string, dest interface{}) error } Stored Settings:\nIP ranges for scanning SNMP community strings Discovery method toggles Performance settings (timeouts, concurrency) Integration credentials (webhooks, MQTT) Configuration Priority:\nDatabase settings (highest priority) config.json file Built-in defaults Database Schema Devices Table CREATE TABLE devices ( serial TEXT PRIMARY KEY, ip TEXT NOT NULL, manufacturer TEXT, model TEXT, hostname TEXT, firmware TEXT, mac_address TEXT, subnet_mask TEXT, gateway TEXT, dns_servers TEXT, -- JSON array dhcp_server TEXT, consumables TEXT, -- JSON array (names only) status_messages TEXT, -- JSON array last_seen DATETIME, created_at DATETIME, first_seen DATETIME, is_saved BOOLEAN DEFAULT 0, visible BOOLEAN DEFAULT 1, discovery_method TEXT, walk_filename TEXT, last_scan_id INTEGER, asset_number TEXT, location TEXT, description TEXT, web_ui_url TEXT, locked_fields TEXT, -- JSON array of FieldLock raw_data TEXT -- JSON object ); Scan History Table CREATE TABLE scan_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, serial TEXT NOT NULL, created_at DATETIME NOT NULL, ip TEXT, hostname TEXT, firmware TEXT, page_count INTEGER, toner_levels TEXT, -- JSON map consumables TEXT, -- JSON array status_messages TEXT, -- JSON array discovery_method TEXT, walk_filename TEXT, raw_data TEXT, -- Full snapshot JSON FOREIGN KEY (serial) REFERENCES devices(serial) ON DELETE CASCADE ); Metrics History Table CREATE TABLE metrics_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, serial TEXT NOT NULL, created_at DATETIME NOT NULL, page_count INTEGER, color_page_count INTEGER, metrics_json TEXT, -- All metrics as JSON FOREIGN KEY (serial) REFERENCES devices(serial) ON DELETE CASCADE ); Agent Config Table CREATE TABLE agent_config ( key TEXT PRIMARY KEY, value TEXT, -- JSON-encoded value updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); Key Operations Upsert Device device := \u0026Device{ Serial: \"JPBHM12345\", IP: \"192.168.1.100\", Manufacturer: \"HP\", Model: \"LaserJet Pro M404n\", LastSeen: time.Now(), } err := store.Upsert(ctx, device) Behavior:\nIf device exists: Updates fields, preserves is_saved status If new device: Inserts with is_saved=false, visible=true Respects field locks (doesn’t overwrite locked fields) Field Locking // Lock hostname to prevent scanner overwriting manual edits device.LockedFields = []FieldLock{ { Field: \"hostname\", Reason: \"manually_entered\", LockedAt: time.Now(), LockedBy: \"admin\", }, } store.Update(ctx, device) Locked Field Behavior:\nScanner upserts skip locked fields (preserve user values) Manual updates via API always succeed (override locks) Locks stored as JSON array in locked_fields column Filtering Devices // Get all saved devices seen in last 24 hours saved := true cutoff := time.Now().Add(-24 * time.Hour) filter := DeviceFilter{ IsSaved: \u0026saved, LastSeenAfter: \u0026cutoff, } devices, err := store.List(ctx, filter) Scan History // Record scan snapshot (device state) snapshot := \u0026ScanSnapshot{ Serial: \"JPBHM12345\", IP: \"192.168.1.100\", Hostname: \"office-printer-01\", Firmware: \"2.4.1\", CreatedAt: time.Now(), } store.AddScanHistory(ctx, snapshot) // Record metrics snapshot (separate from scan history) metrics := \u0026MetricsSnapshot{ Serial: \"JPBHM12345\", PageCount: 12543, TonerLevels: map[string]interface{}{\"Black\": 85, \"Cyan\": 60}, Timestamp: time.Now(), } store.SaveMetricsSnapshot(ctx, metrics) // Retrieve last 10 scans for device (state changes) history, err := store.GetScanHistory(ctx, \"JPBHM12345\", 10) // Retrieve metrics history (for page count analysis) since := time.Now().Add(-30 * 24 * time.Hour) until := time.Now() metricsHistory, err := store.GetTieredMetricsHistory(ctx, \"JPBHM12345\", since, until) if len(metricsHistory) \u003e= 2 { pagesUsed := metricsHistory[0].PageCount - metricsHistory[len(metricsHistory)-1].PageCount fmt.Printf(\"Printed %d pages in last 30 days\\n\", pagesUsed) } Migrations (migrations.go) Purpose: Automatic schema upgrades for existing databases.\nMigration Process:\nCheck schema version in schema_version table Run pending migrations in order Update schema version Example Migration:\n{ Version: 2, Name: \"add_asset_fields\", Up: func(db *sql.DB) error { _, err := db.Exec(` ALTER TABLE devices ADD COLUMN asset_number TEXT; ALTER TABLE devices ADD COLUMN location TEXT; ALTER TABLE devices ADD COLUMN description TEXT; `) return err }, } Current Schema Version: Check migrations.go for latest version number\nDatabase Configuration SQLite Pragmas PRAGMA foreign_keys = ON; // Enable FK constraints PRAGMA journal_mode = WAL; // Write-Ahead Logging for performance PRAGMA synchronous = NORMAL; // Balance safety/speed PRAGMA cache_size = -64000; // 64MB cache Benefits:\nWAL mode: Concurrent reads during writes Foreign keys: Referential integrity (cascade deletes) Large cache: Faster queries on repeated access Database File Locations // Platform-specific paths Windows: %APPDATA%\\printmaster\\devices.db Linux: ~/.local/share/printmaster/devices.db macOS: ~/Library/Application Support/printmaster/devices.db // Or use custom path store, _ := NewSQLiteStore(\"/path/to/custom.db\") Performance Characteristics Query Performance Operation Typical Time Notes Upsert single device 1-5ms Includes index updates Get by serial \u003c1ms Indexed primary key List all devices 5-20ms ~100 devices Add scan history 2-10ms Includes FK check Get last 10 scans 2-5ms Indexed by serial + created_at Concurrency WAL Mode: Multiple readers + single writer concurrently Thread Safety: All methods use context.Context for cancellation Connection Pooling: SQLite driver handles connection reuse Lock Behavior: Writes acquire exclusive lock briefly (sub-millisecond) Scalability Tested: 10,000+ devices with sub-second queries Bottlenecks: Full table scans without filters Optimization: Ensure filters use indexed columns (serial, ip, is_saved) Testing Test Files:\nsqlite_test.go: Device CRUD operations (20+ tests) scan_history_test.go: Scan history tracking paths_test.go: Cross-platform path resolution Run Tests:\ncd agent/storage go test -v Test Coverage:\ngo test -cover Error Handling Standard Errors var ( ErrNotFound = errors.New(\"device not found\") ErrDuplicate = errors.New(\"device already exists\") ErrInvalidSerial = errors.New(\"invalid or empty serial\") ) Usage:\ndevice, err := store.Get(ctx, serial) if errors.Is(err, storage.ErrNotFound) { // Handle missing device } Database Errors Constraint violations: Wrapped with context (e.g., “UNIQUE constraint failed”) Connection errors: Transient, retry recommended Schema errors: Fatal, requires migration or reset Integration Points With Scanner (agent/scanner/) Scanner calls storage after device detection:\npi := scanner.QueryDevice(ctx, ip, \"public\", 5) device := \u0026storage.Device{ Serial: pi.Serial, IP: pi.IP, Manufacturer: pi.Vendor, Model: pi.Model, // ... map fields ... } store.Upsert(ctx, device) With Agent (agent/agent/) Agent discovery updates last seen times:\n// After mDNS/SSDP/WS-Discovery discovery store.Upsert(ctx, \u0026Device{ Serial: discoveredSerial, IP: discoveredIP, DiscoveryMethod: \"mdns\", LastSeen: time.Now(), }) With Main Application (main.go) HTTP API handlers use storage for CRUD:\n// GET /api/devices devices, _ := store.List(ctx, DeviceFilter{}) // DELETE /api/devices/{serial} store.Delete(ctx, serial) // POST /api/devices/{serial}/save store.MarkSaved(ctx, serial) Future Enhancements Planned Features Backup/restore functionality Export to CSV/JSON Device groups/tags Custom fields (user-defined metadata) Audit logging (who changed what when) Device relationships (parent/child for managed print servers) Alerting thresholds (stored per-device) Performance Improvements Batch upsert for bulk imports Read replicas for reporting queries Query result caching (Redis/in-memory) Archival of old scan history (compress/move to cold storage) Troubleshooting Database Locked Symptom: database is locked error during writes\nCauses:\nLong-running transaction blocking writes WAL mode not enabled (check pragmas) External process accessing database Solutions:\nEnsure WAL mode: PRAGMA journal_mode = WAL; Use shorter transactions Close all external DB connections (DB Browser, etc.) Missing Devices After Scan Symptom: Scanner finds devices but they don’t appear in UI\nChecks:\nCheck visible=true filter in query Verify Upsert succeeded (check logs) Look for constraint violations (duplicate serial with different IP) Check is_saved filter (may be showing only saved devices) Slow Queries Symptom: List operations taking \u003e100ms\nDiagnostics:\nEXPLAIN QUERY PLAN SELECT * FROM devices WHERE manufacturer = 'HP'; Solutions:\nAdd indexes on frequently-filtered columns Use specific filters (avoid full table scans) Increase cache size: PRAGMA cache_size = -128000; (128MB) Schema Version Mismatch Symptom: App crashes on startup with schema errors\nCause: Database from older version, migration failed\nRecovery:\nBackup existing database: copy devices.db devices.db.bak Delete database (will recreate with current schema) Re-scan network to repopulate Or run migrations manually (see migrations.go) Related Documentation Scanner Module - Generates device data to store Agent Module - Discovery triggers storage updates API Reference - HTTP endpoints using storage Configuration - Database path configuration ","title":"Storage Module Documentation","url":"/components/agent/storage/"},{"section":"development","text":"This document describes how to write fast, deterministic tests for the PrintMaster agent and server.\nTest Types PrintMaster has three levels of testing:\nUnit Tests - Test individual functions/packages in isolation (agent/, server/) Integration Tests - Test component interactions (e.g., database rotation) End-to-End Tests - Test full agent-server communication (see tests/ directory) For E2E test details, see ../tests/E2E_TESTING.md.\nWhy mocking is necessary Network operations (ICMP, TCP connect, SNMP Get/Walk) are slow and flaky in CI. Unit tests must avoid touching the real network so they remain fast and reliable. The project uses small package-level factories and interfaces to make mocking easy.\nKey patterns SNMPClient interface: production code uses NewSNMPClient (returns a SNMPClient) which wraps gosnmp. Tests replace NewSNMPClient with a mock or fake implementation. DoPing: package-level variable that points to the real pingWithExec by default. Tests override DoPing to return deterministic ping results. Use short context timeouts in tests (1–5s) to guard against runaway scans. Avoid calling DiscoverPrinters() in unit tests; prefer ScanRangesWithWorkers or DiscoverPrintersInRanges with injected mocks. Example test pattern oldNew := agent.NewSNMPClient agent.NewSNMPClient = func(cfg *agent.SNMPConfig, target string, timeout int) (agent.SNMPClient, error) { return \u0026agenttest.MockSNMP{ /* preset varbinds */ }, nil } defer func(){ agent.NewSNMPClient = oldNew }() oldPing := agent.DoPing agent.DoPing = func(ip string, logFn func(string)) bool { return true } defer func(){ agent.DoPing = oldPing }() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() printers, err := agent.ScanRangesWithWorkers(ctx, nil, []string{\"127.0.0.1\"}, 1, nil, 1) // assert on printers and behavior Mocking tips Keep mocks simple and stateless where possible. To simulate timeouts or retries, have the mock delay the response or return an error on the first N calls. For concurrent tests, ensure the mock implementations are goroutine-safe (use channels or mutexes if necessary). Integration testing For full-network tests that exercise the real SNMP stack, maintain separate manual integration scripts (not executed by default in CI). Put those in scripts/integration/ and document the required environment and credentials.\nEnd-to-End Testing E2E tests that verify agent-server communication are located in the tests/ directory at the project root. These tests:\nStart actual server and agent instances Test full workflows (registration, heartbeat, WebSocket, proxy) Run in CI/CD after unit tests pass Use ephemeral ports and temp databases See ../tests/E2E_TESTING.md for complete documentation.\nRunning E2E tests:\ncd tests go test -v ./... Skipping E2E tests (fast unit tests only):\ngo test -short ./... ","title":"Testing and CI guidelines","url":"/development/testing-ci/"},{"section":"guides","text":"Solutions for common PrintMaster issues.\nTable of Contents Discovery Issues Connection Issues Web UI Issues Service Issues Database Issues Performance Issues Logs \u0026 Diagnostics Discovery Issues No Printers Found Symptoms: Scan completes but no devices appear.\nSolutions:\nVerify network connectivity\n# Can you reach the printer? ping 192.168.1.100 Check SNMP is enabled on the printer\nAccess the printer’s web interface Look for SNMP settings in Network or Security Ensure SNMP v1/v2c is enabled Verify the SNMP community string\n# Test with snmpwalk (if available) snmpwalk -v2c -c public 192.168.1.100 sysDescr Default is public, but some printers use private or a custom string Update in Settings → SNMP Community Check firewall rules\nSNMP uses UDP port 161 Ensure outbound UDP 161 is allowed from the agent Check the IP range configuration\nVerify the correct subnet is configured Try scanning a single known-good IP first Some Printers Missing Symptoms: Some printers found, others not.\nSolutions:\nDifferent SNMP community strings\nSome printers may use a different community string Try scanning those IPs individually with the correct string SNMP timeout too short\nIncrease timeout: Settings → SNMP Timeout → 3000ms or higher Printer SNMP disabled or restricted\nCheck the printer’s SNMP access list Some printers only respond to specific IP addresses Network segmentation\nVerify the agent can reach all subnets May need agents in multiple VLANs Incomplete Device Information Symptoms: Devices found but missing model, serial, or counters.\nSolutions:\nIncrease SNMP timeout and retries\n[snmp] timeout_ms = 3000 retries = 2 Check vendor support\nSome older or budget printers have limited SNMP Check the logs for specific OID errors Run a manual deep scan\nGo to Devices → select device → Rescan Connection Issues Agent Not Connecting to Server Symptoms: Agent shows “Disconnected” in server dashboard.\nSolutions:\nVerify server URL format\n[server] url = \"http://server-ip:9090\" # Include protocol and port! Test network connectivity\n# From agent machine curl http://server-ip:9090/api/v1/health Check firewall\nServer port (default 9090) must be accessible Both TCP HTTP and WebSocket connections needed Verify server is running\ndocker ps | grep printmaster # or systemctl status printmaster-server Check agent logs\nLook for connection errors See Logs \u0026 Diagnostics WebSocket Connection Failing Symptoms: “WebSocket error” messages, real-time updates not working.\nSolutions:\nAgent will auto-fallback to HTTP\nThis is normal behavior, not an error Real-time updates will be slightly delayed Check proxy configuration\nReverse proxies must support WebSocket passthrough For Nginx: location / { proxy_pass http://printmaster:9090; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; } Check for WebSocket blocking\nSome corporate firewalls block WebSocket Test with direct connection (bypassing proxy) Agent Keeps Reconnecting Symptoms: Agent status flapping between connected/disconnected.\nSolutions:\nNetwork instability\nCheck network path between agent and server Monitor for packet loss or high latency Server resource issues\nCheck server CPU/memory usage May need to scale up server resources Increase heartbeat interval\n[server] heartbeat_interval_seconds = 120 Web UI Issues Cannot Access Web UI Symptoms: Browser shows connection refused or timeout.\nSolutions:\nVerify service is running\n# Windows Get-Service PrintMasterAgent # Linux systemctl status printmaster-agent Check the correct port\nDefault: Agent = 8080, Server = 9090 May be configured differently Check bind address\nDefault binds to all interfaces May be restricted to localhost only Check firewall\nEnsure the port is open Try localhost\nAccess from the local machine first http://localhost:8080 UI Loading Slowly Solutions:\nCheck network latency\nHigh latency = slow UI Check device count\nLarge device lists may load slowly Use pagination or filters Clear browser cache\nOld cached assets may cause issues Login Issues Symptoms: Cannot log in, or session keeps expiring.\nSolutions:\nVerify credentials\nDefault server: admin / printmaster (or your set password) Check for cookie issues\nClear browser cookies Ensure cookies are enabled Reset password (server)\nStop the server Delete the database (⚠️ loses all data) Restart with new ADMIN_PASSWORD Service Issues Service Won’t Start (Windows) Solutions:\nCheck Event Viewer\nLook in Application log for PrintMaster errors Run interactively to see errors\n.\\printmaster-agent.exe Check port conflicts\nnetstat -ano | findstr :8080 Reinstall service\n.\\printmaster-agent.exe --service uninstall .\\printmaster-agent.exe --service install .\\printmaster-agent.exe --service start Service Won’t Start (Linux) Solutions:\nCheck systemd logs\njournalctl -u printmaster-agent -f Check permissions\nService needs read/write to data directory Check file ownership Check SELinux/AppArmor\nMay be blocking network or file access ausearch -m avc -ts recent Service Stops Unexpectedly Solutions:\nCheck logs for crash information\nCheck resource usage\nOut of memory may cause crashes Check disk space for database Update to latest version\nMay be a known bug that’s been fixed Database Issues Database Locked Errors Symptoms: “database is locked” errors in logs.\nSolutions:\nCheck for multiple instances\nOnly one process should access the database Kill duplicate processes Check disk space\ndf -h /var/lib/printmaster Check disk I/O\nHigh I/O latency can cause locking issues Database Corruption Symptoms: Startup errors mentioning database, or missing data.\nSolutions:\nStop the service\nCreate a backup of the database\ncp printmaster.db printmaster.db.backup Try recovery\nsqlite3 printmaster.db \"PRAGMA integrity_check;\" If corrupt, restore from backup or recreate\nDelete the database file Restart the service (creates new database) Rediscover devices Performance Issues Slow Discovery Scans Solutions:\nReduce concurrent scans (if network constrained)\ndiscovery_concurrency = 25 Increase concurrent scans (if CPU constrained)\ndiscovery_concurrency = 100 Reduce IP range scope\nScan only subnets with printers Avoid scanning entire /16 networks Increase timeout if many devices offline\nScanning dead IPs wastes time waiting for timeout High Memory Usage Solutions:\nCheck device count\nNormal: ~1MB per 100 devices Restart service to clear memory\nCheck for memory leaks\nReport persistent memory growth as a bug High CPU Usage Solutions:\nDuring scans is normal\nCPU usage spikes during discovery Check scan frequency\nToo frequent = constant high CPU Hourly scans usually sufficient Check log level\nDebug logging increases CPU usage Set to info for production Logs \u0026 Diagnostics Log Locations Platform Agent Logs Server Logs Windows Event Viewer → Application Event Viewer → Application Linux journalctl -u printmaster-agent journalctl -u printmaster-server Docker docker logs printmaster-server docker logs printmaster-agent Enabling Debug Logging Via config file:\n[logging] level = \"debug\" Via environment variable:\nexport AGENT_LOG_LEVEL=debug Via command line:\n./printmaster-agent -log-level debug Collecting Diagnostics When reporting an issue, include:\nVersion information\n./printmaster-agent -version Configuration (redact sensitive values)\nRelevant log entries\nSteps to reproduce\nExpected vs actual behavior\nHealth Check Endpoints Agent:\ncurl http://localhost:8080/api/v1/health Server:\ncurl http://localhost:9090/api/v1/health These return JSON with status information useful for diagnostics.\nGetting Help If you can’t resolve an issue:\nSearch existing issues: GitHub Issues\nAsk the community: GitHub Discussions\nReport a bug: Create a new GitHub issue with diagnostics\n","title":"Troubleshooting Guide","url":"/guides/troubleshooting/"},{"section":"deployment","text":"Deploy PrintMaster Server on Unraid using the Docker container.\nInstallation Methods Method 1: Community Applications (Recommended) Install Community Applications plugin if not already installed Search for “PrintMaster Server” Click Install Configure settings (see below) Click Apply Method 2: Manual Docker Setup Go to Docker tab in Unraid Click Add Container Configure: Setting Value Name PrintMaster-Server Repository ghcr.io/printmaster-org/printmaster-server:latest Network Type Bridge Port 9090 → 9090 (TCP) Volume /mnt/user/appdata/printmaster-server/data → /var/lib/printmaster/server Volume /mnt/user/appdata/printmaster-server/logs → /var/log/printmaster/server Configuration Essential Settings Setting Value Description HTTP Port 9090 Web interface and API Data Directory /mnt/user/appdata/printmaster-server/data Database storage Logs Directory /mnt/user/appdata/printmaster-server/logs Application logs Timezone Your timezone (e.g., America/New_York) For correct timestamps Environment Variables Variable Value Description BIND_ADDRESS 0.0.0.0 Allow external access BEHIND_PROXY true or false Behind Nginx Proxy Manager? LOG_LEVEL info debug, info, warn, error TZ America/New_York Your timezone PM_DISABLE_SELFUPDATE true Recommended for Docker Reverse Proxy Setup With Nginx Proxy Manager Set container variables:\nBEHIND_PROXY=true PROXY_USE_HTTPS=true In Nginx Proxy Manager:\nScheme: http (not https) Forward Hostname: printmaster-server or Unraid IP Forward Port: 9090 Enable WebSockets: ✅ Required! Configure SSL certificate Without Reverse Proxy (Direct HTTPS) Variable Value BEHIND_PROXY false TLS_MODE self-signed SERVER_HTTPS_PORT 9443 Map port 9443 in addition to 9090.\nFile Locations On Unraid Host /mnt/user/appdata/printmaster-server/ ├── data/ │ ├── server.db # SQLite database │ └── config.toml # Configuration (optional) └── logs/ └── server.log # Application logs Inside Container /var/lib/printmaster/server/ # Data /var/log/printmaster/server/ # Logs First-Time Setup Start the container\nDatabase and config created automatically Access the Web UI\nDirect: http://YOUR-UNRAID-IP:9090 Via Proxy: https://printmaster.yourdomain.com Login\nUsername: admin Password: printmaster (change immediately!) Connect Agents\nAgent config: server_url = \"http://YOUR-UNRAID-IP:9090\" Updating Via Unraid UI Go to Docker tab Click Check for Updates Click Update if available Via Community Applications Use CA Auto Update Applications plugin for automatic updates.\nBackup What to Backup /mnt/user/appdata/printmaster-server/data/server.db /mnt/user/appdata/printmaster-server/data/config.toml (if customized) Using Appdata Backup Plugin Includes /mnt/user/appdata/printmaster-server/ automatically Manual Backup # Stop container first docker stop PrintMaster-Server # Backup cp /mnt/user/appdata/printmaster-server/data/server.db \\ /mnt/user/backups/printmaster-$(date +%Y%m%d).db # Restart docker start PrintMaster-Server Troubleshooting Container Won’t Start Check logs: Docker tab → Click container → Logs Verify appdata folder permissions Ensure port 9090 isn’t in use Can’t Access Web UI # From Unraid terminal curl http://localhost:9090/api/v1/health Permission Errors # Fix permissions chown -R 99:100 /mnt/user/appdata/printmaster-server/ chmod -R 755 /mnt/user/appdata/printmaster-server/ Container uses UID 99, matching Unraid’s nobody user.\n502 Bad Gateway (Nginx Proxy Manager) Ensure BEHIND_PROXY=true Use http:// in NPM (not https) Enable WebSockets in proxy settings View Logs docker logs -f PrintMaster-Server Integration with Other Apps Uptime Kuma Monitor PrintMaster availability:\nURL: http://printmaster-server:9090/api/v1/health Nginx Proxy Manager Install from Community Applications Create proxy host for PrintMaster Use Let’s Encrypt for SSL Complete Example Container Name: PrintMaster-Server Repository: ghcr.io/printmaster-org/printmaster-server:latest Network Type: bridge Ports: 9090/tcp → 9090 Volumes: /mnt/user/appdata/printmaster-server/data → /var/lib/printmaster/server /mnt/user/appdata/printmaster-server/logs → /var/log/printmaster/server Environment Variables: BIND_ADDRESS=0.0.0.0 BEHIND_PROXY=true LOG_LEVEL=info TZ=America/Chicago PM_DISABLE_SELFUPDATE=true Works with Nginx Proxy Manager at https://printmaster.mydomain.com.\nSee Also Docker Deployment Configuration Guide Installation Guide ","title":"Unraid Deployment","url":"/deployment/unraid/"},{"section":"development","text":"Status: ✅ Implemented (Windows only)\nSummary USB printer support is implemented as an IPP-USB HTTP proxy, not the SNMP-over-USB approach originally planned (see “Abandoned approach” below). The agent enumerates USB printers that expose an embedded web UI over the IPP-USB interface class, then tunnels HTTP requests to that web UI over the USB bulk endpoints — the same approach used by OpenPrinting/ipp-usb.\nMetrics (page counts, toner levels, etc.) are obtained by scraping the printer’s embedded web UI/XML endpoints, not by querying SNMP OIDs over USB.\nPlatform support: Windows only today. Non-Windows builds compile a no-op stub (usbproxy_handlers_other.go, usbproxy_support_other.go) so USB support always reports \"supported\": false on Linux/macOS.\nArchitecture ┌─────────────────────────────────────────────────────────────┐ │ PrintMaster Agent (Windows) │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ usbproxy.Manager (agent/usbproxy/manager.go) │ │ │ │ • Scans USB (WinUSB) devices every ScanInterval │ │ │ │ • Filters interfaces to IPP-USB / printer class │ │ │ │ • Matches devices to Windows spooler port names │ │ │ │ • Opens per-device proxy sessions on demand │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ USBTransport (http.RoundTripper over WinUSB bulk I/O) │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ metrics.Collector (agent/usbproxy/metrics/) │ │ │ │ • Vendor-specific scrapers (HP, Epson, generic, ...) │ │ │ │ • Probes known XML/HTML endpoints, parses page counts │ │ │ │ and supply levels │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ │ ▼ USB Printer (embedded web UI) Key files Component Location Proxy manager (scan, sessions, HTTP round-tripping) agent/usbproxy/manager.go Shared types (USBPrinter, USBTransport, Config) agent/usbproxy/types.go Windows enumeration (WinUSB, SetupDi APIs) agent/usbproxy/usb_windows.go Non-Windows stub (IsSupported() == false) agent/usbproxy/usb_other.go Metrics scraping / vendor registry agent/usbproxy/metrics/registry.go HTTP API handlers (Windows) agent/usbproxy_handlers.go HTTP API no-op stubs (non-Windows) agent/usbproxy_handlers_other.go Startup wiring InitUSBProxy(...) call in agent/main.go HTTP API GET /api/usb-printers – list discovered USB printers with IPP-USB capability POST /api/usb-printers/scan – trigger an immediate rescan GET /api/usb-printers/status – proxy manager status + active session count GET /api/usb-printers/metrics/{serial} – scrape metrics from a specific printer GET /api/usb-printers/probe/{serial} – probe all known endpoints for debugging Data model Discovered USB printers are not merged into the main devices table the way network-discovered printers are. They’re tracked separately in-memory by the usbproxy.Manager and surfaced through the API above; metrics are fetched on demand rather than polled into the time-series metrics tables.\nAbandoned approach: pure-Go SNMP-over-USB (gousbsnmp) An earlier plan proposed a pure-Go library (gousbsnmp) implementing IEEE 1284.4 SNMP-over-USB framing, so USB printers could be queried with the same SNMP OIDs used for network printers. That library was never built out — it was a dead end and is not part of the codebase. Do not resurrect github.com/mstrhakr/gousbsnmp or IEEE 1284.4 framing references from old docs/notes; the IPP-USB proxy approach above is the current and only supported implementation.\n","title":"USB Printer Support","url":"/development/usb-implementation/"},{"section":"development","text":"Date: November 3, 2025\nSource: logs_20251103_064511.zip analysis\nPurpose: Identify meter detection gaps and opportunities for vendor-specific OID improvements\nExecutive Summary Analysis of 10 production devices revealed:\n✅ All devices successfully report page counts during discovery (standard Printer-MIB) ❌ Metrics collection fails with zeros despite OIDs being correct 🎯 Root cause: SNMP GET queries fail where WALK succeeds 💡 Solution: Learned OID system (already implemented) will fix this 🔍 Opportunity: Vendor-specific enterprise OIDs offer additional counters Device Breakdown 1. Epson AM-C550 Series (172.52.105.91) ✅ VALIDATED Current Status: ❌ Metrics returning 0 despite discovery finding 81,563 pages\nVendor Module: Generic (no Epson module exists)\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 81,563 ✅ Validation Date: 2025-11-03\nWhat We’re Finding ✅ Page count: 81,563 - EXACT MATCH with device web interface! Model: “AM-C550 Series” Serial: Working What We SHOULD Be Finding (from device web interface) Printing Counters:\n✅ Total pages: 81,563 (EXACT MATCH!) ❌ B\u0026W pages: 39,797 (not currently captured) ❌ Color pages: 41,766 (not currently captured) ❌ Duplex pages: 45,836 (not currently captured) ❌ Simplex pages: 35,727 (not currently captured) Function-Specific Counters:\n❌ B\u0026W Copy: 30,161 (not currently captured) ❌ Color Copy: 2,313 (not currently captured) ❌ B\u0026W Scan: 2,309 (not currently captured) ❌ Color Scan: 1,101 (not currently captured) ❌ B\u0026W Print (Computer): 9,619 (not currently captured) ❌ Color Print (Computer): 39,434 (not currently captured) ❌ B\u0026W Print (Other): 17 (not currently captured) ❌ Color Print (Other): 19 (not currently captured) Print Language Breakdown:\nESC/P-R: 2 PCL: 319 PostScript/PDF: 41,587 ESC/Page: 7,159 Other: 32,496 Fax Counters:\nB\u0026W Send: 0, Color Send: 0 B\u0026W Receive: 0, Color Receive: 0 Improvement Opportunities Create Epson vendor module (agent/scanner/vendor/epson.go) Research Epson enterprise OID tree 1.3.6.1.4.1.1248.* for: B\u0026W vs Color page separation Copy/Scan/Print function counters Duplex vs Simplex counters Print language counters Map discovered OIDs to these specific metrics Add MFP-specific scan/copy functionality Key Insight: Standard Printer-MIB total page count is 100% accurate. Problem is SNMP GET reliability, not OID accuracy. Epson vendor module would provide rich additional metrics beyond basic page count.\n2. Epson WF-C17590 Series (172.52.105.93) Current Status: Discovery shows 422,294 pages\nVendor Module: Generic (no Epson module exists)\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 422,294 ✅\nWhat We’re Finding Page count: 422,294 (standard Printer-MIB) Enterprise OIDs discovered: 1.3.6.1.4.1.1248.1.2.2.6.* tree contains extensive counters Example: 1.3.6.1.4.1.1248.1.2.2.6.1.1.4.1.2 shows job counters What We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Mono pages: ? Color pages: ? Ink levels (if inkjet): ? Scan/Copy/Fax counters: ? Improvement Opportunities Create Epson vendor module (agent/scanner/vendor/epson.go) Add enterprise OID tree 1.3.6.1.4.1.1248.1.2.2.6.* to GetMetricsOIDs() Parse job counters and ink levels from enterprise OIDs Many Epson devices are inkjet - need ink cartridge level support Epson Enterprise OIDs Found in Logs:\n1.3.6.1.4.1.1248.1.2.2.6.1.1.4.1.2 = 81370 (job counter?) 1.3.6.1.4.1.1248.1.2.2.6.2.1.4.1.5 = 80809 (job counter?) 1.3.6.1.4.1.1248.1.2.2.6.*.*.*.*.* = 200+ additional counters 3. Epson WF-C20600 Series (172.52.105.94) Current Status: Discovery shows 360,841 pages\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 360,841 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Mono pages: ? Color pages: ? Ink levels: ? 4. Kyocera TASKalfa 6052ci (172.52.105.95) ✅ VALIDATED Current Status: Discovery shows 432,951 pages\nVendor Module: Kyocera (implemented!)\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 432,951 ✅ Validation Date: 2025-11-03\nWhat We’re Finding ✅ Total printed: 424,405 - Kyocera enterprise OID 1.3.6.1.4.1.1347.43.10.1.1.12.1.1 ✅ Printer Total: 304,339 (B\u0026W 163,915 + Color 140,424) ✅ Copy Total: 116,254 (B\u0026W 73,811 + Color 42,443) ✅ Fax Total: 3,812 (B\u0026W only) ✅ Scan counters: Copy scans 42,138, Fax scans 3,057, Other scans 43,441 What We SHOULD Be Finding (from device web interface) Printing Counters: ✅ ALL EXACT MATCHES!\n✅ Total pages: 424,405 (EXACT MATCH!) ✅ B\u0026W pages: 241,538 (163,915 + 73,811 + 3,812 = EXACT MATCH!) ✅ Color pages: 182,867 (140,424 + 42,443 = EXACT MATCH!) Function-Specific Counters: ✅ ALL EXACT MATCHES!\n✅ Printer B\u0026W: 163,915 (EXACT MATCH!) ✅ Printer Color: 140,424 (EXACT MATCH!) ✅ Copy B\u0026W: 73,811 (EXACT MATCH!) ✅ Copy Color: 42,443 (EXACT MATCH!) ✅ Fax B\u0026W: 3,812 (EXACT MATCH!) Scan Counters: ✅ ALL EXACT MATCHES!\n✅ Copy Scans: 42,138 (EXACT MATCH!) ✅ Fax Scans: 3,057 (EXACT MATCH!) ✅ Other Scans: 43,441 (EXACT MATCH!) Key Insight: Kyocera provides the most comprehensive metrics of any vendor with complete function and color breakdowns. All values are direct OIDs - no calculation needed!\n5. Epson WF-M5799 Series (172.52.105.97) Current Status: Discovery shows 49,790 pages\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 49,790 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Mono pages: ? (likely mono-only device) Ink/Toner levels: ? 6. Epson CW-C6000Au (172.52.105.107) Current Status: Discovery shows 72,701 pages\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 72,701 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Label/roll printer specifics: ? Ink levels: ? 7. Kyocera ECOSYS PA4000wx (172.52.105.114) Current Status: Discovery shows 15 pages (new device)\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 15 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Mono pages: ? (likely mono-only) Toner level: ? 8. Epson CW-C6500Au (172.52.105.153) Current Status: Discovery shows 22 pages (new device)\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 22 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Label/roll printer specifics: ? Ink levels: ? 9. Epson ST-M3000 Series (172.52.105.162) Current Status: Discovery shows 17,884 pages\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 17,884 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Sublimation printer specifics: ? 10. Epson ST-M1000 Series (172.52.105.196) Current Status: Discovery shows 8,344 pages\nVendor Module: Generic\nDiscovery OID: 1.3.6.1.2.1.43.10.2.1.4.1.1 = 8,344 ✅\nWhat We Should Be Finding Please provide expected meters for this device:\nTotal pages: ? Sublimation printer specifics: ? Current Vendor Module Coverage ✅ Implemented Modules HP (hp.go)\nStandard Printer-MIB markers HP enterprise OIDs: 1.3.6.1.4.1.11.2.3.9.4.2.1.* Fax pages, duplex sheets, jam events, scan counters Canon (canon.go)\nVendor-specific OIDs for Canon devices Brother (brother.go)\nVendor-specific OIDs for Brother devices Epson (epson.go) ✨\nStandard Printer-MIB markers Epson enterprise OIDs: 1.3.6.1.4.1.1248.1.2.2.27.* B\u0026W vs Color page separation Print from computer counters Copy counters (total and color, B\u0026W calculated) Benefits 6 out of 10 devices in production (60% coverage) Validated on AM-C550 and WF-C17590 models Kyocera (kyocera.go) ✨ NEW\nStandard Printer-MIB markers Kyocera enterprise OIDs: 1.3.6.1.4.1.1347.42.3.* and 1.3.6.1.4.1.1347.43.10.* Complete function breakdown (Print/Copy/Fax) B\u0026W vs Color page separation for ALL functions Scan counters (Copy/Fax/Other scans) Most comprehensive metrics of any vendor - all values are direct OIDs Benefits 2 out of 10 devices in production (20% coverage) Validated on TASKalfa 6052ci with 100% exact matches Total coverage with Epson: 8 out of 10 devices (80%) Generic (generic.go)\nFallback for all other vendors Standard Printer-MIB OIDs only Currently used by: unknown vendors ❌ Missing Modules (Opportunity) No critical missing modules - current coverage is 80% of production environment (Epson 60% + Kyocera 20%)\nTechnical Findings Root Cause: SNMP GET vs WALK Reliability Discovery Phase (working):\nUses SNMP WALK operation Returns thousands of PDUs Successfully extracts all page counts Metrics Phase (failing):\nUses SNMP GET with targeted OID list Only returns 4 PDUs when it should return more Same OIDs that work in WALK fail in GET Example from logs:\nDiscovery: WALK finds .1.3.6.1.2.1.43.10.2.1.4.1.1 = 81,563 pages ✅ Metrics: GET for .1.3.6.1.2.1.43.10.2.1.4.1.1 returns nothing ❌ Solution Already Implemented The Learned OID System caches exact OIDs that worked during discovery:\nDuring WALK, track which OIDs returned data Store learned OIDs in database During metrics, query learned OIDs directly Fall back to vendor defaults if learned OIDs unavailable Implementation Status: ✅ Complete and tested\nRecommended Actions ✅ COMPLETED: Epson Vendor Module 📦 Status: Implemented and tested\nCoverage: 6/10 devices (60% of environment)\nWhat Was Implemented:\n✅ Created agent/scanner/vendor/epson.go with VendorModule interface ✅ Added enterprise OIDs from 1.3.6.1.4.1.1248.1.2.2.27.* tree ✅ Registered in registry.go with enterprise number 1248 ✅ Validated against two production devices (AM-C550, WF-C17590) ✅ All tests passing Metrics Now Available:\n✅ Total page count (standard + Epson enterprise) ✅ B\u0026W vs Color page separation ✅ Total Print from Computer counters ✅ Total Copy counters ✅ Color Print from Computer (direct) ✅ Color Copy (direct) ✅ B\u0026W Print from Computer (calculated: Total Print - Color Print) ✅ B\u0026W Copy (calculated: Total Copy - Color Copy) Priority 1: Deploy Updated Agent 🚀 Build and deploy agent with Epson vendor module + learned OID system Monitor for “LEARNED_OIDS” log entries during discovery Verify “Using learned OIDs for metrics collection” during metrics queries Verify Epson devices now return enhanced metrics Expected impact: Fixes metrics=0 problem for 100% of devices (learned OIDs) Provides enhanced B\u0026W/Color breakdown for 60% of devices (Epson module) ✅ COMPLETED: Kyocera Vendor Module 📦 Status: Implemented and tested\nCoverage: 2/10 devices (20% of environment)\nTotal coverage with Epson: 8/10 devices (80% of environment)\nWhat Was Implemented:\n✅ Created agent/scanner/vendor/kyocera.go with VendorModule interface ✅ Added enterprise OIDs from 1.3.6.1.4.1.1347.42.3.* tree ✅ Registered in registry.go with enterprise number 1347 ✅ Validated against TASKalfa 6052ci with 100% exact matches ✅ All tests passing Metrics Now Available (all direct OIDs, no calculation needed):\n✅ Total printed pages ✅ Function totals (Printer/Copy/Fax) ✅ Complete B\u0026W vs Color breakdown for all functions ✅ Scan counters (Copy/Fax/Other scans) ✅ Most comprehensive metrics of any vendor analyzed Priority 3: Test on Second Kyocera Device 🧪 Test ECOSYS PA4000wx (172.52.105.114) - mono device Verify OID structure consistency on mono vs color Kyocera models Confirm capability-aware OID filtering works correctly Questions for User To improve vendor module accuracy, please provide for each device:\nExpected total page count - Does it match discovery? Mono vs Color breakdown - Can you see this on the device panel? Supply levels (toner/ink) - Current percentages Additional counters: Scan pages (if MFP) Copy pages (if MFP) Fax pages (if fax-capable) Duplex pages/sheets Jam events This information will help us:\nValidate discovery data accuracy Build vendor modules with correct OID mappings Ensure we’re not missing important counters Prioritize which vendor modules to create first Testing Plan Once expected meters are provided:\nCompare discovery data to expected values\nIdentify any mismatches Verify standard Printer-MIB accuracy Analyze enterprise OID trees\nMap Epson 1.3.6.1.4.1.1248.* to specific counters Map Kyocera 1.3.6.1.4.1.1347.* to specific counters Document OID → metric mapping Build vendor modules incrementally\nStart with Epson (60% coverage) Add Kyocera (80% coverage) Add Ricoh (90% coverage) Deploy and validate\nMonitor learned OID system first (fixes immediate problem) Deploy vendor modules one at a time Compare metrics to expected values Adjust OID mappings as needed Notes All devices successfully use standard Printer-MIB for page counts Generic vendor module works correctly for basic metrics Problem is SNMP query method (GET vs WALK), not OID selection Learned OID system solves the immediate reliability issue Vendor-specific modules offer enhanced metrics beyond standard Printer-MIB Enterprise OID trees are vendor-documented but require research ","title":"Vendor Meter Analysis from Production Logs","url":"/development/vendor/vendor-meter-analysis/"},{"section":"development","text":"This directory contains vendor-specific SNMP OID mappings and analysis for printer manufacturers.\nFiles EPSON_OID_MAPPING.md Epson-specific OID mappings for enhanced metrics collection.\nEnterprise OID base: 1.3.6.1.4.1.231.* Custom counters and supply tracking Vendor quirks and workarounds KYOCERA_OID_MAPPING.md Kyocera-specific OID mappings and meter analysis.\nEnterprise OID discovery Counter mappings Vendor-specific behaviors VENDOR_METER_ANALYSIS.md Cross-vendor meter analysis and counter normalization strategies.\nComparison of meter implementations across vendors PrintAudit category mappings Heuristics for counter detection Usage These documents serve as reference material for:\nAdding new vendor support Understanding vendor-specific SNMP implementations Debugging vendor-specific issues Improving parser heuristics Implementation Vendor-specific code is implemented in:\nagent/scanner/vendor/hp.go agent/scanner/vendor/canon.go agent/scanner/vendor/brother.go agent/scanner/vendor/generic.go (fallback) agent/scanner/vendor/registry.go (vendor detection) Adding New Vendors Identify vendor enterprise OID from sysObjectID Document OID mappings in this directory Create vendor module in agent/scanner/vendor/ Add detection logic to registry.go Test with real hardware Update main SNMP reference: docs/SNMP_REFERENCE.md See: docs/SNMP_REFERENCE.md for general SNMP documentation\n","title":"Vendor-Specific OID Documentation","url":"/development/vendor/"},{"section":"development","text":"Overview The PrintMaster system now supports proxying HTTP requests through the WebSocket connection between agents and the server. This allows you to access:\nAgent web UIs from anywhere (even behind NAT/firewalls) Device web UIs (printer admin pages) through the agent’s network connection Architecture Browser → Server → WebSocket → Agent → Target (Agent UI or Device) ← ← ← ← Flow User clicks “Open UI” button in the server web interface Browser makes HTTP request to server proxy endpoint Server converts HTTP request to WebSocket message (proxy_request) Server sends message through WebSocket to connected agent Agent receives proxy request and makes local HTTP request to target Agent sends HTTP response back through WebSocket (proxy_response) Server forwards response to browser WebSocket Protocol Message Types proxy_request (Server → Agent) { \"type\": \"proxy_request\", \"data\": { \"request_id\": \"unique-id\", \"url\": \"http://localhost:8080/\", \"method\": \"GET\", \"headers\": { \"User-Agent\": \"...\", \"Accept\": \"...\" }, \"body\": \"base64-encoded-body\" }, \"timestamp\": \"2025-11-07T12:00:00Z\" } proxy_response (Agent → Server) { \"type\": \"proxy_response\", \"data\": { \"request_id\": \"unique-id\", \"status_code\": 200, \"headers\": { \"Content-Type\": \"text/html\", \"Content-Length\": \"1234\" }, \"body\": \"base64-encoded-response-body\" }, \"timestamp\": \"2025-11-07T12:00:01Z\" } API Endpoints Agent UI Proxy GET /api/v1/proxy/agent/{agentID}/{path...} Proxies HTTP requests to the agent’s own web UI (typically running on http://localhost:8080).\nExample:\nGET /api/v1/proxy/agent/my-agent-id/ GET /api/v1/proxy/agent/my-agent-id/api/devices Device UI Proxy GET /api/v1/proxy/device/{serialNumber}/{path...} Proxies HTTP requests to a device’s web UI through its associated agent.\nExample:\nGET /api/v1/proxy/device/ABC123/ GET /api/v1/proxy/device/ABC123/web/index.html UI Features Agent Cards Each agent card now has an “Open UI” button that:\nOpens the agent’s web interface in a new window Is disabled if the agent is not connected via WebSocket Shows a tooltip explaining why it’s disabled Agent Details Modal The agent details view includes an “Open Agent UI” button with the same functionality.\nDevice Cards Each device card now has an “Open Web UI” button that:\nOpens the device’s admin interface in a new window Is disabled if the device has no IP or no associated agent Requires the agent to be connected via WebSocket Implementation Details Server-Side (server/) Files Modified:\nwebsocket.go - Added proxy message handling and request/response tracking main.go - Added proxy endpoint handlers Key Functions:\nhandleAgentProxy() - Proxies requests to agent UIs handleDeviceProxy() - Proxies requests to device UIs proxyThroughWebSocket() - Core proxy logic sendProxyRequest() - Sends proxy request via WebSocket handleWSProxyResponse() - Handles proxy responses from agents Agent-Side (agent/agent/) Files Modified:\nws_client.go - Added proxy request handling Key Functions:\nhandleProxyRequest() - Receives proxy request, makes HTTP call, sends response sendProxyResponse() - Sends successful HTTP response sendProxyError() - Sends error response Web UI (server/web/) Files Modified:\napp.js - Added proxy buttons and JavaScript functions Key Functions:\nopenAgentUI(agentId) - Opens proxied agent UI openDeviceUI(serialNumber) - Opens proxied device UI Security Considerations Authentication: The proxy endpoints currently inherit authentication from the server’s session handling Agent Validation: Only connected agents can be proxied through Request Timeouts: Proxy requests have a 30-second timeout to prevent hanging connections Header Filtering: Hop-by-hop headers are filtered to prevent protocol issues Limitations WebSocket Required: Proxy only works when agent has active WebSocket connection Timeout: Long-running requests (\u003e30s) will timeout Binary Content: All content is base64-encoded, adding ~33% overhead HTTP Only: HTTPS device UIs must be accessed via HTTP from agent’s perspective No Streaming: Response is buffered entirely before being sent back Future Enhancements Potential improvements for future versions:\nStreaming Support: Stream responses instead of buffering WebSocket Upgrade: Support WebSocket connections through the proxy Compression: Add gzip compression for text content Caching: Cache static assets to reduce proxy traffic Port Configuration: Allow agents to specify custom web UI port SSL/TLS Support: Support HTTPS connections to devices Connection Pooling: Reuse HTTP connections to improve performance Testing To test the proxy feature:\nStart the server: ./printmaster-server Start an agent with WebSocket enabled: ./printmaster-agent --config config.toml Open the server web UI: http://localhost:8080 Navigate to Agents tab Click “Open UI” on an active agent - should open agent’s UI in new window Navigate to Devices tab Click “Open Web UI” on a device - should open device’s admin page Troubleshooting Button is Disabled Agent UI: Check that agent status is “active” (WebSocket connected) Device UI: Check that device has an IP address and associated agent “Agent not connected via WebSocket” Verify agent has use_websocket = true in config Check server logs for WebSocket connection status Verify network connectivity between agent and server “Proxy request timeout” Device may be offline or unreachable from agent Target service may be slow to respond Check agent logs for HTTP request errors Blank Page or Error Check browser console for errors Verify target service is running (agent UI or device web server) Check agent logs for proxy request handling Performance Notes Latency: Adds ~50-100ms overhead compared to direct access Throughput: Limited by WebSocket connection (~10-20 MB/s typical) Concurrent Requests: Multiple requests can be in-flight simultaneously Memory: Buffers entire response in memory (both agent and server) For large file downloads or high-throughput needs, consider direct access when possible.\n","title":"WebSocket HTTP Proxy Feature","url":"/development/websocket-proxy/"}]