Skip to main content

koi_certmesh/
core_setup.rs

1//! Construction, wiring, and route/subscription accessors for CertmeshCore.
2//!
3//! Part of the inherent impl CertmeshCore, split from lib.rs (certmesh M2).
4//! As a child module of the crate root, 'use super::*' inherits lib.rs's
5//! imports, sibling modules, and crate-private state/helpers as in the original.
6use super::*;
7
8impl CertmeshCore {
9    /// Construct a facade from an existing shared state.
10    pub(crate) fn from_state(state: Arc<CertmeshState>) -> Self {
11        Self { state }
12    }
13
14    /// The resolved filesystem paths this core operates on.
15    ///
16    /// The data root is resolved once at the composition root and injected
17    /// via the `*_with_paths` constructors; every operation reads it from
18    /// here. There is no ambient fallback.
19    pub fn paths(&self) -> &CertmeshPaths {
20        &self.state.paths
21    }
22
23    /// Create a new CertmeshCore with an unlocked CA and explicit paths.
24    pub fn new_with_paths(
25        ca: ca::CaState,
26        roster: Roster,
27        auth_state: Option<AuthState>,
28        paths: CertmeshPaths,
29    ) -> Self {
30        let rate_limiter = load_rate_limiter(&paths);
31        let posture_tx = initial_posture_tx(&paths);
32        Self {
33            state: Arc::new(CertmeshState {
34                paths,
35                ca: tokio::sync::Mutex::new(Some(ca)),
36                roster: tokio::sync::Mutex::new(roster),
37                auth: tokio::sync::Mutex::new(auth_state),
38                pending_challenge: tokio::sync::Mutex::new(None),
39                rate_limiter: tokio::sync::Mutex::new(rate_limiter),
40                approval_tx: tokio::sync::Mutex::new(None),
41                event_tx: koi_common::events::event_channel().0,
42                posture_tx,
43                renewal_failure_count: std::sync::atomic::AtomicU32::new(0),
44            }),
45        }
46    }
47
48    /// Create a CertmeshCore in locked state with explicit paths.
49    pub fn locked_with_paths(roster: Roster, paths: CertmeshPaths) -> Self {
50        let rate_limiter = load_rate_limiter(&paths);
51        let posture_tx = initial_posture_tx(&paths);
52        Self {
53            state: Arc::new(CertmeshState {
54                paths,
55                ca: tokio::sync::Mutex::new(None),
56                roster: tokio::sync::Mutex::new(roster),
57                auth: tokio::sync::Mutex::new(None),
58                pending_challenge: tokio::sync::Mutex::new(None),
59                rate_limiter: tokio::sync::Mutex::new(rate_limiter),
60                approval_tx: tokio::sync::Mutex::new(None),
61                event_tx: koi_common::events::event_channel().0,
62                posture_tx,
63                renewal_failure_count: std::sync::atomic::AtomicU32::new(0),
64            }),
65        }
66    }
67
68    /// Create a CertmeshCore in uninitialized state with explicit paths.
69    ///
70    /// HTTP routes are still mounted so `/create` is reachable on a fresh install.
71    /// All operations that require an initialized CA will return `CaNotInitialized`.
72    pub fn uninitialized_with_paths(paths: CertmeshPaths) -> Self {
73        let rate_limiter = load_rate_limiter(&paths);
74        let posture_tx = initial_posture_tx(&paths);
75        Self {
76            state: Arc::new(CertmeshState {
77                paths,
78                ca: tokio::sync::Mutex::new(None),
79                roster: tokio::sync::Mutex::new(Roster::empty()),
80                auth: tokio::sync::Mutex::new(None),
81                pending_challenge: tokio::sync::Mutex::new(None),
82                rate_limiter: tokio::sync::Mutex::new(rate_limiter),
83                approval_tx: tokio::sync::Mutex::new(None),
84                event_tx: koi_common::events::event_channel().0,
85                posture_tx,
86                renewal_failure_count: std::sync::atomic::AtomicU32::new(0),
87            }),
88        }
89    }
90
91    /// Build the HTTP router for this domain.
92    ///
93    /// The binary crate mounts this at `/v1/certmesh/`.
94    pub fn routes(&self) -> Router {
95        http::routes(Arc::clone(&self.state))
96    }
97
98    /// Build the HTTP router for external embedding.
99    ///
100    /// This mirrors `routes()` but avoids exposing CertmeshState.
101    pub fn http_routes(&self) -> Router {
102        http::routes(Arc::clone(&self.state))
103    }
104
105    /// Build the inter-node router for the mTLS listener.
106    ///
107    /// Contains only routes that require mutual TLS between mesh members:
108    /// promote, health, renew, roster, set-hook.
109    pub fn inter_node_routes(&self) -> Router {
110        http::inter_node_routes(Arc::clone(&self.state))
111    }
112
113    /// Set the approval channel used for enrollment approvals.
114    pub async fn set_approval_channel(&self, tx: mpsc::Sender<ApprovalRequest>) {
115        *self.state.approval_tx.lock().await = Some(tx);
116    }
117
118    /// Subscribe to certmesh events.
119    pub fn subscribe(&self) -> broadcast::Receiver<CertmeshEvent> {
120        self.state.event_tx.subscribe()
121    }
122
123    /// Watch this node's posture (ADR-020 §5). The receiver always holds the
124    /// current [`Posture`] (so a new subscriber reads it immediately) and is
125    /// notified on every Open↔Authenticated transition — the signal a listener
126    /// supervisor uses to flip plain↔mTLS without polling. Transitions are also
127    /// surfaced as `KoiEvent::PostureChanged` by the embedded facade.
128    pub fn watch_posture(&self) -> watch::Receiver<Posture> {
129        self.state.posture_tx.subscribe()
130    }
131
132    /// Build the RFC 8555 ACME server state over this CA.
133    ///
134    /// The binary calls this when starting the dedicated server-auth TLS
135    /// listener, passing the ACME base URL, the Koi DNS zone, and the
136    /// `AcmeDnsSolver` bridge. The returned `AcmeState` shares this core's CA and
137    /// roster (so ACME issuance lands in the same roster as TOTP enrollment), and
138    /// is mounted via [`acme::routes`].
139    pub fn acme_state(&self, config: acme::AcmeStateConfig) -> std::sync::Arc<acme::AcmeState> {
140        acme::AcmeState::new(Arc::clone(&self.state), config)
141    }
142}