1931 lines
44 KiB
Markdown
1931 lines
44 KiB
Markdown
# 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=<cursor>&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,<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:
|
||
|
||
```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=<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:
|
||
|
||
```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. |