use clap::Parser;
use holochain::conductor::config::ConductorConfig;
use holochain::conductor::manager::handle_shutdown;
use holochain::conductor::Conductor;
use holochain::conductor::ConductorHandle;
use holochain_conductor_api::conductor::paths::DataRootPath;
use holochain_conductor_api::conductor::process::ERROR_CODE;
use holochain_conductor_api::conductor::ConductorConfigError;
use holochain_conductor_api::config::conductor::paths::ConfigRootPath;
use holochain_conductor_api::config::conductor::KeystoreConfig;
use holochain_trace::Output;
use holochain_util::tokio_helper;
#[cfg(unix)]
use sd_notify::{notify, NotifyState};
use std::path::PathBuf;
use tracing::*;
const MAGIC_CONDUCTOR_READY_STRING: &str = "Conductor ready.";
#[derive(Debug, Parser)]
#[clap(version, about, long_about = None)]
struct Opt {
#[arg(long, default_value_t = Output::Log)]
structured: Output,
#[arg(long, short = 'c')]
config_path: Option<PathBuf>,
#[arg(long)]
config_schema: bool,
#[arg(long, short = 'p')]
pub piped: bool,
#[arg(long)]
build_info: bool,
#[arg(long)]
create_config: bool,
#[arg(long)]
pub danger_print_db_secrets: bool,
}
fn main() {
if rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.is_err()
{
tracing::error!("could not set cyrpto provider for tls");
}
tokio_helper::block_forever_on(async_main());
}
async fn async_main() {
human_panic::setup_panic!();
let opt = Opt::parse();
if opt.build_info {
println!("{}", option_env!("BUILD_INFO").unwrap_or("{}"));
return;
}
if opt.config_schema {
let schema = schemars::schema_for!(ConductorConfig);
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
println!("{schema_string}");
return;
}
if opt.create_config {
holochain_conductor_config::generate::generate(
None,
std::env::current_dir().ok(),
None,
true,
0,
#[cfg(feature = "chc")]
None,
)
.inspect_err(|e| tracing::error!("Failed to generate configurations: {}", e))
.unwrap();
return;
}
let config_path = opt.config_path.clone().map(ConfigRootPath::from);
let config = load_config(config_path);
if let Some(t) = &config.tracing_override {
std::env::set_var("CUSTOM_FILTER", t);
}
holochain_trace::init_fmt(opt.structured.clone()).expect("Failed to start contextual logging");
debug!("holochain_trace initialized");
let data_root_path: DataRootPath = config.data_root_path_or_die();
holochain_metrics::HolochainMetricsConfig::new_from_env_vars(data_root_path.as_ref())
.init()
.await;
info!("Conductor startup: metrics loop spawned.");
let conductor = conductor_handle_from_config(&opt, config).await;
info!("Conductor successfully initialized.");
println!("{MAGIC_CONDUCTOR_READY_STRING}");
#[cfg(unix)]
let _ = notify(true, &[NotifyState::Ready]);
tokio::signal::ctrl_c()
.await
.unwrap_or_else(|e| tracing::error!("Could not handle termination signal: {:?}", e));
tracing::info!("Gracefully shutting down conductor...");
let shutdown_result = conductor.shutdown().await;
handle_shutdown(shutdown_result);
}
async fn conductor_handle_from_config(opt: &Opt, config: ConductorConfig) -> ConductorHandle {
let passphrase = match &config.keystore {
KeystoreConfig::DangerTestKeystore => None,
KeystoreConfig::LairServer { .. } | KeystoreConfig::LairServerInProc { .. } => {
if opt.piped {
holochain_util::pw::pw_set_piped(true);
}
Some(holochain_util::pw::pw_get().unwrap())
}
};
let env_path = config.data_root_path_or_die();
if !env_path.is_dir() {
let result = std::fs::create_dir_all(env_path.as_ref());
match result {
Ok(()) => println!("Created database at {}.", env_path.display()),
Err(e) => {
println!("Couldn't create database: {e}");
std::process::exit(ERROR_CODE);
}
}
}
match Conductor::builder()
.config(config)
.passphrase(passphrase)
.danger_print_db_secrets(opt.danger_print_db_secrets)
.build()
.await
{
Err(err) => panic!("Could not initialize Conductor from configuration: {err:?}"),
Ok(res) => res,
}
}
fn load_config(maybe_config_root_path: Option<ConfigRootPath>) -> ConductorConfig {
if let Some(ref config_root_path) = maybe_config_root_path {
match ConductorConfig::load_yaml(config_root_path.as_ref()) {
Err(ConductorConfigError::ConfigMissing(_)) => {
display_friendly_missing_config_message(maybe_config_root_path.as_ref());
std::process::exit(ERROR_CODE);
}
Err(ConductorConfigError::SerializationError(err)) => {
display_friendly_malformed_config_message(config_root_path, err);
std::process::exit(ERROR_CODE);
}
result => result.expect("Could not load conductor config"),
}
} else {
display_friendly_missing_config_message(maybe_config_root_path.as_ref());
std::process::exit(ERROR_CODE);
}
}
fn display_friendly_missing_config_message(maybe_config_root_path: Option<&ConfigRootPath>) {
if let Some(config_root_path) = maybe_config_root_path {
println!(
"
Error: You asked to load configuration from the path:
{path}
but this file doesn't exist. Please create a YAML config file at this path or run the following
command to generate starter configurations.
holochain --create-config
",
path = config_root_path.display(),
);
} else {
println!(
"
Error: You tried to load a conductor config file, but didn't specify a path.
Please run this command again with the -c flag, like this:
holochain -c path/to/conductor-config.yml
"
);
}
}
fn display_friendly_malformed_config_message(
config_root_path: &ConfigRootPath,
error: serde_yaml::Error,
) {
println!(
"
The specified config file ({})
could not be parsed, because it is not valid YAML. Please check and fix the
file. Details:
{}
",
config_root_path.display(),
error
)
}