Fare Drive is a Python CLI for turning one machine into a personal file host and connecting to it from another machine as a client. It supports local storage management, authenticated uploads and downloads, token-based access, share links, trash, storage limits, Cloudflare tunnel launching, and a small terminal dashboard called faretop.
server: hosts the drive, keeps metadata, and serves the HTTP APIclient: logs in and transfers files to and from a Fare Drive server
- Username/password login
- Access-token login without username/password
- Upload files and directories
- Download files and directories
- Share links for file downloads
- Delete to trash
- Storage limit enforcement
- Client sync listing
- Auto-sync pull mode with
--outputand--watch-seconds - Cloudflare tunnel process launcher
faretopterminal status dashboard- Persistent client config in
~/.config/fare-drive/config.json
- src/fare_drive/cli.py: CLI entrypoint
- src/fare_drive/server.py: server commands and HTTP API
- src/fare_drive/client.py: client commands and TUI
- src/fare_drive/auth.py: password hashing and signed tokens
- src/fare_drive/config.py: local client config persistence
- tests/test_integration.py: integration coverage for the main feature set
This repo is set up to run well from a Python 3.12+ environment. If you are using Conda, activate your fare-drive environment first.
python -m pip install -e .You can also invoke the CLI directly through the module:
python -m fare_drive.cli --helpfare-drive server init \
--root /tmp/fare-drive-server \
--username demo \
--password demo-pass \
--storage-limit-gb 5This creates:
/tmp/fare-drive-server/data/tmp/fare-drive-server/trash/tmp/fare-drive-server/.fare-drive/server.json/tmp/fare-drive-server/.fare-drive/sessions.json/tmp/fare-drive-server/.fare-drive/runtime.json
fare-drive server run \
--root /tmp/fare-drive-server \
--host 127.0.0.1 \
--port 8876 \
--public-url http://127.0.0.1:8876fare-drive client login \
--endpoint http://127.0.0.1:8876 \
--username demo \
--password demo-passfare-drive client put ./my-file.txt --remote-path docs/my-file.txtfare-drive client get docs/my-file.txt --output ./downloadsInitialize a new drive root.
fare-drive server init --root <PATH> --username <USER> --password <PASS> [--storage-limit-gb 10]Run the API server.
fare-drive server run --root <PATH> [--host 0.0.0.0] [--port 8765] [--public-url https://your-url]By default, server run now also tries to start cloudflared automatically. It uses:
FARE_DRIVE_SERVER_LOCAL_URLorhttp://<host>:<port>for quick tunnels- the returned Cloudflare public URL to populate server runtime state automatically
TUNNEL_API_TOKENorCLOUDFLARE_TOKENfor named tunnel auth only when you also provideFARE_DRIVE_SERVER_PUBLIC_URLor--public-url
If you want the HTTP server without starting Cloudflare, use:
fare-drive server run --root <PATH> --no-start-tunnelPrint local server metadata.
fare-drive server status --root <PATH>Open a server-side terminal dashboard directly on the host machine.
fare-drive server faretop --root <PATH>It shows:
- drive root
- drive status
- storage usage
- trash size and trash item count
- public URL
- cloudflared pid
- uptime
Press q to exit.
Create a signed endpoint token for client login without a password.
fare-drive server issue-access-token --root <PATH> [--endpoint <PUBLIC_URL>] [--expires-in 3600]If --endpoint is omitted, Fare Drive will use the public_url stored in the server runtime state.
Start cloudflared in the background and persist the pid plus the public URL that Cloudflare exposes.
fare-drive server start-tunnel \
--root <PATH> \
--local-url http://127.0.0.1:8765 \
[--cloudflared-bin cloudflared] \
[--token-env TUNNEL_API_TOKEN]Notes:
- If
.envexists in the current working directory, Fare Drive will load it. TUNNEL_API_TOKENis supported directly.- The sample
.envin this repo already usesTUNNEL_API_TOKEN. - If no tunnel token is available, Fare Drive starts a quick tunnel with
cloudflared tunnel --url .... - Fare Drive watches the
cloudflaredlog output and stores the public URL that Cloudflare returns.
The server can read its key settings from a .env file in the current working directory. CLI flags still win, but any omitted server-side values can come from environment variables.
Supported keys:
FARE_DRIVE_SERVER_ROOTFARE_DRIVE_SERVER_USERNAMEFARE_DRIVE_SERVER_PASSWORDFARE_DRIVE_SERVER_STORAGE_LIMIT_GBFARE_DRIVE_SERVER_HOSTFARE_DRIVE_SERVER_PORTFARE_DRIVE_SERVER_PUBLIC_URLFARE_DRIVE_SERVER_LOCAL_URLFARE_DRIVE_CLOUDFLARED_BINFARE_DRIVE_TUNNEL_TOKEN_ENVFARE_DRIVE_CLOUDFLARED_WAIT_SECONDSFARE_DRIVE_ACCESS_TOKEN_EXPIRES_INTUNNEL_API_TOKENCLOUDFLARE_TOKEN
Example:
FARE_DRIVE_SERVER_ROOT=/tmp/fare-drive-server
FARE_DRIVE_SERVER_USERNAME=demo
FARE_DRIVE_SERVER_PASSWORD=demo-pass
FARE_DRIVE_SERVER_STORAGE_LIMIT_GB=5
FARE_DRIVE_SERVER_HOST=127.0.0.1
FARE_DRIVE_SERVER_PORT=8876
FARE_DRIVE_SERVER_PUBLIC_URL=
TUNNEL_API_TOKEN=your-cloudflare-tunnel-tokenIf FARE_DRIVE_SERVER_LOCAL_URL is omitted, Fare Drive generates it automatically from FARE_DRIVE_SERVER_HOST and FARE_DRIVE_SERVER_PORT.
fare-drive client login --endpoint <URL> --username <USER> [--password <PASS>]fare-drive client login-token --access-token <TOKEN>Upload a file or directory.
fare-drive client put ./local-file.txt --remote-path files/local-file.txt
fare-drive client put ./local-folder --remote-path folders/local-folderDownload a file or directory.
fare-drive client get files/local-file.txt --output ./downloads
fare-drive client get folders/local-folder --output ./downloadsMove a file or directory into the server trash.
fare-drive client delete files/local-file.txtGenerate a signed share token and a ready-to-open share URL.
fare-drive client share files/local-file.txt --expires-in 1800Example response:
{
"share_token": "...",
"share_url": "https://your-public-url/s/..."
}Show live server status through the authenticated API.
fare-drive client statusList remote metadata:
fare-drive client syncMirror remote content into a local directory:
fare-drive client sync --output ./mirrorRepeat periodically for a simple auto-sync pull loop:
fare-drive client sync --output ./mirror --watch-seconds 30Open the terminal dashboard.
fare-drive client faretopIt displays:
- drive status
- storage usage
- upload and download byte counts
- connected clients
- uptime
- current endpoint
Press q to exit.
Client config is stored at:
~/.config/fare-drive/config.json
Stored fields:
endpointusernamebearer_tokenaccess_token
Share links are download-only and path-scoped. A share token can fetch only the file it was created for. It cannot upload, delete, create new shares, or inspect sync/status metadata.
- All server files live under
<root>/data - Deleted files move into
<root>/trash - Storage usage is measured against the configured byte limit
- Archive uploads are checked against uncompressed tar member sizes before extraction
- Passwords are stored as salted SHA-256 hashes
- Access tokens and share tokens are HMAC-signed
- Tar extraction is path-validated and uses safe extraction filtering
- Relative-path traversal such as
..is rejected
Run the integration suite from the local fare-drive environment:
python -m unittest -v tests.test_integrationThe current suite verifies:
- server initialization
- server status
- username/password login
- endpoint-token login
- file upload
- directory upload
- file download
- directory download
- sync listing
- sync pull mode
- share link generation and download
- delete to trash
- storage-limit rejection
- tunnel launcher state update
faretopstartup and exit
This repository now contains a working MVP with integration coverage for the main server and client workflows. It is a strong base for the next round of improvements, especially resumable large-file transfer, richer sync conflict handling, restore-from-trash commands, and a production-grade named Cloudflare tunnel setup.