Files
mcp/PRD.md
T
2026-08-10 21:55:52 +08:00

44 KiB
Raw Blame History

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:

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:

/data/logs/latest.log
/data/logs/
/data/whitelist.json

Environment configuration should permit overrides.

Example:

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:

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:

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:

5002,000 lines

The exact default should be configurable.

When the user scrolls toward the top, the frontend requests older data.

Conceptual API:

GET /api/logs/history?before=<cursor>&limit=1000

Response:

{
  "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:

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:

GET /api/live
Upgrade: websocket

The connection must pass authorization before upgrade.

The application should use structured messages.

Example:

{
  "type": "log_lines",
  "lines": [
    {
      "id": "...",
      "timestamp": "...",
      "text": "..."
    }
  ]
}

Possible additional message types:

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:

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:

IP ↔ successfully joined player

whitelist.json

Used to establish:

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:

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:

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:

{
  "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:

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:

TRUST_PROXY=true
TRUSTED_PROXY_CIDRS=127.0.0.1/32,::1/128,<internal-network>
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:

REDACT_PLAYER_IPS=true

Example source:

PlayerName[/203.0.113.42:51324] logged in

Rendered:

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:

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:

{
  "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:

GET /api/logs/recent?limit=1000

Returns a recent bounded snapshot.

22.3 Historical Logs

Example:

GET /api/logs/history?before=<cursor>&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:

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:

rendered DOM lines: ~1,0005,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:

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:

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:

/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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

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:

{
  "error": "access_denied"
}

50. Privacy-Preserving Denial

The access-denied frontend should not reveal server log snippets.

It may say:

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:

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:

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:

Rust
├── Axum
├── Tokio
├── notify/inotify
├── WebSockets
├── streaming archive readers
├── whitelist + log authorization parser
└── embedded Svelte frontend

Deployment:

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.