A high-performance command-line tool for flashing disk images to block devices. fls can download images from URLs, decompress them on-the-fly, and write directly to block devices with optimized buffering and progress reporting.
- Stream-based flashing: Download, decompress, and write in parallel
- Multiple compression formats: Supports
.xz,.gz,.bz2, and more - Progress monitoring: Real-time progress bars for download, decompression, and write operations
- Automatic retries: Built-in retry logic for network failures
- Optimized I/O: Configurable buffer sizes for optimal performance
Download the latest release for your architecture:
# For ARM64/aarch64 systems
curl -L https://github.com/jumpstarter-dev/fls/releases/download/0.1.5/fls-aarch64-linux -o /usr/local/bin/fls
chmod +x /usr/local/bin/fls
# For x86_64 systems
curl -L https://github.com/jumpstarter-dev/fls/releases/download/0.1.5/fls-x86_64-linux -o /usr/local/bin/fls
chmod +x /usr/local/bin/flsReplace 0.1.5 with the desired version from the releases page.
cargo build --release
sudo cp target/release/fls /usr/local/bin/Use ssh://[user@]host[:port]/device as the destination for HTTP/HTTPS or OCI images:
fls from-url "https://example.com/image.img.xz" ssh://root@board/dev/emmc0
fls from-url "oci://quay.io/org/image:latest" ssh://root@board:2222/dev/emmc0
# IPv6
fls from-url "https://example.com/image.img.xz" ssh://root@[::1]/dev/emmc0The legacy form [user@]host:/device is still accepted (no IPv6 or inline port).
fls uploads its embedded aarch64-QNX write head over SSH, then streams framed
commands. For another target architecture/OS, build the appropriate write head
and use --wh-bin /path/to/flswh. When building from source, run
make remote-wh before cargo build to embed the QNX binary; local-only builds
work without it. See remote/README.md for build instructions.
SSH keys, agents, ports, and aliases use the system ssh and your
~/.ssh/config. Set the port inline: ssh://host:2222/dev/x. -p remains
the registry-password option. Password authentication uses sshpass with
--ssh-password-file <path> (FLS_SSH_PASS_FILE) or, if no file is specified,
the SSHPASS environment variable. --ssh-compress enables SSH compression;
--wh-bin can also be supplied through FLS_WH_BIN.
By default, all SSH connections use StrictHostKeyChecking=no,
UserKnownHostsFile=/dev/null, and LogLevel=ERROR: host keys are not verified
or saved to your user known-hosts file. --strict-ssh-host-key-checking instead
uses StrictHostKeyChecking=yes with your configured known-hosts files and log
level. In strict mode, connect once with ssh root@board (or
ssh -p <port> root@board) to verify the key before flashing; unknown or changed
keys fail promptly. Key/agent authentication remains non-interactive unless
sshpass is selected.
Before uploading, fls queries the remote OS, CPU architecture, and release
using uname (-p for the QNX processor, -m elsewhere). The embedded binary
is selected only for aarch64 QNX 7. Other platforms require --wh-bin with a
compatible write head; automatic Linux/macOS binary embedding can be added
when those artifacts are available.
Remote zero fills use ZERO rather than transferring their contents. Adjacent
DATA writes are coalesced into frames of up to 8 MiB, flushing before SEEK,
FILL, and final SYNC so sparse-image fragments do not each require an ACK.
DATA, ZERO, and SEEK frames are pipelined in order while a separate reader
consumes progress and ACKs. --write-buffer-size also limits unacknowledged
DATA bytes (default: 128 MiB); smaller windows reduce the maximum frame size.
Small-command metadata is bounded to 1024 outstanding requests. Closing drains
the ACKs before SYNC/QUIT, and remote errors stop the pipeline.
The writer checks write, fill, and seek ranges against remote device capacity, and waits
for successful SYNC and QUIT before reporting completion. --o-direct applies
to local devices; remote durability uses SYNC. One flash session per host is
supported because uploads share /tmp/fls-wh.
The reported Written count is the logical device position, including sparse
skips, matching local flashing; DONE still validates only actual written bytes.
Flash a compressed image from a URL to a block device:
fls from-url \
-k \
-n \
"https://example.com/path/to/image.raw.xz" \
/dev/mmcblk1Flags used:
-k- Skip SSL certificate verification (useful for internal servers with self-signed certs)-n- Print progress on new lines (better for logging)
Flash with custom headers and progress interval:
fls from-url \
--header "Authorization: Bearer token123" \
--progress-interval 1.0 \
--buffer-size 2048 \
"https://cdn.example.com/rhivos-image.raw.xz" \
/dev/nvme0n1Block flash command:
URL: https://example.com/path/to/image.raw.xz
Device: /dev/mmcblk1
Buffer size: 1024 MB
Max retries: 10
Retry delay: 2 seconds
Using decompressor: xzcat
Opening block device for writing: /dev/mmcblk1
Starting download from: https://example.com/path/to/image.raw.xz
Content length: 547832416 bytes
Download: 27.92 MB / 522.45 MB (5.3%) | 27.92 MB/s | Decompressed: [░░░░░░░░░░] 0.4% | Written: 63.00 MB | 62.99 MB/s
Download: 101.40 MB / 522.45 MB (19.4%) | 33.78 MB/s | Decompressed: [░░░░░░░░░░] 5.3% | Written: 63.00 MB | 20.99 MB/s
...
Download: 506.08 MB / 522.45 MB (96.9%) | 31.60 MB/s | Decompressed: [█░░░░░░░░░] 12.8% | Written: 319.00 MB | 19.92 MB/s
Download: Done | Decompressed: [██████████████████░░] 91.5% | Written: 1407.00 MB | 18.57 MB/s
...
Download: Done | Decompressed: Done | Written: Done
Download complete: 522.45 MB in 16.46s (31.75 MB/s)
Decompression complete: 5120.00 MB in 87.83s (58.29 MB/s)
Write complete: 5120.00 MB in 281.23s (18.21 MB/s)
Compression ratio: 9.80x
fls can pull OCI images from registries and flash them either to a block device
or to fastboot partitions.
Use from-url with an oci:// prefix:
fls from-url \
-u "$REGISTRY_USER" \
-p "$REGISTRY_PASS" \
"oci://quay.io/org/image:latest" \
/dev/mmcblk1fls fastboot pulls the OCI image, extracts partition images, and flashes them
using the system fastboot CLI:
fls fastboot oci://quay.io/org/image:latestTo avoid using /tmp for fastboot extraction, set FLS_TMP_DIR to a directory
on persistent storage (default is /var/lib/fls).
If the OCI manifest includes
automotive.sdv.cloud.redhat.com/default-partitions (comma-separated),
fls fastboot flashes only those partitions by default. Otherwise it flashes
all annotated partitions.
Provide explicit partition mappings when the OCI image contains multiple files:
fls fastboot oci://quay.io/org/image:latest \
-t boot_a:boot_a.simg \
-t system_a:system_a.simgWhen -t is provided, fls applies those mappings on top of the OCI layer
annotations and includes those partitions in the flash set (e.g., add
-t vbmeta_a:vbmeta_a.simg to flash vbmeta). If the image lacks annotations,
it falls back to looking for the specified files directly in the image.
Registry credentials can be provided with -u/--username and -p/--password
(FLS_REGISTRY_PASSWORD env var is supported for the password). Both are
required for authenticated access.
Flash an image from a URL to a block device.
fls from-url [OPTIONS] <URL> <DEVICE>
Arguments:
<URL>- URL to download the image from<DEVICE>- Destination device path (e.g.,/dev/sdb,/dev/mmcblk1)
Options:
-k, --insecure-tls- Ignore SSL certificate verification--cacert <CACERT>- Path to CA certificate PEM file for TLS validation--buffer-size <SIZE>- Buffer size in MB for download buffering (default: 1024)--write-buffer-size <SIZE>- Write queue and remote unacknowledged DATA limit (default: 128 MiB)--max-retries <NUM>- Maximum number of retry attempts (default: 10)--retry-delay <SECONDS>- Delay in seconds between retry attempts (default: 2)--debug- Enable debug output--o-direct- Enable O_DIRECT mode for direct I/O (bypasses OS cache)-H, --header <HEADER>- Custom HTTP headers (can be used multiple times, format:Header: value)-i, --progress-interval <SECONDS>- Progress update interval in seconds (default: 0.5, accepts float values)-n, --newline-progress- Print progress on new lines instead of overwriting
fls writes directly to block devices and will overwrite all data on the target device. Always double-check the device path before running.
- Requires root/sudo privileges to write to block devices
- Ensure the target device is not mounted
- Verify the device path to avoid data loss
- Use
lsblkorfdisk -lto identify the correct device before flashing