Host Files¶
What this is¶
A file manager on each SSH host (confined SFTP): browse, upload/download (progress, default 512 MiB), create folders, rename, move, edit UTF-8 text, zip / unzip, chmod / chown, search (names and file contents), preview images / hex, delete files or folder trees, and a thin Docker helper (named volumes + docker cp into the current folder). It is not WinSCP, not dual-pane, not console zmodem, not a backup job, and not the compose editor (no version history, no deploy). The token API stays fleet list/get/put (+ limited mkdir/rename); extra verbs are UI + 2FA. The hero says Limited access (fleet) or Elevated access (privileged) — not “jailed SFTP”.
Where: host overview Files button (next to Console) → /servers/{id}/files. Same ops hero as Docker / Backups / Services (host jump, ★ pin, Server / Docker / Hosts map). Kill switch PIHERDER_HOST_FILES (default off). Operator+ only on a real herder. The public demo shows a simulated tree (no SFTP); viewers may browse; writes are refused.
Use the normal PiHerder header (Dashboard / Servers / ☰) to leave the page — Files always receives the logged-in user so that chrome renders. Folder tree on the left, file list on the right — both scroll inside the window (the page does not grow with the folder). The path box is a breadcrumb: one leading / (privileged) or the fleet jail, then folder names; separators are green (not a doubled //). Toolbar: parent, path, search, Upload, ⋯ (New folder, Docker mounts, Refresh, Fleet/Privileged, Maximize), and the expand control (same idea as console Maximize / Hosts map full screen). Maximize hides the hero so the list fills the remaining height — especially useful on a phone. Restore brings the hero back. The choice sticks in this browser. Those ⋯ actions are not extra toolbar buttons. Selection actions appear only after you select rows. Tap or click the name to open a folder, edit text, or preview an image. On a phone, Folders slides the tree over the list; long-press selects and shows actions. Desktop: right-click for the row menu (no per-row ⋯). Drag files or folders onto the list (or a tree folder) to upload. Switch Fleet / Privileged from the toolbar ⋯.
The herder keeps one SFTP session per host/identity for ~75 seconds idle so folder clicks are not a new SSH handshake each time. Transfers use 1 MiB buffers on a dedicated SFTP connection (browse stays on the pooled session). Do not prefetch/pipeline whole files — that stalled around ~12 MiB. Caddy must not gzip application/octet-stream and uses flush_interval -1 so the browser download bar can move. Upload progress is two-phase: send to PiHerder, then write on the host. Traffic still goes browser → herder → host (not a raw LAN scp).
End-to-end: drop a sidecar¶
- Set
PIHERDER_HOST_FILES=truein.env/ compose and restart web. - Open a host → Files (operator). Jail is
docker_base_dirwhen Docker is on, else that user’s home. - Click folders in the tree, or tap the folder name in the list. Upload → Files… or Folder…, or drop
config.yml(confirm if the name exists). Drop a mix of files and folders on the list. - Tap
config.ymlto edit — same monospace gutter / wrap / Tab indent as the compose editor. Ctrl/Cmd+S saves (512 KiB UTF-8 cap). - Optional: toolbar ⋯ → Privileged (same elevate role as console) + Passkey (TOTP fallback) — for paths outside the fleet jail (HAOS
/mnt/data, Docker volume_data). - Check Audit for
host_file_put(path, bytes, sha256 — never the body).
Why it exists¶
The web console is a PTY. Dropping a Frigate config.yml, a compose sidecar, or pulling a large log still meant scp / FileZilla. Files is that transfer, inside PiHerder, using the same SSH identities as the rest of the product.
When to use it¶
| Use Files | Use something else |
|---|---|
| Sidecar / YAML / log sitting next to a stack | Compose editor for docker-compose.yml / .env tabs with history + deploy. .env is redacted there until step-up. |
| One-off upload/download (default 512 MiB; Settings can raise to 32 GiB) | Backups for scheduled trees |
| Zip a folder (save on the host or download) | — |
| Peek at a PNG / hex of a binary | Full media gallery (not in Files) |
| Copy a file out of a container into the jail | Toolbar ⋯ → Docker mounts / docker cp on this page. Whole-stack host→host copy is Move a service |
HAOS /mnt/data via privileged (⋯) | Cert deploy (PEM paste) · template deploy |
Actions¶
| Action | How |
|---|---|
| Edit | Tap/click the name, or Edit. Overlay: line numbers, YAML-ish colours, Wrap, Tab indent, Ctrl/Cmd+S. Binary / larger than 512 KiB opens Preview instead. Close warns if unsaved. Privileged save of a root-owned file uses sudo -n tee. If that fails, the editor shows why (need NOPASSWD sudo, or connect as root) — not a raw PermissionError after Close. |
| Preview | Images in-page (8 MiB). Other binaries: hex/ASCII peek + Download. ‹ › (or arrow keys) step through files in this folder. Next/previous shows a loading overlay until the image bytes arrive (the peek is fast; the picture is a separate SFTP pull). |
| Zip | Select rows → Zip. The archive is built on the host (zip or python3 — the tree does not go through PiHerder). Optional Also download a copy. With download, Remove the zip from the host after download (originals stay). Needs zip or python3 on the machine. Caps: 2000 files, depth 24. |
| Extract | Select a .zip or tap it. Extracts into the current folder. .. / absolute members refused (zip-slip). Existing names replaced after confirm. |
| Delete | Selection (files and folders). Folders go with their contents. Jail root cannot be deleted. |
| Permissions | Selection → Permissions. Octal + rwx checkboxes; owner/group names (pi, www-data) or numeric ids. Listing shows names when getent works. Recursive option for folders. Jail root cannot be changed. Fleet may chmod files it owns; chown and chmod of files you do not own need privileged. If that SSH user is not root: sudo -n chmod / sudo -n chown (no password prompt — add NOPASSWD, or use a root privileged identity). HAOS root often has no sudo — plain chmod/chown is tried too. |
| Search | Toolbar box or Ctrl/Cmd+F, then Enter or Search (it does not search on each keystroke). Tick In files to grep UTF-8 text (512 KiB/file, 80 content hits). Secret-ish files skipped in content search until 2FA unlock. Caps: 200 name hits, 2000 scanned. |
| Move | Select → Move (or drag onto a tree folder). Same jail, SFTP rename. Cannot move a folder into itself. Existing files confirm replace. |
| Upload | Upload → Files… or Folder…. Drop mixed files and folders on the list (browsers cannot pick both in one system dialog). Folder upload creates parents on the host (mkdir -p). .. refused. Cap 2000 files. |
| Secrets | .env, *.pem, and key files still list. Open / edit / download / preview / content-search needs the same 2FA grant as privileged Files (Passkey preferred). Operators can unlock on fleet without being allowed to elevate. Prefer the compose editor when you need redaction. |
| Docker | ⋯ → Docker mounts… Pick a container. You see its named volumes and bind mounts (host path → container path). Browse opens that host path in this same file manager (edit/zip/search all work). Named volumes are usually /var/lib/docker/volumes/<name>/_data and need privileged. Bind mounts under docker_base_dir work on fleet. Optional docker cp copies a path that is not mounted into the current folder. |
| Select | Click the row (not the name). Checkbox, Shift-click, Ctrl/Cmd-click, header checkbox, Ctrl/Cmd+A. Phone: long-press selects and opens actions. |
| Maximize | Toolbar expand control (or ⋯ → Maximize). Hides the hero so the list fills the remaining height — same idea as console Maximize / Hosts map full screen. Fleet nav stays. Restore brings the hero back. Remembered in this browser. |
Identities (Fleet / Privileged)¶
Default fleet (least-priv). Switch from toolbar ⋯. Optional privileged — same Settings who may elevate as the console. Step-up is Passkey first when you have one enrolled; authenticator TOTP is the fallback unless Settings requires passkey. An existing console grant cookie also unlocks privileged Files and secret-ish files. Jobs stay on fleet. API is fleet only.
| Identity | Hero | Where you can go |
|---|---|---|
| Fleet | Limited access | docker_base_dir when Docker is on, else that user’s home (HAOS often /root). Never /. .ssh and OS trees (/etc, /proc, …) blocked. |
| Privileged | Elevated access | Almost the whole host (/ minus /proc /sys /dev /run). The privileged key’s OS rights are the real ACL (root ⇒ almost anything). |
HAOS is in. SSH add-on SFTP works where that user can write. Fleet home may be too tight for /mnt/data — use privileged.
Overwrite and delete¶
Upload onto an existing name: browser confirm, then tmp+rename (same as compose writes). Delete always confirms; folders are recursive. Unzip confirms. Download-and-remove-zip confirms (originals stay).
API¶
Token scope files (not on by default). Fleet identity only. List / download / upload / mkdir / rename / delete-empty. Edit, zip, unzip, chmod, recursive delete, preview, Docker helpers, and privileged stay in the browser (tokens have no 2FA). A richer Files API is under consideration for a later minor. See API tokens.
Env¶
| Variable | Default | Notes |
|---|---|---|
PIHERDER_HOST_FILES | false | Master enable. Compose injects this. Not a Settings checkbox. |
PIHERDER_HOST_FILES_MAX_BYTES | — | Optional lock. When unset, Settings → General → Files sets the cap (default 512 MiB, ceiling 32 GiB). Also caps unzipped size and preview-adjacent transfers. Do not put a default in compose. |
Large uploads stream through the herder (O(chunk) RAM) with an upload progress bar, then “Writing to host…”. If you front PiHerder with nginx, raise client_max_body_size and timeouts for multi-gigabyte copies. Caddy in the bundle has no small body cap.
Stale-data Cleanup does not purge Files (there is no Files table). Audit rows (host_file_*) follow normal Audit retention.
Audit¶
| Action | Details (never the file body) |
|---|---|
host_file_list | identity, directory, entry count |
host_file_get / host_file_put | identity, path, bytes, sha256, overwrite on put; zip download/save, unzip, docker cp, and text save count as get/put |
host_file_mkdir / rename / delete | identity, paths; recursive delete includes file/dir counts; move is rename |
host_file_chmod | identity, names count, mode, owner, group, recursive, changed, whether sudo was used |
Failures store status=error and a reason code (secret_confirm when 2FA is required for a secret-ish file).
Related¶
- Web SSH console — privileged is console and Files
- Compose edit — redacted
.env/ history / deploy - Docker overview
- Move a service — not the Files copy engine
- Public demo — canned Files tree, no SFTP
- HAOS hosts
- API tokens
- Settings — kill switch is env; transfer cap is Settings → Files