Skip to content
dentifragPublic

About

A self-hosted web UI for rclone: browse remotes and pull files onto your server.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

138 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Siphon

CI License: MIT

A self-hosted web UI for rclone. Browse a remote server from any browser (laptop or phone) and download files straight onto your home server's drives, with fast parallel transfers. Siphon is a small, password-protected front end; rclone does the heavy lifting.

Quick start

Docker

docker compose up -d --build
# then open http://<server-ip>:8080

Files download into ./downloads; saved connections live in ./data. rclone is bundled in the image, so there's nothing else to install.

Single-file app (Windows / macOS / Linux)

  1. Download the binary for your OS from the Releases page (siphon-win-x64.exe, siphon-macos-arm64, siphon-macos-x64, or siphon-linux-x64).
  2. Run it once. It creates a config.json next to itself.
  3. Open http://localhost:8080 (or http://<pc-ip>:8080 from another device) and finish first-run setup in the browser.

On macOS/Linux, make it executable first: chmod +x siphon-*. The binaries are unsigned, so Windows SmartScreen or macOS Gatekeeper may warn once. rclone is fetched automatically on first run if it isn't already installed.

Configuration

Set these as environment variables (Docker) or as keys in config.json (single-file app). Environment variables win if both are set.

Setting config.json key Default What it does
APP_USERNAME appUsername admin when auth is enabled Login username. Change this from admin.
APP_PASSWORD appPassword none Plaintext password for login.
APP_PASSWORD_HASH appPasswordHash none Scrypt password hash from --hash-password. Takes precedence over APP_PASSWORD.
LOGIN_MAX_ATTEMPTS loginMaxAttempts 10 Failed login attempts before lockout. Set 0 to disable lockout.
LOGIN_LOCKOUT_MINUTES loginLockoutMinutes 15 Lockout duration after too many failed attempts.
SESSION_TTL_HOURS sessionTtlHours 72 Session lifetime before re-login is required.
TRUST_PROXY trustProxy false Trust reverse-proxy headers for client IP and protocol.
SECURE_COOKIES secureCookies auto Cookie security mode: auto, true, false.
DOWNLOAD_DIRS downloadDirs /downloads Where files can be saved. Label=path pairs, comma-separated.
PORT port 8080 Web UI port.
DATA_DIR dataDir ./data Where saved connections are stored.

DOWNLOAD_DIRS powers the in-app folder picker. List each drive or folder you want to save into, for example Movies=/mnt/movies,Backup=/mnt/backup. In Docker, mount each of those paths as a volume. The same folders scope which local files can be uploaded, since uploads read from the server's local disk into the remote folder you are browsing.

SFTP uploads run single-stream per file (rclone can't multi-thread a single-file SFTP upload). Overall upload throughput across multiple files comes from the Concurrent setting in the transfer queue.

The Segments setting is saved with each download when it is queued. Changing Concurrent does not replace queued or running transfers' segment requests; changing Segments applies only to newly queued downloads. Configured streams can be fewer than requested because each chunk is at least 5 MiB: a 20 MiB file requesting eight segments is configured for four. Requested and configured segment counts are not measurements of rclone's actual runtime streams.

Folder downloads validate the remote tree and create the destination folder structure before queuing each file as its own transfer. Nested paths and empty subfolders inside a non-empty folder are preserved. Concurrent limits how many files transfer at once, while Segments controls the stream limit within each file. Small files can use fewer streams; the queue displays the stream limit, not a measured active-stream count.

Successfully completed transfers show a full green bar and 100%, including empty files and files within folder downloads whose sizes or byte statistics are unavailable. The byte counts still reflect the reported metadata; completion does not invent missing bytes.

Download destination collisions

Downloads preserve existing files unless you explicitly choose Overwrite. An individual file offers Overwrite / Cancel; a folder or multi-selection also offers Skip this file / Cancel batch. Directories can merge without deleting unrelated content, but a file blocking a required folder, a directory in place of a file, or a symlink is never converted destructively. All choices are resolved before any item in the batch is queued or any destination folder is created. Skipped items are reported as skipped, not as empty folders or completed downloads. There is no saved overwrite-all preference, Keep both, or Resume. An admission is limited to 10,000 files, with at most 10,000 entries per expanded folder. Each recursive folder listing has a 35-second deadline (including response-body reading) and a 32 MiB response limit enforced while streaming. Oversized or stalled listings fail before admission; no destination folders or transfers are created.

The preview is valid for five minutes and is bound to its original source connection. Changing that connection before admission invalidates a normal download preview. Retries instead retain the failed transfer's original source binding and ask for fresh collision authorization. Ambiguous or overlapping file destinations in one batch are rejected.

Each file transfers into a unique private .siphon-download-* directory beside its final destination. The final destination stays reserved until publication finishes. New files are published with an exclusive hard link, never a clobbering rename. Existing-file approvals include file identity, timestamps, size, and a content hash; targets are checked again at admission, dispatch, and publication. A file appearing or changing while a download waits or transfers produces a visible conflict instead of silently overwriting it. Overwrite always downloads fresh bytes, including when existing size and modification time match the source. Reading existing files for these checks can make large-file previews and overwrites slower.

An approved overwrite moves the previous file into the private directory, verifies the displaced object, then exclusively publishes the new copy. On a conflict or cancellation, Siphon attempts non-destructive restoration; a new external file is never replaced during that rollback. The displaced object is retained even after success, because an external process might still write through an already-open handle. Transfers and the server log show its recovery path (.siphon-download-*/previous). Inspect and remove that recovery content manually only after confirming no old writer still uses it; it consumes space until removed. If automatic restoration is impossible, the error and recovery path identify the preserved object rather than hiding it.

Publication requires a filesystem supporting exclusive hard links (for example, NTFS). Unsupported filesystems fail safely; Siphon never substitutes an overwriting rename. An overwrite has a short interval during which the final name is absent, not a portable compare-and-swap replacement. Once exclusive publication succeeds, completion wins over a late cancellation; Siphon cannot safely undo that name after another process can use it. The guarantees concern file-content preservation and cooperative external writers, not hostile concurrent replacement of ancestor directories, mounts, or Siphon's private staging directories. Path confinement and protected-path checks remain enforced, but portable Node filesystem APIs cannot eliminate those adversarial directory races. A crash can leave staging or recovery directories; there is no automatic startup deletion or byte-level recovery.

Confirmed finished or canceled writers have their expendable staging removed. Uncertain writers keep their staging and final-destination quarantine, even if their queue rows are removed. Pending previews, publication, queued transfers, and uncertain download writers block in-app update installation. Do not delete uncertain staging or restart to clear quarantine until the old rclone backend is confirmed stopped.

API clients use POST /api/download/plan with remotePaths, downloadDir, segments, and connectionRevision, then POST /api/download/commit with the returned planId and an entryId-keyed decisions map (overwrite or, for batches, skip). Commit returns { transfers, skipped, empty }. Identical commits return the same admission while the plan is retained; different decisions cannot replay it. POST /api/download/plan/:id/release cancels/releases a preview, and POST /api/download/:id/retry-plan previews a failed transfer against its original source. Legacy direct download/retry endpoints remain no-overwrite and return 409 DESTINATION_COLLISION for existing targets. Collision, busy, changed-destination, expired-plan, and changed-connection errors have distinct code fields.

First-run setup

On first launch with no configured credentials (no APP_PASSWORD / APP_PASSWORD_HASH environment variables and no appPassword / appPasswordHash in config.json), Siphon starts in setup mode. Operational APIs are blocked until setup is completed. The minimal public GET /api/health probe returns only the app name and bundled version.

Open the app in a browser and choose one of these:

  • Create an admin username and password.
  • Run without a password (open mode), only for trusted networks.

Setup mode should be completed before exposing Siphon beyond loopback or a trusted LAN.

If APP_PASSWORD / APP_PASSWORD_HASH or appPassword / appPasswordHash is configured, those credentials override stored auth state and setup is skipped.

Features

  • Connect over SFTP with a password or private key.
  • Browse remote folders with current-folder filtering, sorting, multi-select, and right-click download.
  • Adjust Name, Size, and Modified columns with pointer dragging, keyboard controls, or numeric inputs in Columns, and reveal full names with Wrap filenames.
  • Fast parallel downloads (1 to 16 streams) with live progress and speed.
  • Upload files and folders that already exist on the Siphon server's local disk into the remote folder you are browsing.
  • Downloads keep running on the server even if you close the tab or your phone.
  • Save connections so you don't retype credentials. Siphon remembers the last download folder you selected.

Retry failed downloads

Retry appears only for eligible downloads whose writer has finished with a failure, or whose destination validation/publication failed with no writer still running. It starts a new attempt with a new transfer ID and job, not a byte-level resume. The old row keeps its failure diagnostics and shows Retried. Each failed row can create only one successor; repeating the request returns that same successor. If the successor also fails, it can become eligible for its own Retry.

Retries use the original connection snapshot, source path, local destination, and per-item segment request. Switching or disconnecting the browsing connection, or changing or deleting a saved profile, does not redirect the retry or adopt new credentials. A missing source, private key, or connection configuration remains an error. Source size is checked again; the content at the same source path may have changed.

Downloads to the same canonical final local file run one at a time, including retries. A queued row shows "Waiting for another download to this file." Other destinations and uploads still share the Concurrent capacity.

If a submission's outcome is unknown or cancellation has not been confirmed, that target stays blocked for the lifetime of the running server's download manager. Existing queued and newly requested downloads to it show an explicit reason. Not every error is retryable; canceled transfers and uploads cannot Retry. Remove and Clear finished discard the removed rows' diagnostics and retry options, but do not clear uncertain-target protection.

Clear all, reconnecting, closing the browser, waiting for a timer, or a stop request returning does not prove the backend stopped or clear that protection. Before restarting Siphon to unblock a target, confirm that its old supervised rclone backend has actually terminated. A full server restart empties the in-memory queue and forgets target protection; an app restart alone is not proof that old writers stopped. Orphaned or external writers are outside this protection.

Retries are manual: there is no automatic retry, backoff, Retry all, persistent history, or polling-based recovery. Retry uses the same collision decisions and staged publication as other downloads; it does not reuse an old overwrite approval or resume partial bytes.

Browser columns

Name defaults to filling the remaining table width, with sensible default widths for Size and Modified. Drag a column's trailing edge, focus its resize handle and use Left/Right arrows (Shift for larger steps, Home/End for minimum/maximum), or open Columns to enter numeric widths. Wrap filenames reveals full filenames instead of truncating them.

Setting a Name width pins it to a fixed pixel width; enlarged columns may intentionally require horizontal scrolling. Auto-fill Name column returns Name to filling the remaining space. Reset column widths restores auto-fill Name and default metadata widths without changing your Wrap filenames setting.

Widths survive folder navigation and reconnects while the browser component remains mounted. A page reload restores defaults; widths are not saved to persistent storage. At viewport widths <=1024px, the existing stacked, wrapped layout remains and desktop column controls are hidden. On wider screens with coarse-pointer input, filenames wrap by default.

Saved sites and shared connections

Editing a saved site's connection fields or deselecting it creates a manual connection. Re-enter the password before connecting or saving it. An unchanged selected site still uses its saved credentials.

The active connection is shared across tabs. If another tab changes it, a stale tab shows a connection conflict instead of transferring to the wrong server. Select the desired server and reconnect, then refresh the folder and select the intended files again before starting the transfer. Queued transfers and transfers that already acquired their connection keep running against their original server. Reloading the page reattaches the current shared connection; confirm the server before transferring.

Keep it running on boot

  • Docker: already handled (restart: unless-stopped in docker-compose.yml).
  • Windows: run npm run service:install from an Administrator PowerShell (or right-click windows-service/Install-SiphonService.ps1 > "Run with PowerShell"). It wraps the packaged dist-bin/siphon-win-x64.exe in WinSW as an auto-start service that restarts on failure and rolls its logs - no system Node.js needed at runtime. For a first-time build of the exe, add -Build. See windows-service/ for details.
  • Linux: point a systemd unit at the binary with Restart=always.

In-app Windows service updates

The Updates header button can check and install the latest published stable release for an existing Windows service. Checking is explicit; opening the dialog does not contact GitHub or install anything. Docker, development processes, standalone launches, and other operating systems are not updated.

First, complete the one-time administrator setup. An existing executable is not rebuilt by the installer's -Build switch. Rebuild with npm run package:build and npm run package:win, then rerun the service installer, or run one last update.ps1 followed by the service installer.

Sign in with a password, finish or cancel queued/running transfers, choose Check for updates, then confirm Install and restart. A protected independent SYSTEM task downloads the pinned release while Siphon serves, verifies size and SHA-256, replaces only the existing executable, restarts the service, and verifies its exact version. If startup fails, it restores and verifies the old executable. Data, settings, and rclone are preserved.

The browser reconnects without resubmitting an install. You may need to sign in again after the service restarts. Closing the dialog does not cancel an update. A reconnect deadline reports an unknown outcome, not a proven failure; Retry status resumes reading the result. Retry accepted task retries only the already-pinned request after the helper verifies its task is idle. Failed recovery keeps transfers blocked and preserves the backup for administrator recovery.

Updates require the Windows protocol-v1 manifest published alongside future releases. Numeric and v-prefixed stable tags are supported; prereleases and snapshot tags are not. The existing 1.0.2 release can report current without a manifest, but a newer release without compatible metadata cannot be installed. A future incompatible helper protocol requires another explicit administrator setup. The installation directory is excluded from the app's folder and transfer APIs.

Authentication and security

  • Username + password login is supported. The default username is admin when auth is enabled, change it with APP_USERNAME or appUsername.
  • You can configure the password as plaintext (APP_PASSWORD or appPassword) or as a scrypt hash (APP_PASSWORD_HASH or appPasswordHash). Generate hashes with:
./siphon --hash-password

Hashed passwords take precedence over plaintext values.

  • Login lockout is enabled by default, loginMaxAttempts=10 and loginLockoutMinutes=15. Set loginMaxAttempts=0 to disable lockout.
  • Sessions expire automatically, sessionTtlHours=72 by default.
  • For HTTPS behind VPN or reverse proxy, set trustProxy=true and secureCookies=true.
  • Password changes are available in-app for store-managed credentials. Env-managed credentials are read-only and skip the setup wizard.
  • Forgot your password, if a hash is configured (APP_PASSWORD_HASH or appPasswordHash), replace or remove that hash first (or regenerate it with --hash-password) because hash settings take precedence over plaintext. Then set a new password or hash and restart Siphon.
  • Siphon runs rclone privately on localhost with random credentials, browsers only talk to Siphon's API, never to rclone directly.
  • Saved passwords are stored by rclone (obscured in its config), not by Siphon. Downloads are restricted to folders you configure.
  • This version does not verify SFTP host keys, run it on a network you trust.

Breaking change: passwordless upgrades now enter setup mode

Existing installs that previously ran with no password and no env credentials now start in setup mode and block operational APIs until setup is completed. This is intentional and fail-closed.

Offline recovery:

  1. Stop Siphon.
  2. Delete auth.json from dataDir.
    • macOS default: ~/Library/Application Support/Siphon/auth.json
    • Linux default: ~/.local/share/siphon/auth.json
    • Windows default: %APPDATA%\Siphon\auth.json
  3. Start Siphon again and complete setup.

Deleting auth.json is also how you leave open mode.

Development

Use Node.js ^22.22.2, ^24.15.0, or >=26.0.0 on Windows, macOS, or Linux. From your checkout or worktree:

npm install
npm run dev

Open http://127.0.0.1:5174 when the ready message appears. This starts the real UI with hot reload, the API with watch reload on 127.0.0.1:8081, and a local SFTP server on 127.0.0.1:2022. No Docker, separate SSH server, or live service is needed. rclone is reused from RCLONE_PATH or PATH when available; otherwise it is downloaded into the sandbox on first run, which requires internet access. Automatic extraction uses PowerShell on Windows and unzip on macOS/Linux.

Finish the normal first-run setup in the browser. In the connection form, use SFTP and the host, port, username, and generated password printed in the terminal. These are local SFTP credentials, separate from your app login. Browse the sample remote, download Sample folder to try nested files, spaces, and an empty folder, or upload Upload sample.txt from the local file picker. Changes to the UI and API reload without deploying the live service.

All dev state stays in .siphon-dev in the current checkout:

  • data/: app login, saved connections, and the API's rclone config.
  • remote/: files served by the local SFTP server.
  • downloads/: the confined download destination and files available for upload.
  • credentials.json: randomly generated local SFTP credentials, preserved on restart.
  • config.json, rclone.conf, authorized_keys, and cache/: local configuration and caches.

Inherited production app credentials, auth settings, paths, and rclone settings are not used; RCLONE_PATH is retained only as a binary override. Loopback binding and local data/download paths are enforced even if you edit the dev config. You may deliberately connect to a real remote, but local downloads and upload sources remain confined to the dev downloads folder. Sample files and config are created only when missing, so edits and saved app state survive restarts. A damaged credentials file fails explicitly instead of silently changing its password.

Press Ctrl+C to stop the UI, API watcher, and owned rclone processes. To reset everything, stop the dev instance first, then delete only .siphon-dev in that checkout and run npm run dev again. Do not delete the normal data/ or downloads/ directories.

For concurrent worktrees or occupied ports, choose three distinct ports from 1 to 65535. The launcher checks all three before writing files or starting services, and does not silently choose another port.

DEV_API_PORT=8082 DEV_UI_PORT=5175 DEV_SFTP_PORT=2023 npm run dev

In PowerShell:

$env:DEV_API_PORT = '8082'
$env:DEV_UI_PORT = '5175'
$env:DEV_SFTP_PORT = '2023'
npm run dev

The terminal prints the selected UI URL and SFTP connection details.

Other development commands:

npm test             # run the tests
npm run typecheck    # type-check server + web
npm run lint         # lint with oxlint
npm run format       # format with Prettier

For manual control, npm run web:server runs the API on :8080 and npm run web:dev runs the UI at http://127.0.0.1:5174 in a second terminal. Those commands use normal inherited settings and app paths, not the isolated dev sandbox, and do not start a sample SFTP server. The UI proxy targets 127.0.0.1:8080; use npm run dev for isolated development.

To build the single-file binaries yourself: npm run package:all (outputs to dist-bin/).

Project layout

src/
  server/        Fastify API: auth, config, sessions, request handling
    routes/      HTTP route handlers (auth, browse, downloads, uploads, ...)
    rclone/      rclone process supervisor, client, and download manager
  ui/            React UI built with Primer (components + hooks in lib/)
  web/           Vite entry point that mounts the UI
  shared/        Types and the API contract shared between server and UI
test/            Vitest unit tests
scripts/         Isolated full-stack development launcher and sample setup
windows-service/ WinSW-based Windows service install/uninstall (PowerShell)
Dockerfile       Container image (bundles rclone)
docker-compose.yml

Contributing

Contributions are welcome. Please read CONTRIBUTING.md for setup, coding style, and the checks CI runs. By participating you agree to the Code of Conduct.

  • Found a security issue? Follow the security policy and report it privately.
  • Curious what changed between releases? See the changelog.

License

Siphon is released under the MIT License.

About

A self-hosted web UI for rclone: browse remotes and pull files onto your server.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages