OmniStream
中文 · English
A single-binary, streaming file browser and previewer — point it at any local directory or S3-compatible object storage (MinIO / OSS / Ceph / R2 / …) and it instantly exposes them as a browsable, previewable HTTP service. Open it in a browser to walk directories, view images, stream video, and read code — no client to install, no extra frontend or reverse proxy to wire up. The backend is built on axum + tokio + aws-sdk-s3, with one StorageBackend trait abstracting over every supported backend.
A ready-to-use React SPA is bundled into the binary. Once started, opening http://<host>:<port>/ in a browser lets you walk directories, lazy-load thumbnails, and preview files in place — no extra static-file server or object browser required. Preview supports:
- Images — png / jpg / gif / webp / avif / bmp / svg / ico
- Video — mp4 / webm / mov / mkv / m4v / ogv, with
Range-based seeking - Text / code — syntax highlighting by extension: json / yaml / toml / md / rs / ts / py / go / sql / shell / proto, and many more
- Anything else — generic fallback: icon + metadata + the browser's built-in viewer
Previewing files on S3 / S3-compatible storage requires the configured access key to hold both
s3:GetObject(preview / download / HEAD) ands3:ListBucket(directory browsing / thumbnail listing). Missing either yields a 403 on the corresponding action. The local filesystem backend has no such requirement, but is restricted to the directory configured aslocal.root_path.
HTTP API (the bundled SPA is built on top of these — curl or your own client works just as well):
GET /api/list?prefix=&page_token=— browse a directoryGET /api/stat/{*key}— fetch file metadataGET /api/proxy/{*key}— stream the file, transparently forwardingRange, returning 200 / 206 as appropriate- Embedded SPA fallback — anything not under
/api/*falls back toindex.html, so client-side routing just works
1. Install
Recommended: install via cargo. The binary lands in ~/.cargo/bin/ (already on $PATH), so you can invoke it directly as omni-stream:
Or download a pre-built binary from GitHub Releases: https://github.com/maoXyzt/omni-stream/releases/latest.
Three targets are published — x86_64-unknown-linux-gnu / x86_64-unknown-linux-musl /
aarch64-apple-darwin. (Windows users can build from source). For pre-built binaries,
extract, mark omni-stream executable, and put it on $PATH if you like.
Building from source, hacking on the frontend / backend, or contributing? See docs/development_guide.md. The release process lives in docs/how_to_release.md.
2. Configuration
config.toml lookup order (first hit wins):
$OMNI_CONFIG(absolute path, highest priority)$XDG_CONFIG_HOME/omni-stream/config.tomldirectories::ProjectDirsplatform default (macOS:~/Library/Application Support/omni-stream/; Linux:~/.config/omni-stream/)./config.toml(current directory)
config.example.toml in the repo root works as a template. A minimal config:
[]
= "127.0.0.1"
= 8080
[[]]
= "local-data"
= "local"
= true
= { = "/var/lib/omni-stream" }
Or S3 / S3-compatible (MinIO / OSS):
[[]]
= "production-s3"
= "s3"
= true
= { = "http://minio.local:9000", = "data", = "...", = "...", = "us-east-1" }
Multiple
[[storages]]entries can coexist; on startup the one withactive = truewins, and if none is active the first entry is used. The frontend also lets you switch between them at runtime.
Environment overrides (prefix OMNI_, separator _):
| Variable | Effect |
|---|---|
OMNI_SERVER_HOST |
overrides server.host |
OMNI_SERVER_PORT |
overrides server.port |
OMNI_AUTH_ENABLED |
overrides auth.enabled (true / false) |
OMNI_AUTH_TOKEN |
overrides auth.token (recommended for keeping the secret out of the config file) |
OMNI_CONFIG |
force a specific absolute config.toml path |
RUST_LOG |
tracing filter, e.g. info,tower_http=debug,aws=info |
Authentication (optional)
By default /api/* is open — only suitable for trusted LAN environments. To enable Bearer token auth, add to your config:
[]
= true
= "any-long-random-string"
Or rely entirely on environment variables (keep the secret out of the config file):
OMNI_AUTH_ENABLED=true OMNI_AUTH_TOKEN=
Once enabled:
- All
/api/*requests must carryAuthorization: Bearer <token>, otherwise the server returns401plusWWW-Authenticate: Bearer realm="omni-stream". - Token comparison uses constant-time byte equality, so length / content differences cannot leak via timing.
- The embedded SPA (
/,/assets/*) stays open — the browser has to load the page first before the user can enter a token. The first API call gets a 401, the SPA pops up a token input, stores it inlocalStorage, and attaches it to subsequent requests. - TLS is intentionally out of scope for this process. To expose it on an untrusted network, put nginx / caddy / Cloudflare in front and let them handle HTTPS.
Config CLI
If you'd rather not copy config.example.toml by hand, the binary ships three config subcommands:
# List every candidate location in priority order, and mark which one the
# loader will pick.
# Lay down the bundled config.example.toml at one of the candidate paths
# (interactive selection, with a "custom path" option).
# Parse + validate. Surfaces missing fields, wrong types, or an empty
# `storages` list. Without an argument it checks the active path; pass a
# path to validate it directly.
The template config init writes is the verbatim config.example.toml from
the repo, embedded into the binary at compile time via include_str! — no
external file required.
Subcommand output uses ANSI colours and emoji icons to keep interactive
terminals scannable, but follows the NO_COLOR
convention — NO_COLOR=1 or piping to a file / pipe automatically falls
back to plain ASCII. Set FORCE_COLOR=1 to force colour on inside scripts.
3. Run
If you installed via cargo install, omni-stream is already on $PATH:
# Use the config.toml found by the §2 lookup order
# Or point at a specific one
OMNI_CONFIG=/etc/omni-stream/config.toml
# Or override just the port
OMNI_SERVER_PORT=8081
# Turn on request logging while debugging
RUST_LOG=info,tower_http=debug
Tarballs from GitHub Releases don't add themselves to $PATH, so either run ./omni-stream from the extracted directory or move it somewhere like /usr/local/bin/.
After startup, opening http://<host>:<port>/ in a browser lands you on the embedded SPA. Ctrl-C / SIGTERM triggers a graceful shutdown (axum::serve + with_graceful_shutdown).
4. HTTP Error Semantics
| Trigger | HTTP | AppError |
|---|---|---|
| Auth enabled and token missing / wrong | 401 | (middleware — bypasses AppError) |
| File not found | 404 | NotFound |
| Credential lacks GetObject / S3 AccessDenied | 403 | Forbidden |
| Out-of-range / malformed Range | 416 | InvalidRange |
Path contains .. or other escape attempts / requesting a directory as a file |
400 | InvalidPath / Unsupported |
| Other I/O / SDK / network errors | 500 | Io / Backend |
Response bodies are uniformly {"error": "...", "message": "..."} JSON.