A native macOS GUI for managing your Bitcoin Core and Electrs nodes on an external SSD
Built with Rust Β· Iced Β· Metal-accelerated Β· Apple Silicon native
BitEngine is a macOS desktop application that lets you launch, monitor, and shut down a self-hosted Bitcoin Core (bitcoind) and Electrs indexer node β both stored on an external SSD β without touching the terminal.
- Dual side-by-side terminal panels with live log streaming
- Real-time block height display via JSON-RPC
- Green/grey status indicators: Running Β· Synced Β· Ready for each node
- One-click graceful shutdown (RPC stop β SIGTERM β SIGKILL)
- Binary updater: scans
~/Downloads/bitcoin_builds/and atomically replaces binaries - Fully configurable data paths, persisted across sessions
- Single-binary distribution β no runtime, no WebView, no Electron
Dual terminal view with status indicators and live block height
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BLOCK HEIGHT Update Binariesβ¦ β
β 895,234 β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β DIRECTORY PATHS [Hide] β
β Binaries Folder /Volumes/SSD/Binaries [Browseβ¦] β β
β Bitcoin Data Directory /Volumes/SSD/BitcoinChain [Browseβ¦] β β
β Electrs DB Directory /Volumes/SSD/ElectrsDB [Browseβ¦] β β
β Changes take effect on next launch [Save] β
βββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ€
β Bitcoin [Launch] β Electrs [Launch] β
β β Running β Synced β Ready β β Running β Synced β Ready β
βββββββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ€
β $ bitcoind -datadir=β¦ β $ electrs --network bitcoin β¦ β
β 2025-01-15T12:00:01Z Loaded β [2025-01-15T12:00:05Z INFO ] Opening β
β 2025-01-15T12:00:02Z Opening β [2025-01-15T12:00:06Z INFO ] Indexin β
β ... β ... β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β [Shutdown Bitcoind & Electrs] [Shutdown Electrs Only] β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Each node gets its own scrollable terminal panel showing real-time stdout and stderr. Output is streamed on dedicated OS threads and drained into the UI every 100 ms β the interface never blocks.
Three per node, updated automatically:
| Indicator | Condition |
|---|---|
| Running | Process is alive |
| Synced | Bitcoin: verificationprogress > 99.99% via RPC Β· Electrs: key log phrases detected |
| Ready | Running AND Synced |
Polls getblockchaininfo via JSON-RPC every 5 seconds and displays the current block height with comma formatting (e.g. 895,234).
Click Update Binaries⦠to scan ~/Downloads/bitcoin_builds/binaries/ for versioned folders (bitcoin-27.0, electrs-0.10.5), pick the highest semantic version, and atomically replace binaries in your SSD Binaries/ folder.
If bitcoin_builds is not found, BitEngine checks for BitForge.app in /Applications and offers to open it, or shows the download link.
- Electrs only: SIGTERM β 10 s wait β SIGKILL
- Bitcoin (and Electrs): RPC
stopcommand β 60 s wait β SIGKILL fallback - Shutdown runs in a background thread so the UI stays responsive
All three data directories (Binaries, Bitcoin data, Electrs DB) are editable in the UI and persisted to ~/Library/Application Support/BitcoinNodeManager/config.json. Changes take effect on the next node launch.
BitEngine expects this structure on your external SSD:
<SSD root>/
βββ BitEngine.app β this application
βββ Binaries/
β βββ bitcoind
β βββ bitcoin-cli
β βββ bitcoin-tx
β βββ bitcoin-util
β βββ electrs
βββ BitcoinChain/
β βββ bitcoin.conf β auto-created with sensible defaults if missing
βββ ElectrsDB/
The SSD root is auto-detected from the binary's location. When running as a .app bundle the binary lives at Contents/MacOS/, so BitEngine walks up three directories to find the SSD root. You can override this with the BITCOIN_NODE_MANAGER_ROOT environment variable.
# Install Rust (skip if already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"
# Apple Silicon target (already present on arm64 Macs β add to be sure)
rustup target add aarch64-apple-darwin
# Intel Mac target
rustup target add x86_64-apple-darwinRequires: Rust 1.75+, macOS 12 Monterey or later, Xcode Command Line Tools (
xcode-select --install)
cargo build
./target/debug/bitcoin_node_manager# Apple Silicon
cargo build --release --target aarch64-apple-darwin
# Intel
cargo build --release --target x86_64-apple-darwin./build_bundle.sh
# Output: ./dist/BitEngine.app
open dist/BitEngine.appThe script compiles, assembles the .app directory structure, writes Info.plist, copies the binary, and applies an ad-hoc codesign so Gatekeeper doesn't block local execution.
cargo build --release --target aarch64-apple-darwin
cargo build --release --target x86_64-apple-darwin
lipo -create \
target/aarch64-apple-darwin/release/bitcoin_node_manager \
target/x86_64-apple-darwin/release/bitcoin_node_manager \
-output dist/BitEngine.app/Contents/MacOS/BitEngine
codesign --force --deep --sign "-" dist/BitEngine.appFor distribution outside the App Store you need a Developer ID Application certificate from Apple:
# Sign
codesign --force --deep \
--sign "Developer ID Application: Your Name (TEAMID)" \
--options runtime \
dist/BitEngine.app
# Notarise (requires app-specific password from appleid.apple.com)
xcrun notarytool submit dist/BitEngine.app \
--apple-id you@example.com \
--team-id TEAMID \
--password APP_SPECIFIC_PASSWORD \
--wait
# Staple the ticket so the app passes Gatekeeper offline
xcrun stapler staple dist/BitEngine.appConfig is stored at:
~/Library/Application Support/BitcoinNodeManager/config.json
Example:
{
"binaries_path": "/Volumes/SSD/Binaries",
"bitcoin_data_path": "/Volumes/SSD/BitcoinChain",
"electrs_data_path": "/Volumes/SSD/ElectrsDB"
}If no config exists on first launch, defaults are derived from the SSD root.
If <bitcoin_data_path>/bitcoin.conf does not exist, BitEngine creates one automatically:
# Bitcoin Core β auto-generated by BitEngine
server=1
txindex=1
rpcport=8332
rpcallowip=127.0.0.1
# Cookie-based authentication is active by default.Cookie-based RPC authentication (.cookie file) is used by default. BitEngine checks <datadir>/.cookie and <datadir>/mainnet/.cookie before falling back to rpcuser/rpcpassword from bitcoin.conf.
Update Binaries⦠(toolbar button) runs the following flow:
- Check
~/Downloads/bitcoin_builds/binaries/ - Scan for folders matching
bitcoin-X.Y.Zandelectrs-X.Y.Z - Pick the highest semantic version for each (major.minor.patch tuple comparison)
- Copy binaries into the configured
Binaries/folder:- Written to a
.tmpfile first chmod 755applied- Atomically renamed to the final path β a running binary is never half-replaced
- Written to a
- Report what was updated in an overlay dialog
If bitcoin_builds is not found:
| Condition | Behaviour |
|---|---|
/Applications/BitForge.app exists |
Offers to open BitForge |
| BitForge not found | Shows link to BitForge on GitHub |
src/
βββ main.rs Entry point
β Β· Single-instance lock (fcntl LOCK_EX | LOCK_NB)
β Β· SSD root auto-detection from binary path
β Β· Iced application bootstrap
β
βββ config.rs Persistent configuration
β Β· Serialised as JSON via serde_json
β Β· Stored in ~/Library/Application Support (macOS)
β Β· directories crate handles platform path resolution
β
βββ rpc.rs Bitcoin JSON-RPC client
β Β· reqwest + rustls (no OpenSSL dependency)
β Β· Cookie-file auth with bitcoin.conf fallback
β Β· Auto-creates bitcoin.conf when missing
β Β· getblockchaininfo polling, stop command
β
βββ process_manager.rs Child process lifecycle
β Β· Spawns bitcoind / electrs with stdout+stderr pipes
β Β· Two OS reader threads per process β Arc<Mutex<VecDeque>>
β Β· SIGTERM β 10 s grace period β SIGKILL
β Β· Electrs sync-line detection (5 log patterns)
β
βββ updater.rs Binary update system
β Β· Semver folder scanning (tuple comparison, no regex)
β Β· Atomic copy: temp file β chmod 755 β rename
β Β· BitForge.app detection and fallback link
β
βββ ui.rs Iced 0.13 MVU application
Β· App state struct
Β· Message enum (all events)
Β· update() β state transitions + Task dispatch
Β· view() β pure render (no side effects)
Β· subscription() β 100 ms output timer, 5 s RPC timer
Main thread (Iced / tokio event loop)
ββ OutputTick every 100 ms β drains both output queues into terminal buffers
ββ RpcTick every 5 s β Task::perform(async getblockchaininfo)
ββ reqwest HTTP β BlockchainInfoReceived
Per-process background threads (2 per running node)
ββ stdout reader ββ
ββ stderr reader ββ΄β push lines into Arc<Mutex<VecDeque<String>>>
The Iced update loop is the only writer to UI state. The background threads only write to the queues. No shared mutable state outside Arc<Mutex<>>.
| Crate | Version | Purpose |
|---|---|---|
iced |
0.13 | GUI framework (Metal-accelerated, Elm/MVU) |
tokio |
1 | Async runtime (driven by iced's tokio feature) |
reqwest |
0.12 | HTTP client for Bitcoin RPC (rustls, no OpenSSL) |
serde / serde_json |
1 | Config and RPC serialisation |
anyhow |
1 | Ergonomic error propagation |
thiserror |
1 | Structured error type definitions |
rfd |
0.15 | Native macOS file/folder picker dialog |
directories |
5 | XDG / macOS Application Support path resolution |
libc |
0.2 | flock() for single-instance guard, SIGTERM |
iced_runtime |
0.13 | Action<T> type for scroll task mapping |
| Area | Python (tkinter) | BitEngine (Rust / Iced) |
|---|---|---|
| Language | Interpreted | Native compiled |
| Startup time | ~1β2 s | <100 ms |
| Bundle size | 40+ MB (Python + tkinter) | ~5 MB |
| Threading | GIL limits true parallelism | Real OS threads |
| Terminal memory | Unbounded growth | Hard cap: 5 000 lines per panel |
| UI blocking | messagebox blocks event loop |
Overlay widget, never blocks |
| Process shutdown | terminate() only |
RPC stop β SIGTERM β SIGKILL |
| Binary copy safety | shutil.copy2 (non-atomic) |
temp file β chmod β atomic rename |
| Semver comparison | Regex + string sort | Tuple comparison (major, minor, patch) |
| Electrs sync detection | 3 log patterns | 5 log patterns |
| RPC auth | Cookie + fallback | Same, cleaner error messages |
| Single-instance guard | fcntl.flock |
libc::flock (no GIL risk) |
| Error handling | try/except, silent failures |
Result<T,E> throughout, no unwrap() |
| Type safety | Runtime | Compile-time |
MIT β see LICENSE.
- BitForge β builds Bitcoin Core and Electrs binaries for use with BitEngine
- Bitcoin Core
- Electrs