# 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-daemon` in `apps/chronogrid_daemon/`. - CLI args via `clap`: `--config`, `--db`, `--log-level`. - TOML config loading with platform-default fallback. - `chronogrid_crypto` key derivation + OS keychain integration. - `chronogrid_core` database initialization. - `chronogrid_net` SwarmManager startup. - Structured logging via `tracing`. - Graceful shutdown on SIGTERM/SIGINT. - `config.example.toml` documentation. - `docs/DEPLOYMENT/01-headless-daemon-setup.md` setup 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` | Config file path (optional) | | `--db` | `Option` | 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: 1. The "passphrase" for Argon2id is synthesized from the node's `NodeId` hex + the cluster `SwarmKey` fingerprint: `"chronogrid-daemon::"`. 2. 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. 3. 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.toml` provides comprehensive documentation in the file itself. - Systemd unit uses security hardening directives (`NoNewPrivileges`, `ProtectSystem=strict`, `ReadWritePaths=/var/lib/chronogrid`). - `Multiaddr` re-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 `--passphrase` for user-supplied secrets on shared-hardware deployments. --- ## Related - ADR-0002: `chronogrid_crypto` Argon2id KDF & OS Keychain - ADR-0003: `chronogrid_core` SQLCipher Database Initializer - ADR-0011: P2P Swarm Manager (`SwarmManager`, `SwarmHandle`) - Task 2.8: Expand Verification Pipeline (next task)