Skip to main content

max / makenotwork

3.0 KB · 72 lines History Blame Raw
1 use crate::config::Config;
2 use crate::domain::NodeId;
3 use crate::events::EventTx;
4 use crate::topology::{Node, Topology};
5 use ops_exec::{CapabilitySet, Executor, LocalExec, SshExec};
6 use sqlx::SqlitePool;
7 use std::collections::HashMap;
8 use std::sync::Arc;
9 use tokio::sync::Mutex;
10 use tokio::task::AbortHandle;
11
12 /// Per-node executors keyed by node id, built once from the topology at
13 /// startup. The deploy path looks a node's executor up here instead of
14 /// constructing ssh/rsync invocations inline.
15 pub type ExecutorMap = HashMap<NodeId, Arc<dyn Executor>>;
16
17 /// The in-flight build pipeline: its abort handle plus the `build_runs` row it
18 /// drives. A newer `/rebuild` aborts the handle and settles the row `aborted`.
19 pub struct ActiveBuild {
20 pub handle: AbortHandle,
21 pub run_id: crate::domain::RunId,
22 }
23
24 #[derive(Clone)]
25 pub struct AppState {
26 pub pool: SqlitePool,
27 pub topo: Arc<Topology>,
28 pub cfg: Arc<Config>,
29 /// Single-slot guard for the build pipeline. A new /rebuild aborts any
30 /// in-flight build (cargo + gates) so the latest push always wins. Carries
31 /// the run id alongside the handle so the aborting `/rebuild` can settle
32 /// the superseded `build_runs` row as `aborted`.
33 pub active_build: Arc<Mutex<Option<ActiveBuild>>>,
34 /// Serializes the deploy mutators (`/promote`, `/rollback`) so their
35 /// check -> deploy -> advance sequences never interleave. Without it two
36 /// concurrent promotes (or a promote racing a rollback) could deploy mixed
37 /// versions to a node and clobber each other's `tier_state` advance
38 /// (CF3). Held for the whole handler.
39 pub deploy_lock: Arc<Mutex<()>>,
40 /// Broadcast bus for live operator events. WS /events subscribes; all
41 /// build/gate/deploy code sites emit on this.
42 pub events: EventTx,
43 /// One capability-scoped [`Executor`] per node, built from the topology.
44 pub executors: Arc<ExecutorMap>,
45 /// Bearer token required on the deploy-mutating routes (promote, rollback,
46 /// rebuild, confirm, backup/fetch). Sourced from `SANDO_API_TOKEN` (the
47 /// systemd EnvironmentFile). `None` = unauthenticated, which main() permits
48 /// only on a loopback bind (CF2).
49 pub api_token: Option<Arc<str>>,
50 }
51
52 /// Build the executor for one node: a `LocalExec` for the `local` fast-path, an
53 /// `SshExec` otherwise, each granted the node's declared capabilities (which
54 /// default to deploy+restart / observe health — the historical behavior).
55 pub fn build_executor(node: &Node) -> Arc<dyn Executor> {
56 let caps = CapabilitySet::from_tokens(&node.actuate, &node.observe);
57 if node.ssh_target == "local" || node.ssh_target.is_empty() {
58 Arc::new(LocalExec::new(caps))
59 } else {
60 Arc::new(SshExec::new(node.ssh_target.clone(), caps))
61 }
62 }
63
64 /// Build the full node → executor map from every tier's nodes.
65 pub fn build_executors(topo: &Topology) -> ExecutorMap {
66 topo.tiers
67 .iter()
68 .flat_map(|t| t.nodes.iter())
69 .map(|node| (node.name.clone(), build_executor(node)))
70 .collect()
71 }
72