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. The backend is built on axum + tokio + aws-sdk-s3, with one StorageBackend trait abstracting over every supported backend; a React SPA is bundled in, so opening http://<host>:<port>/ lets you walk directories, lazy-load thumbnails, and preview files in place. 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. If you omits3.bucketto use multi-bucket mode (see below), the credentials must additionally holds3:ListAllMyBucketsso the root listing can enumerate every visible bucket. 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=&skip_pages=— browse a directory; optionalskip_pagesmakes the server walk N pages internally and return the target page plus every intermediate token, so jumping to page N takes one round-trip instead of NGET /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 (lands in ~/.cargo/bin/):
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", = "...", = "..." }
s3.regiondefaults tous-east-1, which is fine for MinIO / LocalStack and AWS us-east-1 buckets — leave it out by default. Only set it when: (1) the target AWS bucket lives outside us-east-1, since SigV4 has to use the bucket's actual region (otherwise AWS returnsAuthorizationHeaderMalformed); or (2) the S3-compatible gateway validates the region strictly (most don't).
s3.bucket is optional. Omit it (or set it to "*") to enable multi-bucket
mode: the storage root performs ListBuckets, and every bucket the
credentials can see appears as a top-level directory; navigating into one
drills down with the usual prefix listing. The credentials must hold the
s3:ListAllMyBuckets IAM permission. Example:
[[]]
= "all-prod-s3"
= "s3"
= { = "http://minio.local:9000", = "...", = "..." }
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". - 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 out of scope — put nginx / caddy in front for 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 — no external file required.
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.