Run the same command on many remote hosts concurrently over SSH (with optional non-interactive sudo), stream per-host output, and emit a final summary that is easy to read and to scrape from logs.
curl -fsSL https://raw.githubusercontent.com/kevin197011/kkfly/main/install.sh | bash- Linux: the installer writes to
/usr/local/binand expects root (usesudo). - macOS: install can run without root if your environment allows writing to the install prefix (often you still use
sudofor/usr/local/bin). - Releases: github.com/kevin197011/kkfly/releases
go build -o kkfly ./cmd/kkfly| Flag | Meaning |
|---|---|
-config PATH |
YAML config file (default: kkfly.yml in the working directory). |
-json |
Write a JSON report to kkfly.json (implies no kkfly.log). |
-json-out PATH |
Write JSON to this path (-json not required). |
-version, -v |
Print version and exit. |
Exit status
0only when every host run succeeds.- Non-zero if any host fails, config is invalid, or writing the JSON report fails.
First run
If the config path does not exist, kkfly writes a kkfly.yml template, prints its path, and exits — edit hosts and command, then run again.
During a run:
- Terminal: timestamped rows (
TIME HOST STAGE MESSAGE), withDONElines showing[completed/total]for rough progress while hosts finish out of order. CMDpreview: multi-linecommandin YAML is summarized (not flattened blindly into one unreadable token).SUMMARY: includesoverall=(success,partial_failure, orfailure) plus counts and wall-clock duration.
Logs
Unless you pass -json or -json-out, a plain-text kkfly.log is written in the current directory (same content as the terminal stream, without ANSI color). If the log file cannot be opened, the run continues on stdout only (a warning is printed).
Machine-friendly footer (KKFLY_COLLECT)
After the human-readable tables, the runner prints a plain block between:
<<< KKFLY_COLLECT (plain lines for logs/scripts)>>> END KKFLY_COLLECT
It includes:
- One key/value summary line (
overall,hosts_total,hosts_ok,hosts_failed,duration,wall_seconds). - Optional
failed_hosts_tsv=(tab-separated hostnames). - A TSV table between
RESULT_TSV_BEGIN/RESULT_TSV_ENDwith columns:HOST,STATUS,EXIT,DURATION_MS,ERROR.
Use this block for log pipelines, alerts, or dashboards without parsing ANSI.
Color
Set NO_COLOR or KKFLY_NO_COLOR to force plain output on a tty.
With -json or -json-out, the report file includes:
- Top-level
overall,hosts_total,hosts_ok,hosts_failed,failed_hosts(in addition toresults). - Per host:
host,status,exit_code,started,finished,duration,error, and capturedstdout/stderrwhen present.
Example:
./kkfly --json
jq -e '.overall == "success"' kkfly.json >/dev/nullCopy and edit:
kkfly.yml(default config file), orconfigs/kkfly.ymlas a template.
Required fields
userhosts(non-empty)concurrency(>= 1)command- One of:
private_key_pathorprivate_key_content
Optional (common)
port(default22if omitted or0)sudo, timeouts, output caps, host-key policy — see table below.
Example (kkfly.yml):
user: ubuntu
private_key_path: ~/.ssh/id_ed25519
port: 22
concurrency: 20
sudo: true
connect_timeout_seconds: 10
command_timeout_seconds: 900
max_output_bytes_per_stream: 262144
strict_host_key_checking: false
command: |
echo hello
hosts:
- 1.2.3.4
- example.comFields:
user(required): SSH username.private_key_path(required unlessprivate_key_contentis set): Path to the SSH private key file (relative paths are resolved from the config file directory).private_key_content(optional): Inline private key content (takes precedence overprivate_key_path).port(optional, default22): SSH port.hosts(required): List of hosts (IP or hostname).concurrency(required,>= 1): Max number of hosts to run in parallel (capped at host count internally).command(required): Shell command to run remotely. Runs viabash -lcon the remote side.sudo(optional, defaultfalse): Iftrue, runs the remote command viasudo -n bash -lc ...(non-interactive). Often combined with requesting a PTY forsudo; see sudo section below.connect_timeout_seconds(optional, default10): SSH connect timeout.command_timeout_seconds(optional, default900): Per-host command deadline (applied as a client-side timeout context). Unused / non-positive values default to900(15 minutes).max_output_bytes_per_stream(optional, default262144): Max bytes captured per stream (stdout/stderr) per host; extra output is truncated.known_hosts_path(optional): Path to aknown_hostsfile used when strict host-key checking is enabled.strict_host_key_checking(optional, defaulttrue): Iffalse, host keys are not verified (insecure). Iftrueand no known-hosts source is available, dial may fail fast with a clear error.disable_stdout_stderr_print(optional, defaultfalse): Iftrue, suppress streamingOUT/ERRlines on the tty (captures still apply where implemented).
./kkflyCustom config path:
./kkfly -config /path/to/config.ymlPrint version:
./kkfly --versionWrite a JSON report (default kkfly.json):
./kkfly --jsonCustom JSON output path:
./kkfly -json-out /tmp/report.jsonIf sudo: true, the runner uses sudo -n (no password prompts). The remote user must have passwordless sudo configured for the command pattern you need; otherwise expect a fast failure on that host.