Install
cargo install faucet-cli --features sink-sftp faucet init --sink sftp
# Cargo.toml — use it as a library cargo add faucet-sink-sftp
Starter config
sink:
type: sftp
config:
host: "" # REQUIRED — Server hostname or IP address.
# port: 22 # Server port (default: 22).
username: "" # REQUIRED — SSH username.
# known_hosts: { type: strict } # one of: strict, accept_new, insecure
# --- Alternative variants — replace the block above with one of these ---
# type: accept_new
#
# type: insecure
# connect_timeout_secs: 30 # Seconds the TCP connect, SSH handshake and authentication together may take, and then each SFTP channel's start (default 30).
# keepalive_interval_secs: 15 # Seconds between SSH keepalives on a quiet connection (default 15); the connection is dropped after three go unanswered.
path: "" # REQUIRED — Remote directory the files are written in.
# file_name: "" # File-name template inside `path` — the file sink's `path` file name (#777).
# if_exists: null # What happens when a file this run writes already exists (`if_exists`).
# write_mode: null # Pipeline-level write mode (`write_mode`).
# compression: null # User-facing compression config.
# format: null # On-the-wire format of files written by the SFTP sink (#604).
# file_extension: ".jsonl" # File extension for written objects (default: `.jsonl`).
# batch_size: 1000 # Records per file when `max_records_per_file` is not set (without `file_name`).
# concurrency: 4 # Maximum number of file uploads in flight over the SSH session (default 4): a page that rolls into several files uploads them concurrently while the next file is encoded.
# max_records_per_file: 0 # Maximum records per file before rolling to a new one (#618).
# max_bytes_per_file: 0 # Maximum **bytes** per file before rolling to a new one (#618).
# csv: { ... } # CSV dialect.
# excel: { ... } # Excel worksheet selection.
# xml: { ... } # XML record framing.
# avro: { ... } # Avro read/write options.
# parquet: { ... } # Parquet write options.
# json_lines: { ... } # JSON Lines write options.
# encryption: null # Encrypt files at rest (#777; the `encryption` feature).
# scratch_dir: "" # Directory the sink builds each file in before uploading it (default: the system temporary directory).
# max_listed_entries: 1000000 # Entries one directory listing may scan (default 1,000,000).
Capabilities
| Delivery | At-least-once |
|---|---|
| Write modes | append, overwrite |
| Exactly-once writes | No |
| Schema evolution | No |
| Scoped cleanup | No |
| Staged bulk load | No |
| Run rollback | No |
| Compression | Yes |
Config fields
| Field | Type | Default | Description |
|---|---|---|---|
hostrequired | string | — | Server hostname or IP address. |
usernamerequired | string | — | SSH username. |
pathrequired | string | — | Remote directory the files are written in. |
port | integer | 22 | Server port (default: 22). |
known_hosts | object | mode: accept_new | Host-key verification policy (default: accept_new). |
connect_timeout_secs | integer | 30 | Seconds the TCP connect, SSH handshake and authentication together may take, and then each SFTP channel's start (default 30). A server that accepts and then stalls fails the run instead of hanging it. |
keepalive_interval_secs | integer | 15 | Seconds between SSH keepalives on a quiet connection (default 15); the connection is dropped after three go unanswered. 0 disables them. |
file_name | string | — | File-name template inside path — the file sink's path file name (#777). May contain {part} (numbered files) and ${now.*} tokens; a trailing / is a subdirectory of part-{part}<extension> files. When set, file_extension is not used. Unset: every run writes new files named <run id>-<part><file_extension> (<run id> is a fresh time-ordered UUID, <part> is 00001, 00002, …). |
if_exists | object | replace | What to do when a file of the same name already exists (if_exists): replace it (default), append to it (JSON Lines, CSV, raw text) or fail with error (#777). Needs file_name. mode is accepted as another name for this key, and overwrite / error_if_exists for its values. |
write_mode | object | append | append (default) or overwrite: write the run's files into a hidden swap area and move them into place only after a successful run, then remove files of an earlier run that match file_name and were not rewritten (#777). The move is one file at a time, so a reader listing the destination while it runs can see new files beside old ones; a move that was interrupted is finished by the next run. Needs file_name. |
compression | string | object | auto | Compression codec (default auto: from the file name's extension, .gz / .zst). The compression feature (#777). |
format | object | json_lines | File format (default json_lines) (#604). |
file_extension | string | .jsonl | File extension for written objects (default: .jsonl). |
batch_size | integer | 1000 | Records per file when max_records_per_file is not set (without file_name). Records accumulate across write_batch calls and a file closes once it holds this many records, or at flush. batch_size = 0 is the "no re-chunking" sentinel: no record cap, so JSON Lines and the whole-file formats write one file per flush, and Parquet one file per write_batch call. Ignored when file_name is set. Defaults to DEFAULT_BATCH_SIZE. |
concurrency | integer | 4 | Maximum number of file uploads in flight over the SSH session (default 4): a page that rolls into several files uploads them concurrently while the next file is encoded. flush returns only after every upload has landed. |
max_records_per_file | integer | — | Maximum records per file before rolling to a new one (#618). None removes the record cap; the sink accumulates across write_batch calls, so a small upstream page no longer means a small file. |
max_bytes_per_file | integer | — | Maximum **bytes** per file before rolling to a new one (#618). |
csv | object | delimiter: ","has_headers: truequote: "on_unknown_field: widen | CSV dialect, used when format: csv (#604). |
excel | object | header_row: 0 | Worksheet name, used when format: xlsx (#604). |
xml | object | record_element: recordroot_element: records | Record framing, used when format: xml (#604). |
avro | object | codec: null | Writer schema and block codec, used when format: avro (#719). |
parquet | object | row_group_size: 1048576 | Parquet writer options, used when format: parquet (#777): compression (default zstd), row_group_size, explicit schema. |
json_lines | object | pretty: false | JSON Lines writer options (pretty), used when format: json_lines. |
encryption | object | — | Encrypt files at rest (#777; the encryption feature). Scratch files are not encrypted while the run is in progress; those holding plaintext are kept in a private directory. |
scratch_dir | string | — | Directory the sink builds each file in before uploading it (default: the system temporary directory). JSON Lines and raw text need scratch space for the whole file; every other format needs room for each file being built (up to concurrency of them while uploads are in flight). A private subdirectory is created in it for each sink. |
max_listed_entries | integer | 1000000 | Entries one directory listing may scan (default 1,000,000). The sink lists its output directory, for example to remove files of an earlier run or finish an interrupted overwrite; every entry the server sends counts, and a directory with more fails the call with an error naming this field. Must be at least 1. |
Get started
Your first pipeline runs in five minutes.
Install the CLI, scaffold a config, and move real data — nothing external to stand up.
curl -LsSf https://github.com/faucet-hq/faucet-stream/releases/latest/download/faucet-cli-installer.sh | sh brew install faucet-hq/faucet-stream/faucet-cli