chronogrid/docs/ARCHITECTURE/ADR/0012-daemon-scaffold.md

5.5 KiB
Raw Blame History

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<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:

  1. The "passphrase" for Argon2id is synthesized from the node's NodeId hex + the cluster SwarmKey fingerprint: "chronogrid-daemon:<node_id>:<fingerprint>".
  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.

  • 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)