# Product Requirements Document ## Minecraft Server Live Log Viewer **Status:** Draft for implementation **Target platform:** Fedora Linux **Minecraft deployment:** `itzg/docker-minecraft-server` **Application stack:** Rust + Axum + Tokio backend, Svelte frontend **Deployment model:** Single application service with embedded static frontend assets **Reverse proxy:** Existing external Caddy instance **Primary mode:** Read-only log viewing --- # 1. Product Summary The Minecraft Server Live Log Viewer is a private, read-only web application that allows approved Minecraft players to view current and historical server logs from a browser. The application must: - stream new Minecraft log lines to connected browsers in real time; - avoid periodic polling for live log updates; - expose historical logs through incremental, on-demand loading; - restrict access using the source IP address associated with successfully joined, whitelisted Minecraft players; - infer authorization only from successful player joins, not from failed login attempts; - use the Minecraft whitelist as an additional source of truth; - access all Minecraft data read-only; - require no database or separate frontend runtime; - run efficiently as a small Linux service/container; - cooperate with an existing Caddy reverse proxy. The application is not intended to provide strong identity authentication. IP-based access control is explicitly considered a lightweight privacy barrier suitable for non-critical read-only data. --- # 2. Goals The application should provide a convenient live log viewer for trusted Minecraft players without exposing server logs publicly. Primary goals are: 1. Provide near-real-time display of new lines from `latest.log`. 2. Load historical logs seamlessly when the user scrolls backwards. 3. Use Linux filesystem notifications rather than timer-based polling. 4. Authorize viewers based on IP addresses observed during successful Minecraft joins. 5. Confirm that a player associated with an IP is currently whitelisted. 6. Avoid granting access based on rejected, banned, failed, or otherwise unsuccessful connections. 7. Keep deployment operationally simple. 8. Avoid unnecessary infrastructure such as Redis, SQL databases, Node.js runtime services, or background polling daemons. 9. Prevent the application from modifying Minecraft server state. 10. Remain resilient across Minecraft log rotation, Minecraft restarts, viewer restarts, and archive creation. --- # 3. Non-Goals The first version is not intended to provide: - Minecraft server administration; - RCON access; - command execution; - log editing; - file modification; - player moderation; - user accounts; - password authentication; - OAuth; - durable per-user preferences; - full-text search over an arbitrarily large log corpus; - analytics; - metrics dashboards; - server console input; - strong proof that a web visitor is the exact Minecraft account associated with an IP; - public access; - multi-server aggregation. These may be considered separately in future versions. --- # 4. Threat Model ## 4.1 Protected Information The logs may contain: - player usernames; - UUIDs; - player IP addresses; - connection timestamps; - chat messages; - server internals; - plugin/mod information; - filesystem or diagnostic information emitted by mods; - administrative actions; - server errors. The information is not considered security-critical, but it should not be exposed indiscriminately to the public Internet. ## 4.2 Security Objective The intended security property is: > A random Internet visitor who has not recently connected successfully to the Minecraft server from an IP associated with a currently whitelisted player should not be able to view the log interface. This is a privacy barrier, not strong authentication. ## 4.3 Known Limitations of IP-Based Authorization The implementation must explicitly recognize these limitations: - multiple people behind the same NAT may share one public IP; - VPN users may share egress IPs; - mobile and residential IPs may change; - a reassigned dynamic IP may later belong to an unrelated party; - IPv6 privacy addresses may change frequently; - proxies can obscure true client addresses; - multiple Minecraft players may legitimately appear under one address; - possession of an IP address does not prove possession of a Minecraft account. The application must therefore avoid presenting this mechanism as account-level authentication. --- # 5. High-Level Architecture The deployment architecture is: ```text Browser │ │ HTTPS + WebSocket ▼ Existing Caddy instance │ │ trusted reverse-proxy connection ▼ Minecraft Log Viewer ├── Axum HTTP server ├── WebSocket endpoint ├── authorization service ├── Minecraft log parser ├── historical log index ├── live log follower ├── filesystem watcher ├── optional in-memory archive cache └── embedded Svelte static assets │ │ read-only ▼ Minecraft /data volume ├── whitelist.json └── logs/ ├── latest.log └── historical archives ``` The viewer should run independently of the Minecraft process. No communication with Docker itself is required. The Docker socket must not be mounted into the viewer container. --- # 6. Technology Selection ## 6.1 Backend Use: - Rust; - Tokio; - Axum; - Tower middleware where appropriate. Responsibilities include: - HTTP routing; - WebSocket lifecycle; - client IP extraction; - authorization; - filesystem event handling; - live file following; - historical log indexing; - archive decompression; - log chunk retrieval; - frontend static asset serving. ## 6.2 Frontend Use: - Svelte; - TypeScript; - Vite. The frontend should compile to static assets. Those static files should be embedded into the Rust binary or otherwise bundled into the final application image so that a separate frontend server is unnecessary. ## 6.3 Linux Filesystem Events Use Rust's `notify` ecosystem or equivalent abstraction backed by Linux `inotify`. The application must not periodically poll `latest.log` for new lines. Polling may be used only where filesystem event semantics make it unavoidable for exceptional recovery paths, and must not constitute the normal log-following mechanism. ## 6.4 WebSocket Transport Use native Axum WebSocket support. The WebSocket is used only for server-to-client live updates and optionally lightweight control messages. Historical pagination should use ordinary HTTP requests unless implementation simplicity strongly favors multiplexing both mechanisms. --- # 7. Filesystem Layout The application should support configurable paths rather than hard-coded ones. Recommended defaults: ```text /data/logs/latest.log /data/logs/ /data/whitelist.json ``` Environment configuration should permit overrides. Example: ```text MC_DATA_DIR=/data MC_LOG_DIR=/data/logs MC_LATEST_LOG=/data/logs/latest.log MC_WHITELIST=/data/whitelist.json ``` The viewer must work with the Minecraft data directory mounted read-only. Example Docker mount: ```text minecraft-data:/data:ro ``` --- # 8. Log Formats and Archive Discovery The viewer must not assume that all historical log files use one exact filename extension without configuration or detection. The indexer should support at least: - plain `.log`; - `.log.gz`; - `.gz`; - `.tar.gz` where applicable. The implementation should inspect actual filenames and archive contents before deciding how to read them. Archive readers should use streaming decompression rather than extracting archives to disk. Temporary extraction into the Minecraft data volume is forbidden. If temporary files are ever required, they must reside exclusively within an application-controlled temporary directory and never modify `/data`. --- # 9. Historical Log Index ## 9.1 Startup Behavior At startup, the viewer should scan the configured log directory and create an in-memory metadata index. The startup scan should identify: - filename; - path; - archive type; - compressed size; - modification timestamp; - ordering relative to other log files; - best-known log time range where inexpensive to determine. The application should not eagerly decompress the complete historical archive corpus into memory. ## 9.2 Ordering Historical logs must be presented as one logical chronological stream. The backend must establish a deterministic ordering. Where filenames contain timestamps, use those timestamps. When filenames are ambiguous, fall back to metadata and/or timestamps parsed from content. The implementation should be resilient to overlapping time ranges between rotated files. ## 9.3 Archive Cache An optional in-memory LRU cache may retain recently decompressed historical logs. The cache should have explicit resource bounds. Recommended configuration: ```text ARCHIVE_CACHE_MAX_BYTES ARCHIVE_CACHE_MAX_FILES ``` Eviction should occur automatically. The application must remain correct if caching is disabled. --- # 10. Historical Log Retrieval Historical logs must be loaded incrementally. The browser should initially receive only a bounded recent window. Recommended initial target: ```text 500–2,000 lines ``` The exact default should be configurable. When the user scrolls toward the top, the frontend requests older data. Conceptual API: ```http GET /api/logs/history?before=&limit=1000 ``` Response: ```json { "lines": [ { "id": "...", "timestamp": "...", "text": "...", "source": "..." } ], "next_before": "...", "has_more": true } ``` Cursor-based pagination is preferred over page numbers. A cursor should encode a stable logical position rather than exposing arbitrary filesystem paths. Clients must not be allowed to request arbitrary files. --- # 11. Live Log Following ## 11.1 Core Requirement New lines written to `latest.log` must appear in connected browsers without periodic polling. Expected flow: ```text filesystem modification │ ▼ inotify event │ ▼ read latest.log from previous byte offset │ ▼ buffer incomplete trailing line if necessary │ ▼ emit completed lines │ ├── update authorization state if relevant │ └── broadcast to WebSocket clients ``` ## 11.2 Byte Offset Tracking The live follower must maintain: - currently opened file identity; - current byte offset; - partial-line buffer; - relevant file metadata. Only newly appended bytes should normally be read. The file must not be reread from the beginning on every modification. ## 11.3 Partial Writes A filesystem modification event does not guarantee that the last byte is a newline. The implementation must retain incomplete line fragments until more bytes arrive. No partial log line should be emitted to clients as a complete line. ## 11.4 Directory Watching Watch the containing log directory rather than relying exclusively on an open watch against `latest.log`. This is required because Minecraft may rotate, rename, replace, delete, or recreate the file. The watcher should react to events including: - modify; - create; - move/rename; - remove. ## 11.5 Log Rotation When `latest.log` is replaced: 1. detect identity change; 2. finish reading any remaining bytes from the old file where possible; 3. discover the newly created `latest.log`; 4. reset the live follower to the appropriate offset; 5. refresh the historical index; 6. continue emitting lines without requiring process restart. Duplicate line emission should be avoided. --- # 12. WebSocket Protocol Recommended endpoint: ```text GET /api/live Upgrade: websocket ``` The connection must pass authorization before upgrade. The application should use structured messages. Example: ```json { "type": "log_lines", "lines": [ { "id": "...", "timestamp": "...", "text": "..." } ] } ``` Possible additional message types: ```text hello log_lines rotation history_changed error server_restart ``` A minimal v1 can implement only what is required. ## 12.1 Batching If Minecraft emits many lines in one filesystem event, the backend should send them as a batch rather than one WebSocket frame per line. ## 12.2 Backpressure A slow browser must not block the global log follower. Recommended architecture: ```text log follower │ ▼ Tokio broadcast channel │ ├── WS client A ├── WS client B └── WS client C ``` Each client should have bounded buffering. If a client falls too far behind: - disconnect it; or - send a resynchronization-required signal. Unbounded per-client queues are forbidden. ## 12.3 Reconnection The frontend should reconnect after unexpected WebSocket termination. Use bounded exponential backoff. On reconnection, the client should request recent lines over HTTP to fill any gap before resuming live updates. The correctness of the log display must not depend on every WebSocket frame being received. --- # 13. Authorization Model ## 13.1 Authorization Principle A requesting IP is allowed if the backend can establish that: 1. the IP appeared in a successful Minecraft player login/join; 2. the associated player identity is currently present in the Minecraft whitelist. A mere connection attempt is insufficient. ## 13.2 Sources of Truth Use two independent data sources: ### Minecraft logs Used to establish: ```text IP ↔ successfully joined player ``` ### `whitelist.json` Used to establish: ```text player ↔ currently whitelisted ``` Authorization requires both conditions. --- # 14. Whitelist Parsing `whitelist.json` should be parsed structurally as JSON, never with regular expressions. The parser should extract at least: - player UUID; - player name. Maintain an in-memory whitelist snapshot. The application may watch `whitelist.json` for filesystem modifications so that additions and removals become effective without restart. If the whitelist file cannot be parsed: - log an application error; - retain the last valid whitelist snapshot if one exists; - fail closed for newly evaluated identities if no valid whitelist information exists. A malformed whitelist must never result in all clients being allowed. --- # 15. Successful Join Detection ## 15.1 General Rule Do not authorize merely because a line contains: - an IP address; - a username; - a handshake; - a connection attempt. Authorization should be created only after the parser observes a definitive successful join sequence. Minecraft versions, proxies, mod loaders, and plugins may change exact log text, so parsing must be isolated in a dedicated module. ## 15.2 Parser State The parser should ideally correlate connection events. Conceptually: ```text connection observed │ ▼ candidate { player, uuid, ip } │ ├── rejected → discard │ └── successful login/join → authorize candidate ``` Depending on the actual Minecraft log format, the application may infer the association from nearby deterministic lines. ## 15.3 Failed Attempts These must not authorize an IP: - whitelist rejection; - ban rejection; - authentication failure; - invalid session; - malformed packet; - incompatible version; - server full; - duplicate login; - timeout before joining; - connection closed before successful login; - status ping/query; - bot/scanner traffic. ## 15.4 Parser Tests The parser should have fixture-based automated tests containing realistic log sequences for: - normal successful join; - non-whitelisted player; - banned player; - disconnect during login; - successful join followed by disconnect; - multiple players behind one IP; - IPv4; - IPv6; - reconnect; - username changes where UUID is available; - log rotation between relevant events. Security-sensitive parsing logic should not rely exclusively on ad hoc regex testing. --- # 16. Authorization State Recommended in-memory representation: ```text IP address ↓ one or more observed player identities ``` Each successful association should include: - IP; - player name; - UUID when available; - successful join timestamp; - source log position. Example conceptual record: ```json { "ip": "203.0.113.42", "players": [ { "name": "ExamplePlayer", "uuid": "...", "joined_at": "..." } ] } ``` If any associated player remains currently whitelisted, the address may be authorized. Prefer UUID comparison over username comparison when UUID information is available. --- # 17. Authorization Cache and Rescan Behavior The requested behavior is: 1. obtain incoming request IP; 2. check current in-memory allowed-IP set; 3. if present, allow; 4. if absent, rescan only `latest.log`; 5. update successful-join state; 6. reevaluate whitelist membership; 7. if now authorized, allow; 8. otherwise reject. Historical archives must not be scanned to authorize a new incoming request. This ensures that access reflects players observed in the current active log period rather than indefinitely trusting IP addresses seen months earlier. ## 17.1 Concurrency Multiple simultaneous authorization misses from the same or different IP addresses must not trigger redundant full rescans. Use a synchronization mechanism such as: - Tokio mutex around rescan; - singleflight/coalescing logic; - generation-based cache rebuild. After one task refreshes the auth state, waiting requests should reevaluate against the new generation. --- # 18. Authorization Lifetime An IP association inferred from `latest.log` naturally persists until the active log is rotated/replaced or the viewer restarts and reconstructs state. This behavior should be explicitly documented. Recommended v1 behavior: - successful joins contained in current `latest.log` are eligible; - successful joins present only in archived logs are not eligible; - whitelist removal invalidates authorization as soon as the updated whitelist is observed; - restarting the log viewer reconstructs state by scanning current `latest.log`. A future version may add configurable TTLs, but v1 does not require them. --- # 19. Client IP Extraction Correct client IP extraction is security-sensitive. The app must support operation behind the existing Caddy reverse proxy. It must not blindly trust arbitrary `X-Forwarded-For` or similar headers. Recommended model: ```text Internet │ ▼ Caddy │ ▼ viewer bound only to trusted/internal interface ``` The viewer should accept forwarded client-IP headers only when the direct TCP peer is a configured trusted proxy. Configuration should include something conceptually similar to: ```text TRUST_PROXY=true TRUSTED_PROXY_CIDRS=127.0.0.1/32,::1/128, CLIENT_IP_HEADER=X-Forwarded-For ``` The exact header may be chosen to match the existing Caddy configuration. ## 19.1 Validation Rules The backend should: 1. inspect the immediate TCP peer; 2. determine whether that peer is trusted; 3. only then inspect the forwarded address; 4. parse the forwarded value as a real IP address; 5. normalize IPv4/IPv6 representation; 6. reject malformed values. If a connection does not originate from a trusted proxy, forwarded headers must be ignored. --- # 20. Reverse Proxy Contract Caddy itself is outside this project's deployment scope. The viewer nevertheless requires the reverse proxy to provide: - HTTPS externally; - WebSocket upgrade forwarding; - original client IP forwarding; - no public bypass route to the viewer backend; - appropriate request-size and connection behavior. The application documentation should include a small example Caddy configuration, clearly labelled as an integration example rather than managed infrastructure. --- # 21. Log Privacy and Redaction The source logs may include player IP addresses. The backend should support output redaction before sending lines to browsers. Recommended default: ```text REDACT_PLAYER_IPS=true ``` Example source: ```text PlayerName[/203.0.113.42:51324] logged in ``` Rendered: ```text PlayerName/[IP REDACTED] logged in ``` Authorization parsing must occur against the original unredacted text. Redaction occurs only in the presentation path. If administrators intentionally want players to see addresses, the feature may be disabled through configuration. The frontend must never receive raw addresses when redaction is enabled. --- # 22. HTTP API A minimal API might expose: ```text GET /api/status GET /api/logs/recent GET /api/logs/history GET /api/live ``` All log-related endpoints require authorization. ## 22.1 Status Endpoint A private or minimally revealing health endpoint may return: ```json { "status": "ok" } ``` Operational health checks should not expose logs, player names, addresses, filesystem paths, or other sensitive server information. A separate internal readiness endpoint may expose more detail if bound appropriately. ## 22.2 Recent Logs Example: ```http GET /api/logs/recent?limit=1000 ``` Returns a recent bounded snapshot. ## 22.3 Historical Logs Example: ```http GET /api/logs/history?before=&limit=1000 ``` The backend should clamp `limit` to a configured maximum. --- # 23. Frontend UX The primary interface is a vertically scrollable terminal-like log viewer. Required behavior: - newest lines appear at the bottom; - new lines appear live; - scrolling upward loads older history; - user position should not jump unexpectedly; - if the user is already at the bottom, new lines should keep the view pinned to the bottom; - if the user has scrolled upward, incoming lines should not force the viewport downward; - show a "new lines" indicator when live data arrives while the user is reading older content; - clicking the indicator returns to the live tail; - connection state should be visible; - authorization failure should show a concise access-denied screen. Possible connection states: ```text Live Reconnecting Disconnected Access denied ``` --- # 24. Frontend Performance The browser must not retain an unlimited number of DOM nodes. Use one of: - virtual scrolling; - bounded rendered window; - chunked log rendering. The application may retain more log lines in JavaScript memory than are mounted in the DOM, but memory growth should remain bounded for long-running sessions. For example: ```text rendered DOM lines: ~1,000–5,000 client retained lines: configurable ``` Exact values should be established through testing. --- # 25. Live/History Merge Semantics The frontend will receive historical lines via HTTP and live lines via WebSocket. Each logical line should have a stable identifier or cursor sufficient to deduplicate overlapping ranges. For example, an internal identifier may derive from: ```text source-file-generation + byte-offset ``` Do not rely solely on timestamp + text because duplicate lines may legitimately exist. After reconnecting: 1. fetch recent snapshot; 2. reconcile with existing line IDs; 3. remove duplicates; 4. resume WebSocket tailing. --- # 26. Application Startup Sequence Recommended startup sequence: ```text 1. parse configuration 2. validate filesystem paths 3. load whitelist 4. build historical log index 5. scan latest.log for authorization state 6. establish current latest.log offset 7. start filesystem watcher 8. start live follower 9. initialize broadcast channel 10. start HTTP/WebSocket server ``` The server should not report itself ready before critical initialization succeeds. Historical index failure may be treated as degraded operation if live logs remain available. Authorization initialization failure should fail closed. --- # 27. Race-Free Startup A naïve startup sequence can lose lines written between initial EOF detection and watcher registration. The implementation should explicitly handle this. A robust sequence may: 1. open `latest.log`; 2. note current file identity and offset; 3. register directory watch; 4. reread from the recorded offset; 5. process any events that occurred during initialization. The exact implementation may differ, but no silent live-log gap should be introduced by startup races. --- # 28. File Identity Path equality is insufficient for rotation detection. On Linux, use filesystem metadata capable of distinguishing replaced files, such as inode/device identity where available. Store enough metadata to determine whether: ```text /data/logs/latest.log ``` now refers to a different underlying file. --- # 29. Error Handling The application should distinguish recoverable and fatal errors. ## Recoverable Examples: - temporary `latest.log` disappearance during rotation; - malformed individual log line; - archive failing to decompress; - WebSocket disconnect; - historical file removed between indexing and read; - transient filesystem event loss. These should be logged and recovered from where possible. ## Fatal Examples: - configured Minecraft data directory missing at startup; - unreadable whitelist with no valid snapshot; - inability to bind HTTP server; - invalid security-critical proxy configuration. Fatal initialization errors should terminate with a non-zero exit status so the service manager can surface the failure. --- # 30. Filesystem Watch Overflow and Recovery Linux filesystem notification queues can overflow. The watcher must not assume it receives every event forever. If the backend detects an overflow or uncertain watcher state: 1. rescan directory metadata; 2. compare current file identity and size; 3. recover unread bytes from `latest.log`; 4. rebuild the historical index if necessary; 5. continue normal operation. This is an event-loss recovery mechanism, not normal polling. --- # 31. Truncation Handling If `latest.log` shrinks without being replaced, the follower should detect: ```text current size < previous offset ``` This indicates truncation. The reader should reset safely and avoid attempting to seek beyond EOF. Authorization state may need rebuilding from the newly truncated `latest.log`. --- # 32. Character Encoding Minecraft logs are generally textual UTF-8-compatible content, but malformed bytes should not crash the service. Use tolerant decoding where appropriate. Invalid sequences may be represented using replacement characters. The original raw bytes do not need to be exposed. --- # 33. Rate Limiting and Abuse Resistance Although the app is private, unauthenticated requests can still reach Caddy. The backend should guard expensive miss-path behavior. Authorization rescans must be coalesced as described earlier. Additionally, consider: - small request body limits; - historical pagination limits; - maximum concurrent WebSocket connections; - bounded decompression work; - maximum archive size accepted; - bounded decompressed output; - request timeouts. Do not trust compressed file size as a guarantee of decompressed size. Archive decompression should guard against unexpectedly large/corrupt files. --- # 34. Resource Limits All long-lived or potentially expanding queues should be bounded. This includes: - WebSocket outbound queues; - archive cache; - decompression buffers; - historical response sizes; - frontend retained history; - parser candidate login state. The application should be safe to leave running indefinitely. --- # 35. Observability Use structured application logging. Recommended fields include: ```text event level client_ip path archive error connection_id duration_ms ``` Avoid emitting sensitive information unnecessarily. Particularly, application logs should not duplicate raw Minecraft player IP information more than needed. Suggested operational events: - application startup; - historical index built; - whitelist loaded; - whitelist changed; - live file opened; - rotation detected; - archive read failure; - watcher recovery; - WebSocket connected; - WebSocket disconnected; - authorization denied; - authorization refreshed. Do not log entire HTTP headers. --- # 36. Application Metrics Metrics are optional for v1. If implemented, useful counters include: ```text connected_websocket_clients authorization_allow_total authorization_deny_total authorization_rescan_total live_lines_broadcast_total archive_reads_total archive_cache_hits_total archive_cache_misses_total watcher_recovery_total ``` A Prometheus dependency is not required for initial implementation. --- # 37. Configuration All deployment-specific values should be configurable through environment variables or a simple config file. Suggested settings: ```text LISTEN_ADDR MC_DATA_DIR MC_LOG_DIR MC_LATEST_LOG MC_WHITELIST TRUST_PROXY TRUSTED_PROXY_CIDRS CLIENT_IP_HEADER INITIAL_LOG_LINES MAX_HISTORY_LINES_PER_REQUEST ARCHIVE_CACHE_MAX_BYTES ARCHIVE_CACHE_MAX_FILES WS_CLIENT_QUEUE_CAPACITY MAX_WS_CONNECTIONS REDACT_PLAYER_IPS RUST_LOG ``` Configuration must be validated before starting the HTTP listener. Invalid CIDR ranges, filesystem paths, and numeric bounds should fail startup with clear errors. --- # 38. Container Deployment Recommended Compose concept: ```yaml services: minecraft: image: itzg/minecraft-server volumes: - minecraft-data:/data log-viewer: image: minecraft-log-viewer restart: unless-stopped volumes: - minecraft-data:/data:ro environment: MC_DATA_DIR: /data MC_LOG_DIR: /data/logs MC_LATEST_LOG: /data/logs/latest.log MC_WHITELIST: /data/whitelist.json ``` The exact networking should reflect the existing Caddy deployment. The viewer should not expose a host port if Caddy can reach it through an internal Docker network. --- # 39. Container Security Recommended runtime hardening where practical: ```text read_only: true no-new-privileges drop unnecessary Linux capabilities non-root UID read-only Minecraft volume tmpfs for temporary application files ``` The application requires filesystem read access but should not require privileged capabilities. It must not run as root unless the deployment environment makes that unavoidable. --- # 40. Single-Binary Packaging The preferred build output is a Rust executable containing the compiled Svelte frontend. Possible implementation pattern: ```text npm/pnpm/bun only during build │ ▼ Vite compiles Svelte │ ▼ dist/ │ ▼ Rust build embeds dist/ │ ▼ single runtime binary ``` A Node/Bun runtime is not required in the production container. A multi-stage Docker build may include frontend tooling only in the builder stage. --- # 41. Suggested Backend Module Structure A reasonable source layout: ```text src/ ├── main.rs ├── config.rs ├── http/ │ ├── mod.rs │ ├── history.rs │ ├── recent.rs │ └── websocket.rs ├── auth/ │ ├── mod.rs │ ├── client_ip.rs │ ├── join_tracker.rs │ └── whitelist.rs ├── logs/ │ ├── mod.rs │ ├── index.rs │ ├── parser.rs │ ├── follower.rs │ ├── watcher.rs │ ├── archive.rs │ └── cursor.rs └── frontend.rs ``` Exact naming is implementation-dependent. --- # 42. Suggested Runtime State Shared application state may conceptually include: ```rust AppState { config, whitelist, authorization_state, historical_index, archive_cache, live_broadcast_tx, log_generation, } ``` Use immutable or read-optimized structures where possible. Do not place a single global mutex around all application state. Use independent synchronization domains for: - whitelist; - authorization; - historical index; - cache. This prevents archive reads from blocking live broadcasting or authorization. --- # 43. Concurrency Model Suggested task ownership: ```text main Axum runtime │ ├── filesystem watcher task ├── latest.log follower task ├── optional index refresh worker ├── authorization refresh coordination └── one task per WebSocket connection ``` Blocking archive decompression should not monopolize Tokio's async worker threads. Use: ```text tokio::task::spawn_blocking ``` or an equivalent bounded blocking worker mechanism for CPU/blocking filesystem work where appropriate. --- # 44. Historical Index Refresh The index should update when: - a log file is created; - a file is renamed during rotation; - an archive appears; - an existing archive disappears. Directory filesystem events should trigger targeted index refreshes. A complete rescan is acceptable after infrequent rotation events because the number of archived logs is expected to remain modest. No fixed recurring rescan interval is required. --- # 45. Browser Security The application should ship reasonable browser security headers, unless Caddy centrally provides them. Relevant policies may include: ```text Content-Security-Policy X-Content-Type-Options Referrer-Policy frame-ancestors ``` No third-party scripts are required. Avoid external CDNs. The app should be self-contained. This reduces privacy exposure and simplifies CSP. --- # 46. WebSocket Origin Validation Because authorization is based on source IP rather than a browser credential, a malicious website opened by an authorized user could potentially attempt cross-site WebSocket/API access from that user's browser. Therefore the backend should validate the HTTP `Origin` header for browser-originated API and WebSocket connections. The configured public application origin should be allowlisted. Example: ```text PUBLIC_ORIGIN=https://logs.example.com ``` Requests carrying an unexpected browser Origin should be rejected. This is an important complement to IP-based authorization. --- # 47. CORS Cross-origin API access is not required. CORS should therefore be disabled or configured to allow only the application's own origin. Do not use: ```text Access-Control-Allow-Origin: * ``` for protected log APIs. --- # 48. DNS-Rebinding and Host Validation The service should optionally validate expected `Host`/forwarded host information when operating behind Caddy. The application should not rely on host validation as its primary security mechanism, but checking the expected public host reduces accidental exposure through unexpected proxy routes. --- # 49. Authorization Endpoint Behavior Unauthorized requests should return: ```http 403 Forbidden ``` Do not reveal: - whether an IP was previously seen; - which usernames are associated with the IP; - whitelist contents; - parsed player identities. The response should remain generic. Example: ```json { "error": "access_denied" } ``` --- # 50. Privacy-Preserving Denial The access-denied frontend should not reveal server log snippets. It may say: ```text Access denied. This viewer is available only from an IP address associated with a currently whitelisted player who has successfully joined the server. ``` No diagnostic IP data should be exposed unless intentionally enabled for troubleshooting. --- # 51. Testing Strategy ## 51.1 Unit Tests Required areas: - Minecraft log parsing; - IPv4 parsing; - IPv6 parsing; - forwarded-header handling; - trusted proxy validation; - whitelist parsing; - cursor encode/decode; - log redaction; - archive filename ordering; - line buffering. ## 51.2 Integration Tests Create temporary filesystem fixtures simulating: - append to `latest.log`; - partial-line append; - multiple-line append; - file truncation; - file replacement; - log rotation; - archive creation; - whitelist modification; - concurrent authorization misses. ## 51.3 WebSocket Tests Verify: - unauthorized upgrade rejected; - authorized upgrade accepted; - appended lines delivered; - batches preserve ordering; - slow clients do not block others; - client queue overflow is handled; - reconnection can recover missing lines. ## 51.4 Security Tests Verify: - arbitrary `X-Forwarded-For` from an untrusted peer is ignored; - spoofed forwarded IP cannot grant access; - failed Minecraft joins cannot grant access; - removed whitelist entry revokes authorization; - malformed whitelist fails closed; - cross-origin WebSocket connection is rejected; - history endpoint cannot perform filesystem traversal; - archive filenames cannot escape configured directory. --- # 52. Performance Targets Given the intended small private server deployment, the application should favor correctness and bounded resource consumption over extreme throughput. Reasonable targets: - idle CPU usage effectively negligible; - no recurring file polling workload; - new log lines visible in browser typically within hundreds of milliseconds of write/event delivery; - application memory stable during indefinite operation; - historical page retrieval responsive for small compressed files; - dozens of simultaneous WebSocket viewers supported comfortably. The architecture should not assume only one viewer. --- # 53. Failure Scenarios ## Minecraft server stopped The log viewer remains available. It should continue serving existing history and display that no new lines are arriving. ## `latest.log` temporarily missing The watcher waits for recreation and resumes. ## Minecraft restarts The viewer should detect the resulting log changes/rotation and continue. ## Viewer restarts It rebuilds whitelist, authorization state, and history index from disk. No separate persistence is required. ## Historical archive corrupted Return a controlled error for the affected range while preserving access to other logs. ## Browser loses network Frontend reconnects and fills the gap. ## Caddy restarts WebSockets disconnect; clients reconnect automatically. --- # 54. Data Persistence The application itself should not require persistent writable storage for v1. All authoritative data exists in: ```text Minecraft logs whitelist.json ``` Caches and indexes may be reconstructed on startup. This simplifies backup and deployment. --- # 55. Log Search Full-text search is outside initial scope. However, the backend design should avoid making future search unnecessarily difficult. Historical log retrieval APIs should preserve structured logical positions. A later version could add: ```text GET /api/logs/search?q=... ``` without changing the live-follow architecture. --- # 56. Accessibility The viewer should: - remain keyboard-scrollable; - use sufficient contrast; - not rely on color alone for connection state; - expose connection state textually; - respect browser font scaling; - avoid forced animations; - allow log text selection and copying. --- # 57. Responsive Design Desktop is the primary environment, but the interface should remain usable on mobile. Minimum behavior on narrow screens: - log text horizontally scrollable or sensibly wrapped according to user preference; - controls do not obscure the log; - connection state remains visible. A wrap/no-wrap toggle may be useful but is not required for initial implementation. --- # 58. Recommended MVP The first production-worthy milestone should include: 1. Rust/Axum HTTP server. 2. Embedded Svelte frontend. 3. Read-only Minecraft data mount. 4. Current whitelist parser. 5. Successful-join/IP parser. 6. Authorization cache. 7. Rescan-current-log-on-auth-miss. 8. Trusted reverse proxy client-IP extraction. 9. Same-origin/Origin validation. 10. `inotify`-driven live follower. 11. directory-level log rotation handling. 12. WebSocket broadcasting. 13. latest/recent log endpoint. 14. historical log index. 15. lazy archive decompression. 16. backwards cursor pagination. 17. frontend infinite upward scrolling. 18. WebSocket reconnect and gap recovery. 19. bounded queues and caches. 20. IP redaction enabled by default. 21. automated parser/security tests. 22. container image suitable for existing Caddy infrastructure. --- # 59. Deferred Enhancements Potential later features include: - text search; - regex filtering; - log-level filtering; - player-name filtering; - client-side highlighting; - URL-addressable timestamps; - downloadable selected log ranges; - administrator authentication mode; - multiple Minecraft servers; - configurable authorization TTL; - audit trail; - persistent decompressed index; - structured parsing of chat/player events; - metrics endpoint. None are required for initial launch. --- # 60. Implementation Priorities Development should proceed in this order: ### Phase 1 — Files and parsing Implement: - configuration; - whitelist reader; - log reader; - successful join parser; - authorization reconstruction; - parser fixtures and tests. ### Phase 2 — Live backend Implement: - directory watcher; - append follower; - rotation handling; - broadcast channel; - WebSocket transport. ### Phase 3 — Security boundary Implement: - trusted proxy logic; - client IP normalization; - authorization middleware; - same-origin checks; - history path isolation; - redaction. ### Phase 4 — Historical backend Implement: - archive discovery; - chronological index; - lazy decompression; - cursor pagination; - bounded cache. ### Phase 5 — Frontend Implement: - recent snapshot; - live WebSocket; - incremental history; - scroll anchoring; - reconnect recovery; - connection indicators; - access denied state; - virtualization/bounded DOM. ### Phase 6 — Packaging Implement: - Vite production build; - frontend embedding; - multi-stage container build; - health/readiness behavior; - non-root runtime; - read-only filesystem where possible. ### Phase 7 — Hardening Exercise: - log rotations; - server restarts; - viewer restarts; - whitelist changes; - malformed archives; - IPv6; - proxy spoofing attempts; - slow WebSocket clients; - notification overflow recovery. --- # 61. Acceptance Criteria The implementation is considered production-ready for the intended environment when all of the following are true. ### Live logs - Appending a complete line to `latest.log` causes that line to appear in authorized connected browsers without timer-based polling. - Partial writes do not generate malformed lines. - File replacement and rotation do not require restarting the viewer. - A slow WebSocket client cannot stall the global live-log pipeline. ### Historical logs - Archived logs can be viewed without preloading the full archive corpus into RAM. - Scrolling upward retrieves older logs incrementally. - Chronological ordering remains correct across file boundaries. - Browser DOM/memory behavior remains bounded during extended use. ### Authorization - A currently whitelisted player who successfully joined from an IP can access the viewer from that address. - A whitelist-rejected connection cannot gain access. - A banned/failed login cannot gain access. - An unknown IP causes the current `latest.log` authorization state to be refreshed before denial. - Historical archives are not used to grant access. - Removing a player from `whitelist.json` invalidates authorization associated solely with that player. - Spoofing `X-Forwarded-For` from outside the trusted proxy path does not grant access. ### Privacy - Raw player IPs are not sent to browsers when redaction is enabled. - Unauthorized responses reveal no player or whitelist information. - Cross-origin browser access is rejected. ### Deployment - The viewer runs without a Node/Bun runtime. - The frontend is served by the Rust application. - Minecraft data is mounted read-only. - No database is required. - No Docker socket is required. - Existing Caddy can proxy normal HTTP and WebSocket traffic to the application. ### Resilience - Minecraft restart does not permanently interrupt live log viewing. - Viewer restart reconstructs required state from Minecraft files. - Filesystem notification overflow has a defined recovery path. - A corrupt archive does not crash the whole service. - All potentially growing queues and caches have explicit bounds. --- # 62. Final Architecture Decision The selected implementation is: ```text Rust ├── Axum ├── Tokio ├── notify/inotify ├── WebSockets ├── streaming archive readers ├── whitelist + log authorization parser └── embedded Svelte frontend ``` Deployment: ```text existing Caddy │ ▼ single log-viewer service │ ▼ read-only Minecraft /data ``` No application database, Redis instance, Node runtime, polling loop, Docker API access, or Minecraft administrative interface is required. The design intentionally favors reconstructable in-memory state, event-driven IO, bounded resource usage, and a narrow read-only security boundary.