Skip to content

Configuration Examples

虚拟世界的懒猫 edited this page Feb 26, 2026 · 4 revisions

English | 中文

Real-world configuration examples for common deployment scenarios.

This page provides complete, copy-paste-ready configuration examples for FileHunter. Each example targets a specific deployment scenario with a full TOML configuration and an explanation of the key design decisions. For a complete reference of all fields and their defaults, see the Configuration Reference.


1. 🟢 Minimal Configuration

The simplest possible FileHunter setup: a single catch-all location serving all files from one directory with no extension filtering. Every field not specified falls back to its default value.

[server]
bind = "0.0.0.0:8080"            # listen on all interfaces, port 8080

[[locations]]
prefix = "/"                      # catch-all: matches every request path

[[locations.paths]]
root = "/data/files"              # serve files from this directory

Key points:

  • Only bind and one location with one path are required to start FileHunter.
  • mode defaults to sequential, max_file_size defaults to 10MB, and all other server settings use their defaults.
  • No extensions filter means every file type in /data/files is eligible.
  • A request like GET /reports/q4.pdf will look for /data/files/reports/q4.pdf.

2. 🎨 Multi-Prefix by Content Type

Separate URL prefixes for images, documents, and videos, each with appropriate extension filters to prevent serving the wrong content type from a shared storage backend.

[server]
bind = "0.0.0.0:8080"
max_file_size = "50MB"            # global limit covers most content

# Image assets
[[locations]]
prefix = "/images"
mode = "sequential"

[[locations.paths]]
root = "/data/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg"]

# Office and text documents
[[locations]]
prefix = "/docs"
mode = "sequential"

[[locations.paths]]
root = "/data/documents"
extensions = ["pdf", "docx", "xlsx", "pptx", "txt", "csv"]

# Video files
[[locations]]
prefix = "/videos"
mode = "sequential"

[[locations.paths]]
root = "/data/videos"
extensions = ["mp4", "mkv", "avi", "mov", "webm"]

Key points:

  • Each prefix routes to its own directory and only allows matching file types.
  • Extension filters prevent accidental exposure of unrelated files (e.g., a .env file in the documents directory).
  • Extensions are specified without a leading dot: "jpg" not ".jpg".
  • Longest-prefix matching means /images/photo.png hits the /images location, not a / catch-all.

3. 🌐 NFS / Network Storage with Concurrent Mode

When files are spread across multiple NFS mount points with unpredictable latency, concurrent mode probes all roots in parallel and returns the first successful result.

[server]
bind = "0.0.0.0:8080"
connection_timeout = 600          # longer timeout for slow NFS operations

[[locations]]
prefix = "/shared"
mode = "concurrent"               # parallel I/O across all NFS mounts

[[locations.paths]]
root = "/mnt/nfs-server-a/files"

[[locations.paths]]
root = "/mnt/nfs-server-b/files"

[[locations.paths]]
root = "/mnt/nfs-server-c/files"

Key points:

  • concurrent mode spawns parallel probes to all three NFS mounts simultaneously.
  • The first root to respond with a valid file wins; remaining probes are cancelled.
  • This eliminates head-of-line blocking when one NFS server is slow or temporarily unreachable.
  • Increasing connection_timeout to 600 seconds accommodates slow network storage without dropping clients.

4. 🔄 Mirrored / Staged Storage

Use latest_modified mode to always serve the newest version of a file when the same filename exists across production, staging, and archive directories.

[server]
bind = "0.0.0.0:8080"

[[locations]]
prefix = "/assets"
mode = "latest_modified"          # always serve the newest version

[[locations.paths]]
root = "/data/production"

[[locations.paths]]
root = "/data/staging"

[[locations.paths]]
root = "/data/archive"

Key points:

  • latest_modified mode checks ALL roots and compares each file's modification timestamp (mtime).
  • The file with the most recent mtime is returned, regardless of which root it lives in.
  • Ideal for staged deployments: push a new version to /data/staging and it is served immediately without touching production.
  • Unlike concurrent mode, this always scans every root before responding (it needs to compare all candidates).

5. 📐 Per-Location File Size Limits

Apply different file size limits to different content types. Thumbnails get a strict cap, full-size images get more room, and videos are allowed to be very large.

[server]
bind = "0.0.0.0:8080"
max_file_size = "10MB"            # global default

# Thumbnails: strict size limit
[[locations]]
prefix = "/thumbnails"
max_file_size = "1MB"             # override: reject anything over 1MB

[[locations.paths]]
root = "/data/thumbnails"
extensions = ["jpg", "png", "webp"]

# Full-size images: generous limit
[[locations]]
prefix = "/images"
max_file_size = "20MB"            # override: allow up to 20MB

[[locations.paths]]
root = "/data/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"]

# Video files: large limit
[[locations]]
prefix = "/videos"
max_file_size = "500MB"           # override: large video files

[[locations.paths]]
root = "/data/videos"
extensions = ["mp4", "mkv", "webm", "avi"]

Key points:

  • The per-location max_file_size overrides the global [server] default for that specific prefix.
  • Files exceeding the limit are silently skipped during search (as if they do not exist).
  • Set max_file_size = 0 on a location to disable the size limit entirely for that prefix.
  • Size values accept human-readable strings like "1MB", "500MB", "2GB", or raw integers in bytes like 1048576.

6. 🔍 Fine-Grained Extension Filtering

Multiple paths under a single location can each carry their own extension filter. This lets you combine different content sources behind one URL prefix while controlling which file types are served from each directory.

[server]
bind = "0.0.0.0:8080"

[[locations]]
prefix = "/media"
mode = "sequential"

# First path: only web-safe image formats
[[locations.paths]]
root = "/data/web-images"
extensions = ["jpg", "png", "webp", "svg"]

# Second path: also allow legacy formats
[[locations.paths]]
root = "/data/legacy-images"
extensions = ["bmp", "tiff", "gif"]

# Third path: video content
[[locations.paths]]
root = "/data/video-clips"
extensions = ["mp4", "webm"]

# A separate location with no extension filter
[[locations]]
prefix = "/raw"

# No extensions specified — serve any file type
[[locations.paths]]
root = "/data/raw-assets"

Key points:

  • Extension filters are per-path, not per-location. Each [[locations.paths]] entry can restrict its own allowed types.
  • Omitting the extensions field entirely means all file types in that root are eligible.
  • In sequential mode, paths are probed in config order: /data/web-images first, then /data/legacy-images, then /data/video-clips.
  • This lets you prioritize certain directories or formats over others simply by ordering them in the config.

7. 🌐 CORS for Frontend Applications

Enable CORS to allow a frontend application on a different domain to fetch files directly from FileHunter. This example restricts access to two specific origins and enables credentials for authenticated requests.

[server]
bind = "0.0.0.0:8080"

[server.cors]
enabled = true
allow_origins = ["https://app.example.com", "https://admin.example.com"]
allow_methods = ["GET", "HEAD", "OPTIONS"]
allow_headers = ["Authorization", "Content-Type"]
expose_headers = ["Content-Length", "Content-Type"]
max_age = 3600                   # cache preflight for 1 hour
allow_credentials = true         # allow cookies / Authorization headers

[[locations]]
prefix = "/"

[[locations.paths]]
root = "/data/files"

Key points:

  • enabled = true activates CORS header injection on all responses. Without this, no CORS headers are added regardless of other settings.
  • Listing specific origins instead of "*" is required when allow_credentials = true (per the CORS specification).
  • allow_headers can be set to ["*"] to accept any request header, or restricted to specific headers like ["Authorization"] for tighter control.
  • max_age = 3600 tells browsers to cache the preflight response for 1 hour, reducing the number of OPTIONS requests.
  • Preflight (OPTIONS) requests are handled automatically by the CORS layer.

8. 🚦 Rate Limiting for Public APIs

Protect a public-facing FileHunter instance from abuse by limiting each client IP to a sustained rate of 20 requests per second with a burst allowance of 50.

[server]
bind = "0.0.0.0:8080"

[server.rate_limit]
enabled = true
requests_per_second = 20         # sustained rate per IP
burst_size = 50                  # allow short bursts
cleanup_interval = 300           # clean up expired entries every 5 minutes

[[locations]]
prefix = "/"

[[locations.paths]]
root = "/data/public"

Key points:

  • enabled = true activates per-IP rate limiting. Without this, all requests are allowed without throttling.
  • requests_per_second sets the long-term sustained rate. burst_size allows temporary spikes above this rate (e.g., a browser opening multiple image requests simultaneously).
  • When a client exceeds the limit, they receive a 429 Too Many Requests response with a Retry-After header indicating how long to wait.
  • cleanup_interval controls how often the background task removes expired entries. Lower values free memory faster but consume slightly more CPU.
  • Rate limiting is checked before all other request processing (method checks, file search), so throttled requests have minimal server impact.

9. 🏭 Production-Ready Full Configuration

A comprehensive configuration with every field explicitly set and annotated. Suitable as a starting template for production deployments.

[server]
bind = "0.0.0.0:8080"            # listen address
keepalive = true                  # HTTP/1.1 keep-alive
connection_timeout = 300          # seconds, 0 = unlimited
max_header_size = "16KB"          # request line + headers limit
max_headers = 100                 # max number of request headers
max_body_size = "1MB"             # Content-Length limit (413 if exceeded)
http2_max_streams = 256           # HTTP/2 concurrent streams per connection
max_file_size = "50MB"            # global file size limit (0 = unlimited)
stream_buffer_size = "128KB"      # response streaming chunk size

# CORS — allow CDN consumers
[server.cors]
enabled = true
allow_origins = ["*"]             # public CDN, allow any origin
allow_methods = ["GET", "HEAD"]
max_age = 86400

# Rate limiting — protect against abuse
[server.rate_limit]
enabled = true
requests_per_second = 50
burst_size = 100
cleanup_interval = 300

# Compression — compress text-based content for public clients
[server.compression]
enabled = true
algorithms = ["gzip", "br"]
min_size = "1KB"

# High-priority image serving
[[locations]]
prefix = "/cdn/images"
mode = "sequential"               # deterministic, config order priority
max_file_size = "20MB"            # override: images up to 20MB

[[locations.paths]]
root = "/data/primary/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif"]

[[locations.paths]]
root = "/data/backup/images"
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif"]

# Document serving from multiple sources
[[locations]]
prefix = "/cdn/docs"
mode = "concurrent"               # parallel search across storage
max_file_size = "100MB"           # allow large PDFs

[[locations.paths]]
root = "/data/primary/documents"
extensions = ["pdf", "docx", "xlsx", "pptx"]

[[locations.paths]]
root = "/data/secondary/documents"
extensions = ["pdf", "docx", "xlsx", "pptx"]

# Video with latest-modified for staged deployments
[[locations]]
prefix = "/cdn/videos"
mode = "latest_modified"          # always serve newest version
max_file_size = "2GB"             # large video files

[[locations.paths]]
root = "/data/production/videos"
extensions = ["mp4", "mkv", "webm"]

[[locations.paths]]
root = "/data/staging/videos"
extensions = ["mp4", "mkv", "webm"]

# Catch-all for everything else
[[locations]]
prefix = "/"
mode = "sequential"

[[locations.paths]]
root = "/data/general"

Key points:

  • All [server] fields are shown explicitly so nothing is left to implicit defaults.
  • Three different search modes are used across locations, each chosen for its use case: sequential for deterministic priority, concurrent for parallel multi-source search, and latest_modified for versioned content.
  • Per-location max_file_size overrides the global 50MB default where appropriate.
  • The "/" catch-all at the end handles any request that does not match a more specific prefix. Longest-prefix matching ensures /cdn/images/photo.jpg always hits /cdn/images, never /.
  • stream_buffer_size = "128KB" increases the default streaming chunk size from 64KB for better throughput on large files.

10. 🐳 Docker Deployment

A complete Docker deployment with a TOML configuration and matching docker-compose.yml. Container paths in the TOML must match the volume mount targets.

config.toml:

[server]
bind = "0.0.0.0:8080"            # bind inside the container
max_file_size = "50MB"
stream_buffer_size = "128KB"

[[locations]]
prefix = "/images"
mode = "sequential"

[[locations.paths]]
root = "/data/images"             # must match volume mount target
extensions = ["jpg", "jpeg", "png", "gif", "webp", "svg"]

[[locations]]
prefix = "/docs"
mode = "concurrent"

[[locations.paths]]
root = "/data/documents"          # must match volume mount target
extensions = ["pdf", "docx", "xlsx", "txt"]

[[locations]]
prefix = "/"

[[locations.paths]]
root = "/data/general"            # must match volume mount target

docker-compose.yml:

services:
  filehunter:
    image: filehunter:latest
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8080:8080"
    volumes:
      - ./config.toml:/etc/filehunter/config.toml:ro
      - /data/images:/data/images:ro
      - /data/documents:/data/documents:ro
      - /data/general:/data/general:ro
    restart: unless-stopped

Key points:

  • The config file is mounted read-only (:ro) at /etc/filehunter/config.toml, the default config path inside the container.
  • Each data directory on the host is mapped into the container at the same path referenced in the TOML root fields.
  • All volumes use :ro (read-only) since FileHunter only reads files, never writes.
  • restart: unless-stopped ensures the service recovers from crashes or host reboots.
  • To use a pre-built image, replace the build section with just image: your-registry/filehunter:tag.

11. 📦 Response Compression for Public Deployment

Enable response compression for a standalone public-facing deployment where FileHunter serves text-based files (logs, CSVs, JSON, XML) directly to clients over the internet without a reverse proxy.

[server]
bind = "0.0.0.0:8080"

[server.compression]
enabled = true
algorithms = ["gzip", "br", "zstd"]  # broad algorithm support
min_size = "1KB"                      # skip tiny files

[[locations]]
prefix = "/data"
mode = "sequential"

[[locations.paths]]
root = "/data/exports"
extensions = ["csv", "json", "xml", "txt", "log"]

Key points:

  • Compression is disabled by default — you must explicitly set enabled = true to activate it.
  • Best suited for text-based content served directly to public clients. Images, videos, and archives are already compressed and will be automatically skipped.
  • min_size = "1KB" avoids the overhead of compressing very small files where the savings are negligible.
  • If FileHunter runs behind a reverse proxy (Nginx, Caddy, etc.) that already handles compression, keep compression disabled to avoid double-encoding and unnecessary CPU usage.

12. 🔐 Basic Authentication for Internal Services

Protect an internal file server with HTTP Basic Authentication. Health check endpoints remain accessible without credentials for orchestration tools like Kubernetes.

[server]
bind = "0.0.0.0:8080"

[server.basic_auth]
enabled = true
username = "admin"
password = "s3cr3t"
realm = "internal-files"         # shown in browser login dialog

[[locations]]
prefix = "/"

[[locations.paths]]
root = "/data/internal"

Key points:

  • Basic Auth is disabled by default — you must explicitly set enabled = true to activate it.
  • Health check endpoints (/health and /ready) are exempt from authentication, ensuring Kubernetes probes and load balancer health checks continue to work.
  • Password comparison uses constant-time XOR-fold to prevent timing side-channel attacks.
  • The realm field controls the text shown in the browser's login dialog. Defaults to "filehunter" if omitted.
  • Both username and password must be non-empty when enabled — this is validated at startup.
  • Authentication is checked before rate limiting, so unauthenticated requests do not consume rate limit quota.
  • For production use, consider combining Basic Auth with TLS (e.g., via a reverse proxy) to protect credentials in transit.

Clone this wiki locally