Skip to main content

max / makenotwork

4.8 KB · 171 lines History Blame Raw
1 //! PoM CLI entry point, parses subcommands and dispatches to handlers or MCP server.
2
3 use clap::{Parser, Subcommand};
4 use rmcp::ServiceExt;
5 use tokio::io::{stdin, stdout};
6 use tracing::info;
7 use tracing_subscriber::{EnvFilter, fmt, prelude::*};
8
9 use pom::config::{self, Config};
10 use pom::db;
11 use pom::error::Result;
12 use pom::tools::PomServer;
13
14 mod cli;
15
16 #[derive(Parser)]
17 #[command(
18 name = "pom",
19 version,
20 about = "Peace of Mind: health checks and test orchestration"
21 )]
22 struct Cli {
23 /// Path to config file (default: ~/.config/pom/pom.toml)
24 #[arg(long, global = true)]
25 config: Option<std::path::PathBuf>,
26
27 #[command(subcommand)]
28 command: Option<Commands>,
29 }
30
31 #[derive(Subcommand)]
32 enum Commands {
33 /// Check health of targets
34 Health {
35 /// Target name (omit for all)
36 target: Option<String>,
37 /// Output as JSON
38 #[arg(long)]
39 json: bool,
40 },
41 /// Run tests on a target via SSH
42 Test {
43 /// Target name
44 target: String,
45 /// Filter tests
46 #[arg(long, short)]
47 filter: Option<String>,
48 /// Output as JSON
49 #[arg(long)]
50 json: bool,
51 },
52 /// Show status dashboard
53 Status {
54 /// Output as JSON
55 #[arg(long)]
56 json: bool,
57 },
58 /// Show what version each target is running, and how far behind
59 Versions {
60 /// Output as JSON
61 #[arg(long)]
62 json: bool,
63 },
64 /// View history
65 History {
66 #[command(subcommand)]
67 kind: cli::HistoryKind,
68 },
69 /// Prune old records
70 Prune {
71 /// Number of days to keep (default 30)
72 #[arg(long, default_value = "30")]
73 days: i64,
74 },
75 /// Run DNS and WHOIS checks
76 Dns {
77 /// Target name (omit for all)
78 target: Option<String>,
79 /// Output as JSON
80 #[arg(long)]
81 json: bool,
82 },
83 /// Run as a daemon, checking health at intervals
84 Serve,
85 /// Show peer mesh status
86 Mesh {
87 /// Output as JSON
88 #[arg(long)]
89 json: bool,
90 },
91 }
92
93 #[tokio::main]
94 async fn main() -> Result<()> {
95 // Install the pure-Rust ring crypto provider before any TLS operation. It
96 // verifies signatures for every certificate pom validates: reqwest (built
97 // rustls-no-provider) and the TLS-expiry check both read the process default
98 // via CryptoProvider::get_default. aws-lc-rs is kept out of the dependency
99 // tree entirely (see Cargo.toml), so its C/asm build dep and the aws-lc-sys
100 // advisory surface never reach a build host or the cert-verification path.
101 pom::tls::install_crypto_provider();
102
103 let cli = Cli::parse();
104
105 let config_path = cli.config.as_deref();
106 let config = Config::load(config_path)?;
107
108 match cli.command {
109 None => run_mcp_server(config).await,
110 Some(cmd) => run_cli(cmd, config).await,
111 }
112 }
113
114 async fn run_mcp_server(config: Config) -> Result<()> {
115 tracing_subscriber::registry()
116 .with(fmt::layer().with_writer(std::io::stderr))
117 .with(EnvFilter::from_default_env().add_directive("pom=info".parse()?))
118 .init();
119
120 info!("Starting PoM MCP server");
121
122 let db_path = config::db_path()?;
123 let pool = db::connect(&db_path).await?;
124 info!("Database ready at {}", db_path.display());
125
126 let server = PomServer::new(pool, config);
127 let transport = (stdin(), stdout());
128
129 info!("MCP server ready");
130 let service = server.serve(transport).await?;
131 let quit_reason = service.waiting().await?;
132 info!(?quit_reason, "MCP server shutting down");
133
134 Ok(())
135 }
136
137 async fn run_cli(cmd: Commands, config: Config) -> Result<()> {
138 let log_level = if matches!(cmd, Commands::Serve) {
139 "pom=info"
140 } else {
141 "pom=warn"
142 };
143 tracing_subscriber::registry()
144 .with(fmt::layer().with_writer(std::io::stderr))
145 .with(EnvFilter::from_default_env().add_directive(log_level.parse()?))
146 .init();
147
148 let db_path = config::db_path()?;
149 let pool = db::connect(&db_path).await?;
150
151 match cmd {
152 Commands::Health { target, json } => {
153 cli::cmd_health(&pool, &config, target.as_deref(), json).await
154 }
155 Commands::Test {
156 target,
157 filter,
158 json,
159 } => cli::cmd_test(&pool, &config, &target, filter.as_deref(), json).await,
160 Commands::Status { json } => cli::cmd_status(&pool, &config, json).await,
161 Commands::Versions { json } => cli::cmd_versions(&pool, &config, json).await,
162 Commands::History { kind } => cli::cmd_history(&pool, kind).await,
163 Commands::Prune { days } => cli::cmd_prune(&pool, days).await,
164 Commands::Dns { target, json } => {
165 cli::cmd_dns(&pool, &config, target.as_deref(), json).await
166 }
167 Commands::Serve => cli::cmd_serve(&pool, &config).await,
168 Commands::Mesh { json } => cli::cmd_mesh(&config, json).await,
169 }
170 }
171