A small, fast CLI tool to deploy a directory to an FTP server by tracking file hashes and uploading only changed files.
- Uses a local tracking store (.ftp/files.json) that stores SHA-256 hashes of files.
- Scans the project tree in parallel, computes hashes, and uploads only changed (or forced) files.
- Creates missing directories on the remote FTP server as needed.
- Supports pre-deploy hooks and a simple JSON-based config/credentials format.
- Fast file collection with parallel directory walk (num_cpus for worker count)
- SHA-256 based change detection to only upload modified/new files
- Progress bar for uploads
- Automatic creation of remote directories (walks path components and mkdir/cwd)
- .ftpignore support (default file created with .ftp/)
- Pre-deploy hook execution (shell on Unix, cmd on Windows)
- Simple JSON configuration and credentials stored in the project directory
Prerequisites
- Rust toolchain (cargo + rustc)
- An FTP server and account
Build and run locally
# clone
git clone https://github.com/yourname/ftp-deploy.git
cd ftp-deploy
# build
cargo build --release
# run
./target/release/ftp-deploy --help(Optional) Install to your cargo bin
cargo install --path .Initializes the current directory (or given path) with default configuration, credentials and tracking files.
Usage:
ftp-deploy init
# or initialize a specific path
ftp-deploy init --path /path/to/projectWhat it creates:
- ftp-deploy.json — project config (hooks)
- ftp-deploy-creds.json — credentials and remote base path
- .ftp/. (tracking directory)
- .ftp/files.json — generated by the first run to track file hashes
- .ftpignore — default contains ".ftp/"
Compute file hashes, compare with tracked hashes and upload updated files.
Usage:
ftp-deploy deploy
# options:
# --path, -p Directory to operate on (defaults to .)
# --jobs, -j Number of threads for file walk (defaults to number of CPUs)
# --force, -f Force upload of all files even if hashes match
ftp-deploy deploy --path /path/to/project --jobs 8List all files considered/tracked by the collector (honors .ftpignore).
Usage:
ftp-deploy files
# list files under a different path:
ftp-deploy files --path /path/to/project-
ftp-deploy.json (FtpConfig)
- Fields:
- hooks: array of shell commands to run prior to deployment
- Example:
{ "hooks": ["npm run build", "cargo build --release"] }
Hooks are run via:
sh -c "<hook>"on Unixcmd /C "<hook>"on Windows
- Fields:
-
ftp-deploy-creds.json (FtpCreds)
- Fields:
- server: FTP server address (host:port)
- base_path: remote base directory (uploaded files are joined on this path)
- username
- password
- Example:
{ "server": "ftp.example.com:21", "base_path": "/www/my-site", "username": "deploy", "password": "hunter2" }
- Fields:
-
.ftpignore
- Default created with a single entry: ".ftp/"
- Acts like .gitignore for the collector (the project uses ignore::WalkBuilder)
-
.ftp/files.json (FilesTracking)
- Stores a JSON map of local Path -> SHA-256 hex digest for previously-deployed files.
- Created/updated automatically by the tool.
- Walks the filesystem using ignore::WalkBuilder and respects custom ignore file (.ftpignore).
- Uses SHA-256 (sha2 crate) to compute file hashes.
- Scans in parallel with configurable number of threads (num_cpus default).
- Builds a list of changed/added files compared to .ftp/files.json, unless --force is used.
- Connects using the ftp crate and logs in with credentials from ftp-deploy-creds.json.
- For each file to upload:
- Ensures remote directory exists by walking path components and calling mkdir/cwd (cwd_or_create_recursive).
- Calls ftp.put(file_name, reader) to upload the file.
- Shows an indicatif progress bar during upload.
- File path handling: The current implementation uploads files to creds.base_path joined with the local file path. Be mindful of how local paths map to remote directories.
- The upload loop currently always attempts mkdir/cwd for each file; an optimization could be to sort files and reduce redundant mkdir/cwd operations (commented TODO in code).
- Credentials are stored in plaintext JSON inside the project directory. Make sure file permissions are appropriate for your security needs.
Initialize a project and deploy:
# initialize current directory with default files
ftp-deploy init
# edit ftp-deploy-creds.json to add your server/username/password/base_path
# optionally edit ftp-deploy.json hooks
# perform a deploy
ftp-deploy deployForce upload every file (ignore tracking):
ftp-deploy deploy --forceSpecify a custom jobs count for hashing pass:
ftp-deploy deploy --jobs 12This repository is provided under the MIT License by default. Change as necessary for your project. See LICENSE file.
NOTE: This README file was generated using generative AI.