Architecture¶
fnb is a modular Python package designed for efficient backup workflows. This page explains the core design and implementation details.
Overall Structure¶
fnb consists of the following main components:
src/fnb/
├── __init__.py # Package initialization and version
├── backuper.py # Backup execution logic
├── cli.py # Command-line interface with Typer
├── config.py # Configuration models with Pydantic
├── env.py # .env loading and SSH password lookup orchestration
├── fetcher.py # Fetch execution logic
├── gear.py # Core rsync execution utilities
├── generator.py # Configuration file generation
├── gpg.py # GPG-encrypted password file source
├── keychain.py # macOS Keychain password source
├── reader.py # Configuration discovery and loading
└── uploader.py # Upload execution logic
Component Overview¶
CLI Interface (cli.py)¶
The command-line interface built with Typer framework that provides:
- Main Commands:
fetch,backup,upload,sync,init,status,version - Argument Validation: Type checking and error handling
- Delegate Pattern: Routes commands to appropriate modules
- User Feedback: Progress messages and error reporting
Key design principles:
- Single responsibility for each command
- Consistent error handling across all commands
- Help documentation integrated into command definitions
Configuration Models (config.py)¶
Pydantic-based data models for type-safe configuration:
RsyncTaskConfig¶
Represents a single rsync task with validation:
@dataclass
class RsyncTaskConfig:
label: str # Unique task identifier
summary: str # Human-readable description
host: str # SSH host or "none" for local
source: str # Source path
target: str # Target path
options: list[str] # rsync options
enabled: bool # Task activation status
FnbConfig¶
Top-level configuration container:
class FnbConfig(BaseModel):
fetch: dict[str, RsyncTaskConfig] = {}
backup: dict[str, RsyncTaskConfig] = {}
upload: dict[str, RsyncTaskConfig] = {}
def get_task_by_label(self, kind: Literal["fetch", "backup", "upload"], label: str) -> RsyncTaskConfig | None
def get_enabled_tasks(self, kind: Literal["fetch", "backup", "upload"]) -> list[RsyncTaskConfig]
Note that RsyncTaskConfig itself is unchanged for upload: rsync_source/rsync_target already encode "host describes the source" for fetch/backup, and upload needs the opposite ("host describes the target"). Rather than overload those shared properties, uploader.py builds its remote target locally with a small _remote_target() helper.
Configuration Discovery (reader.py)¶
Handles configuration file discovery and loading:
ConfigReader¶
Main configuration management class:
- Auto-discovery: Searches multiple standard locations
- Environment Expansion: Supports
$HOME,${VAR}syntax, and a leading~ - Validation: TOML parsing and schema validation
- Status Reporting: Configuration overview and diagnostics
A leading ~ is only expanded on the local side of a task, since a ~ on the remote side means the remote user's home and must be left for the remote shell to expand. Which field is local depends on direction: fetch/backup always expand target (and source only when host = "none"); upload inverts this and always expands source (and target only when host = "none").
Search order:
./fnb.toml(project-local)./config.toml./config/*.toml(split configurations)~/.config/fnb/config.toml(user configuration)- Platform-specific user directories
Core Execution Engine (gear.py)¶
The heart of fnb's backup operations:
run_rsync()¶
Main execution function with features:
- SSH Password Automation: Uses
pexpectfor password handling - Process Management: Signal handling and cleanup
- Error Detection: rsync exit code interpretation
- Dry-run Support: Preview mode for all operations
SSH Automation¶
Implements secure SSH password handling:
def run_rsync(task: RsyncTaskConfig,
dry_run: bool = False,
ssh_password: str | None = None,
create_dirs: bool = False) -> None
Key features: - Timeout handling for SSH connections - Secure password masking in logs - Signal-based process termination - Comprehensive error reporting
Task Execution Modules¶
Fetcher (fetcher.py)¶
Handles remote-to-local data retrieval:
- Validates remote connectivity
- Manages SSH authentication
- Creates local target directories
- Delegates to
gear.run_rsync()
Backuper (backuper.py)¶
Handles local-to-external backup operations:
- Validates local source paths
- Creates backup target directories
- Supports various backup destinations
- Delegates to
gear.run_rsync()
Uploader (uploader.py)¶
Handles local-to-remote upload operations, the mirror of fetcher.py:
- Validates the local source path (not the target, which may be remote)
- Manages SSH authentication, same as
fetcher.py - Builds the remote target path locally via
_remote_target()instead ofRsyncTaskConfig.rsync_target, sincehostmeans the opposite side for uploads - Delegates to
gear.run_rsync()
Not part of the sync command (fetch → backup); upload is invoked on its own.
Utility Modules¶
Environment Handler (env.py, keychain.py, gpg.py)¶
env.py handles .env files and orchestrates password lookup; the
Keychain and GPG sources each live in their own module, imported by
env.py but otherwise independent of it:
- Password Discovery: Multiple
.envfile locations - Variable Normalization: Hostname to env var mapping
- Secure Loading: Uses
python-dotenvfor parsing - macOS Keychain (
keychain.py):get_keychain_password()reads afnb-{host}generic password item via thesecurityCLI, gated by_is_macos()so it's a no-op on other platforms - GPG-Encrypted Files (
gpg.py):get_gpg_password()decrypts~/.config/fnb/passwords/{normalized_host}.gpg(falling back todefault.gpg) viagpg --decrypt --quiet --batch; cross-platform, works with symmetric or public-key encrypted files since decryption is delegated entirely togpg/gpg-agent - Fallback Strategy: CLI flag → host env var → Keychain (macOS) → GPG →
FNB_PASSWORD_DEFAULT→ interactive prompt
Configuration Generator (generator.py)¶
Creates initial configuration files:
- Template System: Predefined configuration templates
- File Type Support: Generates
fnb.tomland.envfiles - Interactive Mode: User prompts for configuration
- Force Overwrite: Option to replace existing files
Data Flow Architecture¶
1. Command Processing Flow¶
graph TD
A[User Command] --> B[CLI Parser]
B --> C[Config Discovery]
C --> D[Task Resolution]
D --> E[Execution Module]
E --> F[Core Engine]
F --> G[rsync Process]
2. Configuration Loading¶
graph TD
A[Config Request] --> B[Path Discovery]
B --> C[TOML Parsing]
C --> D[Schema Validation]
D --> E[Env Expansion]
E --> F[Task Objects]
3. Sync Workflow¶
graph TD
A[Sync Command] --> B[Fetch Task]
B --> C[SSH Connection]
C --> D[Remote → Local]
D --> E[Backup Task]
E --> F[Local → External]
F --> G[Complete]
4. Upload Workflow¶
graph TD
A[Upload Command] --> B[Verify Local Source]
B --> C[SSH Connection]
C --> D[Local → Remote]
D --> E[Complete]
Unlike Sync, Upload is invoked directly and does not chain with Fetch or Backup.
Design Principles¶
Modularity¶
- Each module has a single, well-defined responsibility
- Clear interfaces between components
- Minimal coupling between modules
- Easy to test and maintain
Type Safety¶
- Comprehensive type hints throughout codebase
- Pydantic for runtime type validation
- mypy compatibility for static analysis
- Clear error messages for type mismatches
Error Handling¶
- Consistent error reporting across modules
- User-friendly error messages
- Graceful degradation when possible
- Detailed logging for debugging
Extensibility¶
- Plugin-like architecture for new task types
- Configuration-driven behavior
- Easy addition of new rsync options
- Support for custom validation rules
Security Considerations¶
Password Handling¶
- Passwords never stored in plaintext files
- Environment variable isolation
- Process memory cleanup
- Secure inter-process communication
Path Validation¶
- Prevention of directory traversal attacks
- Validation of local vs. remote paths
- Safe expansion of environment variables
- Protection against malicious rsync options
Process Security¶
- Controlled subprocess execution
- Signal handling for clean termination
- Resource cleanup on errors
- Minimal privilege requirements
Performance Characteristics¶
Memory Usage¶
- Minimal memory footprint (< 10MB typical)
- Streaming-based file operations
- Efficient configuration caching
- Low overhead for multiple tasks
Execution Speed¶
- Fast configuration loading (< 100ms)
- Parallel-ready architecture
- Minimal startup overhead
- Efficient rsync delegation
Scalability¶
- Handles hundreds of tasks efficiently
- Configuration file size not a bottleneck
- Suitable for automated/scripted usage
- Resource usage scales linearly
Testing Architecture¶
Test Structure¶
tests/
├── unit/ # Component isolation tests
├── integration/ # End-to-end workflow tests
└── fixtures/ # Shared test utilities
Test Strategy¶
- Unit Tests: Individual module validation
- Integration Tests: Complete workflow validation
- Mocking Strategy: External dependency isolation
- Coverage Target: 85%+ code coverage
Key Test Areas¶
- Configuration validation and error cases
- SSH authentication scenarios
- File system operations
- Error propagation and handling
- CLI argument processing
Future Architecture Considerations¶
Planned Improvements¶
- Plugin system for custom backup destinations
- Configuration templating and inheritance
- Enhanced progress reporting and logging
- Support for parallel task execution
Extension Points¶
- Custom rsync option validators
- Alternative SSH authentication methods
- Integration with cloud storage APIs
- Configuration management via web interface
This architecture provides a solid foundation for reliable, secure, and maintainable backup workflows while remaining simple enough for individual users and robust enough for production environments.