This document explains the internal architecture, design patterns, and code structure of endur. It is intended for developers who wish to modify, debug, or extend the codebase.
The following diagram illustrates the relationship between the CLI client, the background Daemon, the filesystem, and the Git repository:
graph TD
Client[CLI Client: endur watch/list-snapshots/restore]
Daemon[Daemon: endur serve]
LockFile[Lock File: runtime.lock]
UDS[Unix Domain Socket: UDS IPC]
FS[File System]
Index[Isolated Git Index: endur_index]
Repo[Git Repository]
Client -->|IPC Commands| UDS
UDS -->|RPC handler| Daemon
Daemon -->|Exclusive Advisory Lock| LockFile
Daemon -->|File Events| FS
FS -->|Notify Event Channel| Daemon
Daemon -->|Stage changes| Index
Daemon -->|Commit to endur/HEAD branch| Repo
- Subcommand Routing: Parses arguments using
clapand routes them. - Daemon Notification: Subcommands like
watchandunwatchperform the local configuration changes and notify the running daemon using thepoller::send_uds_commandhelper. - Direct Command execution: Core recovery tasks like
list-snapshotsandrestoreexecute directly in the client context by parsing the Git repository, avoiding UDS overhead.
RuntimeLockstruct: Enforces that only one daemon instance is active.- Exclusive Advisory Locking: Utilizes
fs2::FileExt::try_lock_exclusiveto acquire an OS-level lock on the file~/.cache/endur/runtime.lock(and database atruntime.db). - Auto-Cleanup: The lock is automatically released by the operating system when the daemon process terminates.
- Unix Domain Sockets: The daemon binds to local sockets using
tokio::net::UnixListeneron both Unix and Windows 10/11 (where UDS is natively supported):- macOS/Linux:
~/.cache/endur/endur.sock - Windows:
%AppData%\Local\endur\endur.sock
- macOS/Linux:
- JSON-RPC Protocol: Communication uses simple JSON payloads:
reload: Reloads configuration when watched repositories change.kill: Triggers a graceful shutdown of the daemon.status: Responds with daemon metrics.
WatcherManagerstruct: Manages recursive file system monitoring using thenotifycrate.- Path Canonicalization: Filesystem events on macOS and Linux often report relative paths or symlink roots (like
/varvs/private/var). The watcher canonicalizes all event paths prior to comparison. - Git-Aware Filtering:
- Excludes
.git/folder and its contents automatically. - Loads
.gitignorefiles usingignore::gitignore::Gitignoreto match change events against gitignore rules and discard ignored files.
- Excludes
- Event Await & Debounce: The daemon loop listens to four event sources using
tokio::select!:- OS Signal Handler Channel: Listens to target-specific shutdown signals pinned to the stack (
wait_for_signal()). On Unix, handlesSIGINT,SIGTERM, andSIGHUP. On Windows, handlesCtrl+CandCtrl+Breakevents. - IPC Command Channel: Process socket operations.
- File Watcher Channel: Pushes modified files. Captured file changes are cached in a hash map with a 500ms delay.
- Timer Channel: Triggers a capture operation for repositories that have modifications older than 500ms.
- OS Signal Handler Channel: Listens to target-specific shutdown signals pinned to the stack (
- Graceful Exit Actions: When a shutdown signal is received, the loop sends a broadcast termination signal to UDS socket handlers, clears the
RuntimeLockPID metadata value back toNone, and exits cleanly. - Optimization: Debouncing prevents multiple rapid writes (e.g., compilation artifacts or quick editor saves) from creating dozens of intermediate commits.
snapshots::capture: Stages changes and commits them.- Git Index Isolation: Rather than using the user's primary Git index (
.git/index), it creates and maintains a dedicated staging index file at.git/endur_indexviagit2::Index::open. - Branch Architecture:
- Backups are committed to local branches named
endur/<base-commit-hash>. - The parent of the first endur snapshot is the user's HEAD commit. Subsequent backups chain off the previous endur backup commit, keeping histories completely linear.
- Backups are committed to local branches named
- SQLite Metadata Cache:
- To keep reads fast, snapshot metadata is cached in a local SQLite database at
$ENDUR_CACHE_HOME/snapshot_cache.db(usually~/.cache/endur/snapshot_cache.db). - Schema:
snapshots (repo_path TEXT, commit_hash TEXT PRIMARY KEY, base_hash TEXT, timestamp INTEGER, message TEXT, files_changed TEXT). - An index is placed on
(repo_path, timestamp DESC)to speed up list queries. - The cache database uses Write-Ahead Logging (WAL) mode to permit concurrent reads during background writes.
- Transparent Fallback: The caching module functions on a best-effort basis. If the SQLite database is corrupt, locked, or fails to open, it fails silently and falls back directly to walking the repository's Git commits.
- To keep reads fast, snapshot metadata is cached in a local SQLite database at
- Snapshot Filtering:
snapshots::list_snapshots(path, show_all)queries the cache first (falling back to a Git walk if cold or failed).- When
show_allisfalse(default), snapshots are filtered to only those whosebase_hashmatches the currentHEADcommit hash. This hides snapshots that are older than the latest formal commit. - When
show_allistrue, all snapshots recorded for the repository are returned.
prune::prune: Filters and deletes historical snapshot branches.- By Ancestry: Resolves the target commit OID, walks back the history to identify its ancestors, and marks any branch
endur/PwherePis an ancestor ofT(andP != T) for deletion. - By Count: Walks back from
HEADhistory to keep the lastNformal commits, marking older branches for deletion. - By Age: Compares the timestamp of the latest backup commit in
endur/Pbranches to system time and prunes branches older than a duration. - SQLite Cache Eviction: Synchronously deletes database cache entries matching pruned base hashes via
cache::delete_snapshots_for_baseto ensure metadata queries instantly reflect the pruning. - Git GC: Optionally spawns a
git gc --prune=nowsubcommand to purge deleted commits and reclaim disk space immediately.
- By Ancestry: Resolves the target commit OID, walks back the history to identify its ancestors, and marks any branch
- Decoupled
TuiState: Separates core snapshot-browsing state management (watched repositories, snapshots, modified file listings, checkmarked files, and pane focusing) from the rendering engine. This keeps state management clean and modular, allowing the backup-browsing logic to be reused and integrated within the unified multi-tab Control Center. - Asynchronous Control Center TUI (
endur tui):- Tokio Event Loop: Uses an async main loop running on Tokio to merge multiple non-blocking event sources: Crossterm keyboard events, periodic daemon PID/uptime status checks, and log file tail changes.
- Live Log Streaming: Spawns an asynchronous log tail watcher that tails changes to
endur.logand feeds new lines directly into the state machine to trigger real-time UI updates. - Multi-Tab Architecture: Tracks the active view using a
ControlCenterTabenum (tabs:Repositories,Backups,Log,Metrics).
- Safe Terminal RAII: An RAII wrapper
TerminalGuardmanages terminal raw mode, alternate screen buffers, and cursor visibility, ensuring the shell is always cleanly restored on program exit or panic.
- macOS (
launchctlIntegration): Creates~/Library/LaunchAgents/com.endur.daemon.plistconfigured to runendur servein the background, and registers it using the modernlaunchctl bootstrapframework (falling back tolaunchctl loadif necessary). - Linux (
systemdIntegration): Configures a systemd user unit at~/.config/systemd/user/endur.serviceand executes systemd commands to daemon-reload, enable, and start/stop the service.
The Desktop GUI is structured as a Cargo Workspace member separate from the core endur library to ensure independent versioning and lean dependencies.
- Tauri 2.0 Backend (
crates/endur-desktop/src-tauri):- IPC Commands (
src-tauri/src/commands.rs): Exposes Tauri command endpoints (e.g.get_daemon_status,control_daemon,get_watched_repositories,toggle_watch_repo,get_snapshots,get_snapshot_files,get_snapshot_diff,restore_files,get_metrics_summary,get_log_tail) that translate IPC calls into calls to the coreendurlibrary APIs. - Core Integration: Directly links the local
endurcrate path dependency, sharing the sameRuntimeLock,Config,poller, andsnapshotsmodules. - Shared Log Formatting: Uses the public
format_log_lineAPI fromendur::tuito parse and filter daemon logs into clean, human-readable lines in reverse-chronological order (most recent first) before returning them to the UI.
- IPC Commands (
- Svelte 5 Frontend (
crates/endur-desktop/src):- SPA Structure (
src/routes/+page.svelte): Fully interactive Single Page App styled with a premium glassmorphic dark-theme palette using modern CSS variables, scrollbars, and active status animations. - IPC Bridge: Communicates with the Rust backend using Tauri's
invokeAPI. - Log Polling Loop: Uses a 1.5s interval to pull daemon logs from
get_log_tailand refresh the live log panel.
- SPA Structure (
- CI/CD Release Packaging (
.github/workflows/release-desktop.yaml):- Runs on tag pushes matching
desktop-v*or manual workflow runs. - Builds Node dependencies, compiles the Rust backend, and uses
tauri-apps/tauri-actionto compile and bundle native installers: macOS DMG, Windows MSI/EXE, and Linux DEB/AppImage.
- Runs on tag pushes matching
cargo buildThe test suite consists of several test suites covering locking, IPC, watcher, and snapshotting:
cargo test- tests/startup_test.rs: Tests UDS communication (now verified on both Unix and Windows), OS signal graceful shutdown, invalid lock files, and double-lock prevention.
- tests/watch_test.rs: Tests watcher registration and event-driven backup snapshots.
- tests/snapshots_test.rs: Tests Git index isolation, git ignore support, listing snapshots, and restoring snapshots.
- tests/poll_guard_test.rs: Tests poller lock mechanics and state changes.