mod config; mod service; mod stratis; use std::{ fs::{File, Permissions}, os::unix::fs::PermissionsExt, }; use bpaf::Bpaf; use camino::Utf8PathBuf; use tokio::net::UnixListener; use tokio_stream::wrappers::UnixListenerStream; use tonic::transport::Server; use tonic_health::pb::{HealthCheckRequest, health_client::HealthClient}; use tracing::{info, warn}; use tracing_subscriber::{EnvFilter, prelude::*}; use crate::{ config::Config, service::{ Service, lvmd::{lv_service_server::LvServiceServer, vg_service_server::VgServiceServer}, }, }; #[derive(Default, Debug, Clone, Bpaf)] struct OptionsServe { #[bpaf(hide)] unsafe_world_writable_socket: bool, } #[derive(Debug, Clone, Bpaf)] enum OptionsCommand { #[bpaf(hide, command)] Serve(#[bpaf(external(options_serve))] OptionsServe), #[bpaf(command)] Health, } impl Default for OptionsCommand { fn default() -> Self { Self::Serve(Default::default()) } } #[derive(Debug, Clone, Bpaf)] #[bpaf(options)] struct Options { #[bpaf(fallback("/etc/topolvm/lvmd.yaml".into()))] config: Utf8PathBuf, #[bpaf(external(options_command), fallback(OptionsCommand::default()))] command: OptionsCommand, } #[tokio::main] async fn main() -> Result<(), Box> { let opts = options().run(); tracing_subscriber::registry() .with(tracing_subscriber::fmt::layer().json()) .with(EnvFilter::from_default_env()) .init(); info!(config = %opts.config, "loading config"); let mut config_file = File::open(&opts.config)?; let config: Config = serde_norway::from_reader(&mut config_file)?; match opts.command { OptionsCommand::Health => { info!(socket = %config.socket_name, "connecting to socket"); let chan = tonic::transport::Endpoint::try_from(format!("unix://{}", config.socket_name))? .connect() .await?; info!("performing health check"); let mut client = HealthClient::new(chan); client .check(HealthCheckRequest { service: "".to_owned(), }) .await?; } OptionsCommand::Serve(opts) => { info!(socket = %config.socket_name, "binding to socket"); if let Some(parent) = config.socket_name.parent() { std::fs::create_dir_all(parent)?; } std::fs::remove_file(&config.socket_name).or_else(|err| { if err.kind() == std::io::ErrorKind::NotFound { Ok(()) } else { Err(err) } })?; let sock = UnixListener::bind(&config.socket_name)?; if opts.unsafe_world_writable_socket { warn!("setting socket to be WORLD-WRITABLE for tests!"); std::fs::set_permissions(&config.socket_name, Permissions::from_mode(0o666))?; } info!("connecting to system bus"); let conn = zbus::Connection::system().await?; let service = Service::start(config, conn).await?; let (health_reporter, health_service) = tonic_health::server::health_reporter(); health_reporter .set_serving::>() .await; health_reporter .set_serving::>() .await; info!("serving!"); Server::builder() .add_service(LvServiceServer::from_arc(service.clone())) .add_service(VgServiceServer::from_arc(service)) .add_service(health_service) .serve_with_incoming(UnixListenerStream::new(sock)) .await?; } } Ok(()) }