FlashKraft ⚡
FlashKraft is a lightning-fast OS image writer built entirely in Rust. No Electron, no shell scripts, no external tooling — pure Rust from UI to block device.
FlashKraft ships two front-ends from a single Rust workspace:
| Binary | Use case |
|---|---|
flashkraft |
Desktop GUI — mouse-driven with animated progress, native file dialogs, 43 themes |
flashkraft-tui |
Terminal UI — keyboard-driven, works over SSH, built-in file explorer & themes |
flashkraft (GUI) |
flashkraft-tui (TUI) |
|
|---|---|---|
| Framework | Iced 0.14 | Ratatui 0.30 |
| Input | Mouse + keyboard | Keyboard only |
| Themes | 43 themes from flashkraft-core |
43 themes from flashkraft-core |
| Best for | Desktop users | SSH / headless / minimal setups |
Preview
GUI (Iced desktop)
TUI (Ratatui terminal)
Full workflow

Flash progress (tui-slider)

File explorer & theme switcher

Note: Demo GIFs are stored with Git LFS. Run
git lfs install && git lfs pullafter cloning if the images appear broken.
Features
Shared (both interfaces)
- ⚡ Pure-Rust flash engine — no
dd, no bash scripts; writes directly to the block device usingstd::fs,nixioctls, andsha2verification - 🔒 Write verification — SHA-256 of the source image is compared against a read-back of the device after every flash
- 🔄 Partition table refresh —
BLKRRPARTioctl ensures the kernel picks up the new partition layout immediately, so the USB boots first time - 🧲 Lazy unmount — all partitions are cleanly detached via
umount2(MNT_DETACH)before writing - 📁 Multiple image formats — ISO, IMG, DMG, ZIP, and more
- 💾 Automatic drive detection — removable drives refreshed on demand
- 🛡️ Safe drive selection — system drives flagged, oversized drives warned, read-only drives blocked
- 🎯 Real-time progress — stage-aware progress bar with live MB/s speed display
- 🪶 Tiny footprint — Rust-compiled binary, C-like memory usage, no Electron runtime
GUI extras
- 🎨 43 themes shared with the TUI — all from
flashkraft-core, persisted across sessions ingui-settings.json - 🖱️ Native file picker — powered by
rfdfor OS-native open dialogs
TUI extras
- ⌨️ Fully keyboard-driven — vim-style
j/knavigation,b/Escto go back - 📂 Built-in file explorer — browse and pick ISO files without leaving the terminal (
Tab/Ctrl+F) - 📊 Pie-chart drive overview — storage breakdown rendered inline using
tui-piechart - ✅ Checkbox confirmation screen — safety checklist before every flash via
tui-checkbox - 🎚️ Slider progress bar — smooth flash-progress widget via
tui-slider - 🎨 43 themes shared with the GUI — all from
flashkraft-core, switchable live witht, instant live-preview panel withShift+T, persisted intui-settings.json - 🌀 Animated spinners — braille flux spinners and bouncing bars via
tui-spinner - 🖥️ Works over SSH — no display server required
How flashing works
FlashKraft uses a self-elevating pure-Rust helper pattern. When you click/confirm Flash:
Main process (GUI or TUI)
└─ pkexec /path/to/flashkraft[−tui] --flash-helper <image> <device>
└─ Runs as root, pure Rust, no shell
1. UNMOUNTING — reads /proc/mounts, calls umount2(MNT_DETACH) per partition
2. WRITING — streams image → block device in 4 MiB chunks, emits PROGRESS lines
3. SYNCING — fsync(fd) + sync() to flush all kernel write-back caches
4. REREADING — BLKRRPART ioctl so the kernel sees the new partition table
5. VERIFYING — SHA-256(image) == SHA-256(device[0..image_size])
6. DONE — UI shows success
The same binary is re-executed with elevated privileges via pkexec — no separate helper binary needs to be installed. All output (progress, logs, errors) is written to stdout as structured lines that the UI reads in real time.
Why not dd?
dd approach |
FlashKraft | |
|---|---|---|
| Shell dependency | ✗ requires bash, coreutils | ✓ pure Rust |
| Progress format | \r-terminated, locale-dependent |
✓ structured PROGRESS:bytes:speed |
| Speed unit handling | ✗ kB/s / MB/s / GB/s mixed | ✓ always normalised to MB/s |
| Write verification | ✗ none | ✓ SHA-256 read-back |
| Partition table refresh | ✗ not done | ✓ BLKRRPART ioctl |
| Error reporting | ✗ exit code only | ✓ ERROR:<message> on every failure path |
The Elm Architecture (GUI)
The GUI crate is built using The Elm Architecture (TEA), which Iced embraces as its natural pattern for interactive applications.
Core Concepts
1. Model (State)
2. Messages
3. Data Flow
User Action → Message → Update → State
↓
Task/Subscription
↓
Async Result → Message → Update → State
↓
View → UI
TUI Screen Flow
The TUI is a multi-screen application driven entirely by keyboard input:
SelectImage ──(Enter/confirm)──► SelectDrive ──(Enter)──► DriveInfo
▲ ▲ │
│ (Esc/b) │ (Esc/b) (f/Enter)
│ │ ▼
│ SelectDrive ConfirmFlash
│ │
│ (y — flash)
│ ▼
│ Flashing
│ (c — cancel)
│ │
└──────────────────(r — reset)────────── Complete / Error
TUI Key Bindings
| Screen | Key | Action |
|---|---|---|
| SelectImage | i / Enter |
Enter editing mode |
| SelectImage | Tab / Ctrl+F |
Open built-in file browser |
| SelectImage | Esc / q |
Quit |
| BrowseImage | j / ↓ |
Move cursor down |
| BrowseImage | k / ↑ |
Move cursor up |
| BrowseImage | Enter |
Descend into directory / select file |
| BrowseImage | Backspace |
Ascend to parent directory |
| BrowseImage | Esc / q |
Dismiss without selecting |
| SelectDrive | j / ↓ |
Scroll drive list down |
| SelectDrive | k / ↑ |
Scroll drive list up |
| SelectDrive | Enter / Space |
Confirm selected drive |
| SelectDrive | r / F5 |
Refresh drive list |
| SelectDrive | Esc / b |
Go back |
| DriveInfo | f / Enter |
Advance to ConfirmFlash |
| DriveInfo | Esc / b |
Go back |
| ConfirmFlash | y / Y |
Begin flashing |
| ConfirmFlash | n / Esc / b |
Go back |
| Flashing | c / Esc |
Cancel flash |
| Complete | r / R |
Reset to start |
| Complete | q / Esc |
Quit |
| Error | r / Enter |
Reset to start |
| Error | q / Esc |
Quit |
| Any | Ctrl+C / Ctrl+Q |
Force quit |
Building and Running
Prerequisites
- Rust 1.70 or later
pkexec(part ofpolkit, available on all major Linux distributions)- For GUI: a running display server (X11 or Wayland)
- For TUI: any terminal emulator (works over SSH)
Build
# Build everything
# Build only the GUI
# Build only the TUI
Run
# Launch the GUI
# Launch the TUI
Development
# Debug builds (faster compilation)
# Run all tests across the workspace
# Check without building
# Lint
# With backtraces
RUST_BACKTRACE=1
Usage
GUI
- Select Image — click the
+button and choose an ISO, IMG, or DMG file - Select Drive — pick the target USB or SD card from the detected drives list
- Flash — click Flash!; authenticate with
pkexecwhen prompted - Wait — the progress bar shows live stage, bytes written, and MB/s
- Done — verification passes automatically; safely remove the drive
TUI
- Select Image — press
ito start typing a path, orTab/Ctrl+Fto open the file browser - Select Drive — use
j/kto scroll,rto refresh,Enterto confirm - Review Drive Info — inspect the storage pie-chart, then press
fto proceed - Confirm — read the safety checklist and press
yto flash, orbto go back - Wait — the slider progress bar shows live stage, bytes written, and MB/s
- Done — press
rto reset orqto quit
Project Structure
This is a Cargo workspace with three crates:
flashkraft/ ← workspace root
├── Cargo.toml ← workspace manifest (shared dep versions)
│
├── crates/
│ │
│ ├── flashkraft-core/ ★ shared logic — no GUI/TUI deps
│ │ └── src/
│ │ ├── lib.rs
│ │ ├── flash_helper.rs ★ privileged flash pipeline
│ │ ├── theme/ ★ single source of truth for all 43 themes
│ │ │ ├── mod.rs
│ │ │ ├── types.rs AppTheme, Rgb
│ │ │ └── presets.rs all 43 constructors + THEME_NAMES
│ │ ├── domain/
│ │ │ ├── drive_info.rs
│ │ │ ├── image_info.rs
│ │ │ └── constraints.rs drive/image compatibility checks
│ │ ├── commands/
│ │ │ ├── drive_detection.rs async /sys/block enumeration
│ │ │ └── hotplug.rs USB connect/disconnect watcher
│ │ └── utils/
│ │ └── logger.rs debug_log!, flash_debug!, status_log! macros
│ │
│ ├── flashkraft-gui/ Iced desktop application
│ │ ├── examples/
│ │ │ ├── basic_usage.rs
│ │ │ └── custom_theme.rs
│ │ └── src/
│ │ ├── main.rs entry point + privilege escalation
│ │ ├── lib.rs
│ │ ├── core/ Elm Architecture
│ │ │ ├── state.rs Model + TEA methods
│ │ │ ├── message.rs all Message variants
│ │ │ ├── update.rs state transition logic
│ │ │ ├── flash_runner.rs Iced Subscription — streams FlashProgress
│ │ │ ├── storage.rs JSON settings (gui-settings.json)
│ │ │ └── commands/
│ │ │ └── file_selection.rs async rfd file dialog
│ │ ├── ui/ presentation layer
│ │ │ ├── mod.rs view dispatch
│ │ │ ├── theme.rs theme utilities
│ │ │ ├── screens/ one file per screen
│ │ │ │ ├── select_image.rs
│ │ │ │ ├── select_drive.rs
│ │ │ │ ├── flashing.rs
│ │ │ │ ├── complete.rs
│ │ │ │ └── error.rs
│ │ │ └── components/ reusable widgets
│ │ │ ├── animated_progress.rs
│ │ │ ├── header.rs
│ │ │ ├── progress_line.rs
│ │ │ ├── step_indicators.rs
│ │ │ └── theme_selector.rs
│ │ └── utils/
│ │ └── icons.rs Bootstrap icon mapper
│ │
│ └── flashkraft-tui/ Ratatui terminal application
│ ├── examples/
│ │ ├── headless_demo.rs
│ │ ├── tui_demo.rs
│ │ ├── flash_progress_demo.rs tui-slider progress showcase
│ │ └── theme_demo.rs full theme switcher + live-preview panel
│ └── src/
│ ├── main.rs entry point + privilege escalation
│ ├── lib.rs event loop + terminal setup
│ ├── core/ business logic
│ │ ├── state.rs App state + screen transitions
│ │ ├── message.rs AppScreen, FlashEvent, InputMode
│ │ ├── update.rs keyboard event handler per screen
│ │ ├── flash_runner.rs async flash task bridge
│ │ └── storage.rs JSON settings (tui-settings.json)
│ └── ui/ presentation layer
│ ├── mod.rs render dispatch + themed_block!, kv_line! macros
│ ├── theme.rs TuiPalette — derives from flashkraft-core (43 themes)
│ ├── screens/ one file per screen
│ │ ├── select_image.rs
│ │ ├── select_drive.rs
│ │ ├── confirm.rs
│ │ ├── flashing.rs
│ │ ├── complete.rs
│ │ └── error.rs
│ └── components/ reusable widgets
│ ├── chrome.rs header, footer, breadcrumbs
│ ├── helpers.rs centred_rect, file_icon, piechart
│ ├── file_ops.rs file operation modals
│ └── theme_panel.rs live-preview theme picker overlay
│
├── scripts/
│ ├── version.nu print current workspace version
│ ├── bump_version.nu automated workspace version bump
│ ├── check_publish.nu pre-publish readiness check
│ ├── release_prepare.nu generate CHANGELOG.md + RELEASE_NOTES.md
│ ├── upgrade_deps.nu nightly dependency upgrade
│ ├── setup_gitea.nu add / update the Gitea remote
│ ├── migrate_to_gitea.nu dual GitHub + Gitea hosting
│ ├── ci/
│ │ ├── validate_tag.nu validate vX.Y.Z tag format
│ │ ├── publish_crate.nu idempotent crates.io publisher
│ │ ├── quality_gate.nu fmt + clippy + test + nu tests
│ │ ├── release_notes.nu CHANGELOG + RELEASE_NOTES generator
│ │ └── stage_artifacts.nu binary staging for release
│ └── tests/
│ ├── runner.nu shared test runner
│ ├── run_all.nu runs every test_*.nu suite
│ ├── test_version.nu
│ ├── test_bump_version.nu
│ ├── test_check_publish.nu
│ ├── test_release_prepare.nu
│ ├── test_upgrade_deps.nu
│ ├── test_validate_tag.nu
│ └── test_publish_crate.nu
│
├── .github/workflows/
│ ├── ci.yml
│ ├── deps-update.yml
│ └── release.yml
│
└── .gitea/workflows/
├── ci.yml
├── deps-update.yml
└── release.yml
Items marked ★ form the flash pipeline and are described in detail above.
Dependencies
flashkraft-core
| Crate | Version | Purpose |
|---|---|---|
sysinfo |
0.38 | Drive enumeration |
nix |
0.31 | umount2, BLKRRPART ioctl, fsync |
sha2 |
0.11 | SHA-256 write verification |
tokio |
1 | Async runtime |
futures / futures-timer |
0.3 / 3.0 | Async channel primitives |
dirs |
6.0 | XDG data directory resolution |
anyhow |
1 | Error handling |
flashkraft-gui
| Crate | Version | Purpose |
|---|---|---|
iced |
0.14.0 | Cross-platform GUI framework (Elm Architecture) |
iced_aw |
0.14.1 | Additional Iced widgets |
iced_fonts |
0.3.0 | Bootstrap icon font |
rfd |
0.17 | Native file/folder dialogs |
serde / serde_json |
1 | JSON settings persistence |
flashkraft-tui
| Crate | Version | Purpose |
|---|---|---|
ratatui |
0.30 | Terminal UI framework |
crossterm |
0.29 | Cross-platform terminal control |
tui-file-explorer |
1.0.6 | Built-in keyboard-driven file browser |
tui-slider |
0.3.2 | Flash-progress slider widget |
tui-spinner |
0.2.3 | Animated braille spinners (flux, bar, linear) |
tui-piechart |
0.3.3 | Drive storage pie-chart widget |
tui-checkbox |
0.4.4 | Drive-list and confirm-screen checkboxes |
serde / serde_json |
1 | JSON settings persistence |
Architecture Highlights
- Shared core crate —
flashkraft-corecontains all flash logic and all 43 themes; both UIs are thin frontends over the same engine - Centralised theme catalogue —
flashkraft-core::themeis the single source of truth; adding a theme to core automatically makes it available in both GUI and TUI with no other changes - Pure-Rust flash engine — zero shell scripts or external binaries
- Self-elevating helper — single binary per UI, no install-time setup beyond
polkit - Elm Architecture (GUI) — unidirectional data flow, pure
update/viewfunctions - Screen-based state machine (TUI) — each
AppScreenvariant owns its event handler and render function - JSON settings — human-readable
gui-settings.json/tui-settings.jsonin the OS config dir - 557 tests, 0 warnings — clean
cargo test --workspaceandcargo clippy --workspace
Demo GIFs & Git LFS
All generated GIFs under examples/vhs/generated/ are tracked by Git LFS (see .gitattributes).
# One-time setup after cloning
&&
# Regenerate all demos (requires vhs installed)
# Regenerate TUI demos only (output: crates/flashkraft-tui/examples/vhs/generated/)
# Regenerate GUI demos only (output: crates/flashkraft-gui/examples/vhs/generated/)
# Render a single tape by name
# List all available tapes and generated GIFs
| Tape | What it shows |
|---|---|
tui-demo |
Full keyboard-driven wizard: image → drive → flash → complete |
tui-headless |
Headless state-machine demo (no TTY required) |
flash-progress |
Animated tui-slider progress bar during a simulated write |
theme-switcher |
Live-preview theme panel (Shift+T), 43-theme catalogue, t cycling |
demo-basic |
GUI basic usage |
demo-build |
GUI build walkthrough |
demo-quick |
GUI quick-start |
Install VHS:
To run Rust examples from the workspace root:
# Core examples
# GUI examples (crates/flashkraft-gui/examples/)
# TUI examples (crates/flashkraft-tui/examples/)
Contributing
Contributions are welcome! Please:
- Keep all flash logic in
flashkraft-core— neither the GUI nor the TUI crate should contain flash pipeline code - Follow the Elm Architecture pattern in the GUI — all state changes via
update - Follow the screen-state-machine pattern in the TUI — screen transitions via
Appmethods - Both frontends share the same
core/+ui/screens/+ui/components/layout - Keep functions pure where possible
- Add unit tests for any new logic, especially in
flash_helper.rs,core/state.rs, andcore/update.rs - Run
cargo test --workspaceandcargo clippy --workspacebefore opening a PR
Learning Resources
- Iced Documentation
- Ratatui Documentation
- The Elm Architecture Guide
- Iced Examples
- Ratatui Examples
- The Rust Book
- nix crate — POSIX ioctls and syscalls from Rust
License
MIT — see LICENSE for details.
Acknowledgments
- GUI built with Iced
- TUI built with Ratatui
- Follows The Elm Architecture
- Flash pipeline design inspired by Balena Etcher
- Terminal widgets: tui-slider, tui-spinner, tui-piechart, tui-checkbox