# filebrowser-ng A self-hosted web file browser. One static binary, one SQLite file, no external services. ![Screenshot of the file browser](docs/screenshot.png) ## Features - **Browsing**: grid and list view, breadcrumbs, sizes and dates, multi-select with shift and ctrl, context menu. - **File operations**: upload files and folders, create folders and files, rename, move, copy, delete. Download a folder as `zip`, `tar`, `tar.gz`, or `tar.zst`, streamed without a temporary file. - **Preview**: images, video, audio, and PDF inline. Video and audio support range requests, so seeking works. - **Thumbnails**: images and videos show a small preview in grid view. - **Text editor**: CodeMirror 6 with syntax highlighting for common languages. Saves back to disk in writable folders. - **Search**: by name, by content, or both. Results stream in as they are found and the search can be stopped at any time. Hidden and gitignored files are included. - **Users and folders**: each user gets one or more root folders, each read-write or read-only. Admins manage users, and can deactivate an account without deleting it. - **Shares**: public links to a file or folder, with an optional expiry. A writable share gives the link holder the same operations as a read-write folder. An admin setting turns writable shares on or off globally, and a read-only folder cannot be shared writable. Deleting, renaming, or moving an item revokes its shares. See [Shares](#shares). - **WebDAV**: mount your folders, or a share, in a file manager. See [WebDAV](#webdav). - **UI**: light, dark, or system theme. English, German, and French. Optional single-click open. - **Security**: Argon2 password hashes, HttpOnly session cookies with a 30-day lifetime, growing delay on failed logins per user name. ## Setup The first visit shows a setup page that creates the admin account. The admin's root is the `--root` folder. Further users and folders are created under **Users**. ### Container ```bash docker compose up -d # or: podman compose up -d ``` `compose.yml` builds the image and mounts two host folders: | Host path | Container path | Content | |--------------------|-----------------------|-------------------------------| | `./data` | `/data` | The browsed files | | `./filebrowser-db` | `/var/lib/filebrowser` | SQLite database: users, shares, settings | Both folders are created on first start. The image is Alpine plus the static binary and exposes port 8080. ### Binary ```bash filebrowser-ng --root /srv/files --db /var/lib/filebrowser/db.sqlite --bind 0.0.0.0 ``` | Flag | Default | Description | |---------------|-------------|------------------------------------------------------------------| | `--root` | _(required)_ | Folder the server may access. All user folders are inside it. | | `--db` | _(required)_ | SQLite file. Created when missing. | | `--port` | `8080` | Listen port | | `--bind` | `127.0.0.1` | Listen address. `0.0.0.0` exposes the server beyond localhost. | | `--https` | off | Set when behind a TLS-terminating proxy. Marks the cookie `Secure`. Also `FILEBROWSER_HTTPS=true`. | | `--root-name` | folder name | Display name of the root folder. Also `FILEBROWSER_ROOT_NAME`. | | `--cache` | off | Folder for the thumbnail cache. Turns thumbnails on. Also `FILEBROWSER_CACHE`. | Log level comes from `RUST_LOG` (`error`, `warn`, `info`, `debug`, `trace`). ### Reverse proxy The server speaks plain HTTP. Put a TLS-terminating proxy in front of it and pass `--https`, or set `FILEBROWSER_HTTPS=true` in `compose.yml`. Without the flag the session cookie is sent over plain HTTP as well. Search uses server-sent events, so the proxy must not buffer responses on `/api/search`. ## Thumbnails Grid view shows a small preview of an image or video instead of an icon. Point `--cache` at a folder to turn them on: ```bash filebrowser-ng --root /srv/files --db /var/lib/filebrowser/db.sqlite \ --cache /var/cache/filebrowser ``` Notes: - Videos need `ffmpeg` on the server. The container image ships with it. Without it, images still get thumbnails and videos keep their icon. - The cache is disposable. You can delete the folder at any time; the server makes the thumbnails again as they are needed. ## Shares A share is a link to a **path**, not to the account that made it. Things that end one: deleting, renaming or moving the item through the web UI (this also revokes the shares on anything inside it), setting an expiry, deleting the share under **Shares**, and deleting the creator's account. Two admin actions do **not** end a share, so do them together with a revoke: - **Deactivating an account.** Its logins and sessions stop immediately, but a link that user made keeps serving — and a writable link keeps accepting writes from whoever holds it. Delete the account, or the share, to close it. - **Taking a folder away from a user.** A share stays open on a folder its creator can no longer open. Nor does a change the server did not make: a file moved or replaced over SSH, or by any other process, keeps the shares that name its path — the server only sees the operations it performs itself. Under **Shares**, a signed-in user sees the links they made. An admin sees every link on the server, in one section per account, and can end any of them — including the links of an account that can no longer sign in, whose section is marked as deactivated. That listing is admin-only because it is a list of working credentials: every row's copy button produces the live link. Holding a share token is access, so treat that view (and the tokens copied from it) accordingly. ## WebDAV Two mount points. Your permissions are the same as in the web UI. | URL | Contents | Credentials | |-----|----------|-------------| | `https://host/dav` | Every root folder the user has, one collection each | Account name and password | | `https://host/dav-share/` | One public share | None, or the share password | Mount it with the file manager you already have: ```bash # Linux (davfs2) sudo mount -t davfs https://host/dav /mnt/files # Linux (GNOME/KDE), macOS Finder: Go → Connect to Server davs://host/dav ``` Notes: - Under `/dav` each of your folders is one entry, named as it is in the web UI. Two folders with the same name get a number added. - A share of a single file cannot be mounted. Share a folder or open it in the web UI instead. - A browser opens the same URLs. A folder shows a plain index, a file downloads. Hidden files are left out of that index but a mount still shows them. - If another client has a file locked, your write waits or fails. An abandoned lock clears after ten minutes, and a server restart clears all of them. - If two people save the same file at once, the last save wins. ## Development Requirements: Rust 1.90 or newer with the `wasm32-unknown-unknown` target, [trunk](https://trunk.rs) 0.21, [bun](https://bun.sh), and [just](https://github.com/casey/just). ```bash rustup target add wasm32-unknown-unknown cargo install trunk ``` | Command | Effect | |------------------|------------------------------------------------------------------------| | `just dev` | Backend on :8081 with a dev root under `.dev/` | | `just dev-web` | Frontend on :8080 with hot reload, proxying `/api` to :8081 | | `just build` | Release binary with the frontend embedded, `target/release/filebrowser-ng` | | `just run` | Build and run the release binary against the dev folders | | `just test` | Server and API type tests | | `just e2e` | Build the real frontend and run the server suite against the embedded build | | `just lint` | `cargo fmt --check` and clippy with warnings denied | | `just reset-db` | Delete the dev database | ### Layout | Crate | Content | |-------------|------------------------------------------------------------------| | `server` | axum HTTP server, SQLite via rusqlite, file and archive handling, embedded frontend | | `web` | Leptos client-side app compiled to WebAssembly | | `api-types` | Request and response types plus route constants shared by both | The frontend build lands in `web/dist`, is copied to `server/dist`, and is compiled into the binary with the `embedded` feature. `web/cm6.js` is the vendored CodeMirror bundle, built by bun from `web/cm/wrapper.js`. Both are build artifacts and not committed. Tests live in `server/tests` and run against an in-process server with a temporary root. CI runs on Hearthforge, see `.hearthforge-ci.toml`. ## License [AGPL-3.0-or-later](LICENSE.md).