5.5 KiB
ADR-0012: chronogrid_daemon — Headless Peer Node Binary Scaffold
Date: 2026-08-23
Status: Accepted
Task: 2.7
Author: Automated Agent (Cline)
Context
Phase 2 completed the P2P networking stack (chronogrid_net) and the
core-to-net sync bridge (chronogrid_core with p2p feature). The missing
piece was a runnable binary that composes these components into a headless
background process suitable for deployment on Linux servers, NAS devices,
or always-on Windows machines.
Requirements (from docs/TASKS/phase_2_networking_and_schema.md, Task 2.7):
- Binary crate
chronogrid-daemoninapps/chronogrid_daemon/. - CLI args via
clap:--config,--db,--log-level. - TOML config loading with platform-default fallback.
chronogrid_cryptokey derivation + OS keychain integration.chronogrid_coredatabase initialization.chronogrid_netSwarmManager startup.- Structured logging via
tracing. - Graceful shutdown on SIGTERM/SIGINT.
config.example.tomldocumentation.docs/DEPLOYMENT/01-headless-daemon-setup.mdsetup guide.
Decision
1. Crate Structure
Added apps/chronogrid_daemon to the workspace Cargo.toml members list.
The crate is a pure [[bin]] crate (no [lib] target) named chronogrid-daemon.
Dependencies are intentionally minimal — all heavy lifting is delegated to the
three internal crates (chronogrid_core, chronogrid_crypto, chronogrid_net).
2. CLI Argument Design
clap with derive is used for zero-boilerplate CLI argument parsing.
Three arguments:
| Flag | Type | Purpose |
|---|---|---|
-c/--config |
Option<PathBuf> |
Config file path (optional) |
--db |
Option<PathBuf> |
DB path override (optional) |
--log-level |
String |
Tracing filter (default: info) |
3. TOML Configuration
Four config sections with #[serde(default)] on every field, allowing partial
or empty config files. The Config::default() implementation provides sensible
platform-aware defaults:
- Linux defaults:
/var/lib/chronogrid/for all data files,/etc/chronogrid/config.toml. - Windows defaults:
%APPDATA%\ChronoGrid\for all data files.
4. Headless Key Management
The daemon does not require a human-entered passphrase. Instead:
- The "passphrase" for Argon2id is synthesized from the node's
NodeIdhex + the clusterSwarmKeyfingerprint:"chronogrid-daemon:<node_id>:<fingerprint>". - A fresh 16-byte random salt is generated on first boot and stored together with the 32-byte derived key as a 48-byte blob in the OS keychain.
- On subsequent boots, the 48-byte blob is retrieved, the salt extracted, and the key re-derived — providing cryptographic verification that the retrieved credential is correct for this node.
This approach provides per-node database encryption uniqueness without requiring interactive password input, while still leveraging the OS keychain for credential protection (DPAPI on Windows, SecretService on Linux).
5. Startup Sequence (8 Steps)
Step 1: Init tracing (log_level from CLI or RUST_LOG env)
Step 2: Load TOML config (graceful fallback to defaults)
Step 3: load_or_create_identity() + SwarmKey::load_or_create()
Step 4: resolve_master_key() — Argon2id KDF + OS keychain
Step 5: Database::open() — SQLCipher + schema validation
Step 6: SwarmManager::start() — libp2p swarm with mDNS/Kad/Gossipsub
Step 7: SyncEngine::new().run() — bridges swarm events to CRDT DB
Step 8: shutdown_signal().await — blocks until SIGINT/SIGTERM
6. Graceful Shutdown
tokio::signal is used for cross-platform signal handling:
ctrl_c()for SIGINT on all platforms.signal(SignalKind::terminate())for SIGTERM on Unix (#[cfg(unix)]).- Windows only handles SIGINT (SIGTERM is not a native Win32 concept).
The shutdown sequence: SwarmHandle::shutdown() → join SyncEngine task.
7. libp2p::Multiaddr Re-export
Rather than adding a direct libp2p dependency to chronogrid_daemon,
chronogrid_net::lib.rs was extended to re-export libp2p::Multiaddr
alongside the existing libp2p::PeerId re-export. This keeps chronogrid_net
as the single dependency for all libp2p types used by downstream crates.
Consequences
Positive
- Single binary with zero runtime dependencies beyond the OS keychain service.
- First-boot experience is fully automated (key generation, schema init).
config.example.tomlprovides comprehensive documentation in the file itself.- Systemd unit uses security hardening directives (
NoNewPrivileges,ProtectSystem=strict,ReadWritePaths=/var/lib/chronogrid). Multiaddrre-export simplifies downstream crate dependency graphs.
Neutral
- Argon2id KDF runs on every boot (even when retrieving from keychain) to verify the credential. This takes ~1–2 s at 64 MiB / 3 iterations — acceptable for a daemon that starts infrequently.
Negative / Risks
- Android: The keychain module is excluded on Android (
cfg(not(target_os = "android"))). The daemon binary is not intended for Android — the Flutter KeyStore bridge (Task 1.5) handles that platform. - Passphrase-based key mode not yet exposed: The current daemon uses only
the automated node+swarm-fingerprint passphrase. A future enhancement could
add
--passphrasefor user-supplied secrets on shared-hardware deployments.
Related
- ADR-0002:
chronogrid_cryptoArgon2id KDF & OS Keychain - ADR-0003:
chronogrid_coreSQLCipher Database Initializer - ADR-0011: P2P Swarm Manager (
SwarmManager,SwarmHandle) - Task 2.8: Expand Verification Pipeline (next task)