Skip to content

usm serve

Full-featured directory server — uploads, drag-and-drop UI, range requests, on-the-fly zip / tar.gz, QR codes, basic auth — built on miniserve (a single Rust binary). usm auto-installs the binary on first run, locally or on a remote SSH host. No system packages, no sudo.

usm serve ./reports                       # serve the directory locally
usm serve ~/data -p 8000 --bind 0.0.0.0   # expose on the LAN
usm serve ~/data --no-upload              # read-only
usm serve ~/data -a alice:wonderland -q   # basic auth + terminal QR code

usm serve user@host:/srv/logs             # spawn miniserve on the remote
usm serve user@host:~/models -p 8080      # ssh -L brings it to localhost
usm serve ~/data --tunnel u@bastion       # push: serve locally, expose on bastion

What it does

  1. Resolves a binary: prefers ~/.cache/usm/bin/miniserve (the usm-managed install), then $PATH, otherwise downloads the appropriate pinned release into ~/.cache/usm/bin/miniserve (chmod +x). On the remote, same logic — miniserve lives under ~/.cache/usm/bin/miniserve and is downloaded via curl, wget, or python3 urllib (whichever the host has).
  2. Spawns miniserve:
  3. Local source — directly, on --bind:--port.
  4. Remote source (user@host:/path) — over ssh -L LPORT:127.0.0.1:RPORT so the server stays on the remote's 127.0.0.1 and you get it on your laptop.
  5. Forwards Ctrl-C to the whole tree (server + ssh) for clean shutdown.

Defaults

Option Default Why
--upload / --no-upload upload Uploads (with mkdir + overwrite) are on; pass --no-upload for read-only.
--delete off Deletion is destructive — opt-in only.
--hidden / --no-hidden hidden Dotfiles are listed by default (it's almost always what you want when sharing your own files).
--bind 127.0.0.1 LAN exposure is explicit (--bind 0.0.0.0).
--auth none Set with USER:PASS; see miniserve docs for hashed form.
archives on Three download buttons (.tar, .tar.gz, .zip) at the top of every folder page; pass --no-archive to disable.
--upgrade (-U) — Force re-download of the miniserve binary on this run.
--ssh-timeout 30 Seconds per remote setup command, including connection/authentication. Binary installation allows at least 180 seconds.

SSH setup timeouts

A path/platform probe timed out error happens before miniserve starts. The timeout covers connection, authentication, remote shell startup, and the path/platform command; it does not identify which stage stalled. Increase the budget for a slow host or jump connection:

usm serve li2:~ --ssh-timeout 90

Setup commands use non-interactive SSH (BatchMode=yes): key/agent authentication must already work. They disable TTY allocation, configured RemoteCommand, and inherited port forwards, while still using the host alias, user, identity, and proxy settings in your SSH config. The serving session also disables TTY allocation and RemoteCommand, but keeps its requested -L tunnel.

On timeout, serve includes any captured SSH stderr and a matching ssh -vvv diagnostic command. Run that command in the same terminal/environment as usm. If it also stalls, inspect the last verbose messages for proxy, authentication, or remote shell startup problems; increasing a timeout will not fix a blocked login or shell startup script.

Local vs. remote — picking the right command

usm share usm serve
Engine stdlib http.server miniserve (Rust binary)
Extra install none ~2.3 MB binary, auto-downloaded
Range / resume no yes
Upload no yes (drag-drop UI)
Directory zip / tar.gz no yes
Browser UI basic listing dark-themed, sortable, searchable
Remote runtime python3 / uv self-contained binary

Use share for the absolute minimal-dependency case (and when you just want to push something to a teammate's box). Use serve for serious browsing / large files / uploads / "give me the whole folder".

Security notes

  • Default bind is 127.0.0.1; you have to be explicit to expose on the LAN.
  • Uploads default to on because that's the whole point of running this instead of usm share. If you're serving over a network you don't trust, add --auth USER:PASS (or pass --no-upload).
  • Remote (user@host:/path) mode binds miniserve to the remote's 127.0.0.1 only; the ssh tunnel does the rest. No remote port is exposed publicly.

Source

scripts/serve.py