// SPDX-License-Identifier: Apache-2.0 use clap::builder::PossibleValue; use clap::{Arg, Command}; use detee_cli::general::cli_handler::{ handle_account, handle_completion, handle_operators, handle_packagers, }; use detee_cli::sgx::cli_handler::{handle_app, handle_app_nodes}; use detee_cli::snp::cli_handler::{handle_vm, handle_vm_nodes}; use detee_cli::*; const ABOUT: &str = r#"The DeTEE CLI allows you to manage and deploy applications and virtual machines. All software runs within Trusted Execution Environments on a distributed network. More information can be found at https://detee.ltd Feel free to browser applications bundles or VM disks available for immediate deployment."#; shadow_rs::shadow!(build); fn main() { // TODO: figure if there is a more elegant way to solve this than calling default_provider in main let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); let log_level = match std::env::var("LOG_LEVEL") { Ok(val) => match val.as_str() { "DEBUG" => log::LevelFilter::Debug, "INFO" => log::LevelFilter::Info, _ => log::LevelFilter::Error, }, _ => log::LevelFilter::Warn, }; env_logger::builder().filter_level(log_level).format_timestamp(None).init(); let cmd = clap_cmd(); let matches = cmd.clone().get_matches(); match matches.get_one::("format").unwrap().as_str() { "json" => std::env::set_var("FORMAT", "JSON"), "yaml" => std::env::set_var("FORMAT", "YAML"), _ => (), } match matches.subcommand() { Some(("completion", subcom_args)) => handle_completion(subcom_args, cmd), Some(("app", subcom_args)) => handle_app(subcom_args), Some(("app-node", subcom_args)) => handle_app_nodes(subcom_args), Some(("vm", subcom_args)) => handle_vm(subcom_args), Some(("vm-node", subcom_args)) => handle_vm_nodes(subcom_args), Some(("operator", subcom_args)) => handle_operators(subcom_args), Some(("packager", subcom_args)) => cli_print(handle_packagers(subcom_args)), Some(("account", subcom_args)) => handle_account(subcom_args), _ => { println!("No valid subcommand provided. Use --help for more information."); std::process::exit(0); } } } fn clap_cmd() -> Command { let snp_locations = [ PossibleValue::new("GB").help("London, England, GB"), PossibleValue::new("Canada").help("Montréal or Vancouver"), PossibleValue::new("Montreal").help("Montréal, Quebec, CA"), PossibleValue::new("Vancouver").help("Vancouver, British Columbia, CA"), PossibleValue::new("California").help("San Jose, California, US"), PossibleValue::new("US").help("San Jose, California, US"), PossibleValue::new("France").help("Paris, Île-de-France, FR"), PossibleValue::new("Any").help("List offers for any location."), ]; Command::new("detee-cli") .version(build::CLAP_LONG_VERSION) .author("https://detee.ltd") .about(ABOUT) .arg( Arg::new("format") .help("format output as JSON or YAML") .long("format") .default_value("human") .value_parser(["human", "json", "yaml"]) ) .subcommand(Command::new("completion") .about("generates shell completion scripts") .arg( Arg::new("shell") .help("The shell to generate the script for") .value_parser(["bash", "zsh", "fish"]) .required(true), ) ) .subcommand(Command::new("app") .about("a lightweight service that run on Intel SGX") /* .subcommand( Command::new("package") .about("package new app from x86_64-linux-musl binary") .arg( Arg::new("package-type") .long("package-type") .help("Enclave package type") .long_help( "Type of package, public or private. public packages are signed by DeTEE,".to_string() + "Public packages are used wellknown mr_enclave for hratls connection into enclave." ) .default_value("public") .value_parser(["public", "private"]) ) .arg( Arg::new("files") .help("List of binaries to package") .long_help("List of x86_64 linux musl binaries to package") .num_args(1..) .required(true) .value_name("FILE") .value_delimiter(' ') )) */ .subcommand( Command::new("deploy") .about("create new app from a YAML configuration file") /* .arg( Arg::new("yaml-path") .long("from-yaml") .help("allows extended config through App deploy config yaml") .long_help("Allows extended configuration options, including:".to_owned() + "\n- deploying to a specific node or to a specific city.") .exclusive(true) ) */ .arg( Arg::new("vcpus") .long("vcpus") .default_value("1") .value_parser(clap::value_parser!(u32).range(1..64)) .help("the number of vCPUs") ) .arg( Arg::new("memory") .long("memory") .default_value("1500") .value_parser(clap::value_parser!(u32).range(1000..8000)) .help("memory in MB") ) .arg( Arg::new("disk") .long("disk") .default_value("2") .value_parser(clap::value_parser!(u32).range(1..100)) .help("disk size in GB") ) .arg( Arg::new("port") .long("expose-port") .value_parser(clap::value_parser!(u32).range(0..65535)) .action(clap::ArgAction::Append) .help("Application exposing port") .long_help("Port to expose on the application which mapped into the host's public IP's port") ) .arg( Arg::new("package") .long("package") .help("Enclave package name") .default_value("base-package") .value_parser(["base-package", "actix-static-server", "actix-app-info", "go-app-info"]) ) .arg( Arg::new("name") .long("name") .help("app name") ) .arg( Arg::new("hours") .long("hours") .short('h') .help("for how many hours should the app run") .default_value("1") .value_parser(clap::value_parser!(u64).range(1..5000)) .long_help("How long should the app run for so it locks up credits accordingly") ) .arg( Arg::new("price") .long("price") .help("maxium accepted price per unit per minute") .default_value("4000") .value_parser(clap::value_parser!(u64).range(1..50000000)) ) .arg( Arg::new("location") .help("deploy to a specific location") .long("location") .default_value("Any") .value_parser([ PossibleValue::new("DE").help("Frankfurt am Main, Hesse, Germany"), PossibleValue::new("Any").help("List offers for any location."), ]), ) .arg( Arg::new("env") .short('e') .long("env") .help("env override") .long_help("environment variable override on launch config") .action(clap::ArgAction::Append) ) .arg( Arg::new("arg") .long("arg") .help("arg override") .long_help("application arguement variable override on launch config") .action(clap::ArgAction::Append) ) ) .subcommand(Command::new("inspect").about("list all available information about a App") .arg( Arg::new("id") .help("supply the long ID of the App contract") .required(true) ) .arg( Arg::new("show-node") .long("show-node") .help("inspect the node that is runnnig this App") .action(clap::ArgAction::SetTrue) ) ) .subcommand( Command::new("delete") .about("delete deployed app") .arg( Arg::new("id") .help("supply the long ID of the App contract") .required(true) ) ) .subcommand( Command::new("config") .about("App launch config in a YAML file") .long_about("The YAML configuration file for the environment variables, child processes, restart policy and arguments for the app") .subcommand( Command::new("validate") .about("Validate a YAML configuration file") .arg( Arg::new("config") .help("App config yaml file path to validate") .long_help("Validate YAML configuration file for the app which you want to run in the enclave") .long("from-yaml") .required(true) ) ) .subcommand( Command::new("update") .about("App launch config yaml file path to update in enclave") .long_about("Update the YAML configuration file for the app which you want to run in the enclave") .arg( Arg::new("config") .help("Path to yaml file") .long("from-yaml") .required(true) ) .arg( Arg::new("id") .help("supply the long ID of the App contract") .required(true) ) ) .subcommand( Command::new("get") .about("download current launch configuration file from enclave") .arg( Arg::new("path") .help("path to save launch config") .long_help("path to save current launch config from enclave as yaml") .required(true), ) .arg( Arg::new("id") .help("supply the ID of the App contract") .required(true) ) ) ) .subcommand( Command::new("list") .about("list all deployed apps") .long_about("List all the deployed apps in the enclave") .arg( Arg::new("as-operator") .long("as-operator") .help("list Apps running on your SGX nodes") .action(clap::ArgAction::SetTrue) ) ) ) .subcommand(Command::new("app-node") .about("info about Intel SGX servers registerd to DeTEE") .subcommand(Command::new("search").about("search nodes based on filters")) .subcommand(Command::new("inspect").about("get detailed information about a node") .arg( Arg::new("ip") .long("ip") .help("main IP of the node your want to inspect") .required(true) ) ) .subcommand(Command::new("report").about("report a node for poor performance") .arg( Arg::new("pubkey") .long("pubkey") .help("public key of the node you are reporting") .required(true) ) .arg( Arg::new("contract") .long("contract") .help("ID of the active contract with this node") .required(true) ) .arg( Arg::new("reason") .long("reason") .help("detail the performance issue you experienced") ) ) ) .subcommand(Command::new("vm") .about("virtual machines that run on AMD SEV-SNP nodes") .subcommand(Command::new("deploy").about("deploy a VM on the DeTEE network") .arg( Arg::new("yaml-path") .long("from-yaml") .help("allows extended config through yaml") .long_help("Allows extended configuration options, including:".to_owned() + "\n- deploying to a specific node or to a specific city." + "\n- specifying the kernel and dtrfs version." + "\n- configuring extra ports to publish for a VM no public IPv4" + "\n- request a public IPv6 address for yout VM" + "\nSamples can be found in ~/.detee/samples/new_vm/") .exclusive(true) ) .arg( Arg::new("location") .help("deploy to a specific location") .long("location") .default_value("Any") .value_parser(snp_locations.clone()), ) .arg( Arg::new("vcpus") .long("vcpus") .default_value("1") .value_parser(clap::value_parser!(u32).range(1..64)) .help("the number of vCPUs") ) .arg( Arg::new("memory") .long("memory") .default_value("1") .value_parser(clap::value_parser!(u32).range(1..500)) .help("memory in GiB") ) .arg( Arg::new("disk") .long("disk") .default_value("10") .value_parser(clap::value_parser!(u32).range(5..4000)) .help("disk size in GiB") ) .arg( Arg::new("distribution") .long("distro") .help("GNU/Linux distribution") .default_value("arch") .value_parser(["arch", "ubuntu", "fedora"]) ) .arg( Arg::new("hours") .long("hours") .help("for how many hours should the VM run") .default_value("1") .value_parser(clap::value_parser!(u32).range(1..5000)) ) .arg( Arg::new("price") .long("price") .help("maxium accepted price per unit per minute") .default_value("4000") .value_parser(clap::value_parser!(u64).range(1..50000000)) ) .arg( Arg::new("hostname") .long("hostname") .help("hostname of you VM and OS") ) .arg( Arg::new("public-ip") .long("public-ip") .help("get a public IPv4 address for this VM") .action(clap::ArgAction::SetTrue) .conflicts_with("port") ) .arg( Arg::new("port") .long("expose-port") .value_parser(clap::value_parser!(u32).range(0..65535)) .action(clap::ArgAction::Append) .help("vm exposing port") .long_help("Port to expose on the vm which mapped into the host's public IP's port") .conflicts_with("public-ip") ) ) .subcommand(Command::new("inspect").about("list all available information about a VM") .arg( Arg::new("id") .help("supply the long ID of the VM contract") .required(true) ) .arg( Arg::new("show-node") .long("show-node") .help("inspect the node that is runnnig this VM") .action(clap::ArgAction::SetTrue) ) ) .subcommand(Command::new("ssh").about("connect to the VM using SSH") .arg( Arg::new("id") .help("supply the ID of the VM contract") .required(true) ) .arg( Arg::new("just-print") .long("just-print") .help("print the command instead of connecting") .action(clap::ArgAction::SetTrue) ) ) .subcommand(Command::new("list").about("list all existing VM contracts") .arg( Arg::new("as-operator") .long("as-operator") .help("list VMs running on your SNP nodes") .action(clap::ArgAction::SetTrue) ) ) .subcommand(Command::new("delete").about("delete a VM from the DeTEE network") .arg( Arg::new("id") .help("the (long) ID of the VM that you wish to delete") .required(true) ) ) .subcommand(Command::new("update") .about("update the hardware or the lifetime of a VM") .long_about("Allows you to update the hardware or the lifetime".to_string() + "\nAny hardware modifiations will restart the VM." + "\nChanging the lifetime of a VM will not restart." + "\nIf changing the lifetime to a higher value, credits will locked accordingly.") .arg( Arg::new("id") .help("supply the ID of the VM you wish to upgrade") .required(true) ) .arg( Arg::new("hostname") .long("hostname") .default_value("") .help("change the hostname within the smart contract") ) .arg( Arg::new("vcpus") .long("vcpus") .default_value("0") .value_parser(clap::value_parser!(u32).range(0..100)) .help("modify the number of vCPUs") ) .arg( Arg::new("memory") .long("memory") .default_value("0") .value_parser(clap::value_parser!(u32).range(0..5000)) .help("modify the GiB of memory reserved") ) .arg( Arg::new("disk") .long("disk") .default_value("0") .value_parser(clap::value_parser!(u32).range(0..500)) .help("increase the size of the disk in GiB") ) .arg( Arg::new("hours") .long("hours") .help("delete vm after the time expires") .default_value("0") .value_parser(clap::value_parser!(u32).range(0..5000)) ) .arg( Arg::new("dtrfs") .long("dtrfs") .help(r#""update kernel to "latest" or custom""#) .long_help("Reboot the VM and upgrade dtrfs together with the kernel.\n".to_owned() + r#"Specify "latest" to simply upgrade to the latest kernel"# + "\nAlternatively, you can specify full path to DTRFS config." + "\nSamples can be found in ~/.detee/samples/dtrfs/") .default_value("") ) ) .subcommand(Command::new("distro") .about("GNU/Linux distributions for Virtual Machines") .subcommand( Command::new("search").about("search public registry for distributions"), ) ) .subcommand(Command::new("dtrfs") .about("DeTEE initramfs archives required to boot a VM") .subcommand(Command::new("list").about("list locally stored dtrfs")) ) ) .subcommand(Command::new("vm-node") .about("info about AMD SEV-SNP servers registerd to DeTEE") .subcommand(Command::new("search").about("search nodes based on filters") .arg( Arg::new("location") .help("deploy to a specific location") .long("location") .default_value("Any") .value_parser(snp_locations.clone()), ) ) .subcommand(Command::new("offers").about("search nodes based on filters") .arg( Arg::new("location") .help("deploy to a specific location") .long("location") .default_value("Any") .value_parser(snp_locations), ) ) .subcommand(Command::new("inspect").about("get detailed information about a node") .arg( Arg::new("ip") .long("ip") .help("main IP of the node your want to inspect") .required(true) ) ) .subcommand(Command::new("report").about("report a node for poor performance") .arg( Arg::new("pubkey") .long("pubkey") .help("public key of the node you are reporting") .required(true) ) .arg( Arg::new("contract") .long("contract") .help("ID of the active contract with this node") .required(true) ) .arg( Arg::new("reason") .long("reason") .help("detail the performance issue you experienced") ) ) ) .subcommand(Command::new("operator") .about("node operators host nodes on the DeTEE network") .subcommand(Command::new("list").about("list operators currently offering servers")) .subcommand(Command::new("register").about("register as a node operator to boost rewards") .arg( Arg::new("escrow") .long("escrow") .help("At least 5000 credits is required as escrow") .long_help("Escrow is used by node operators to guarantee quality.".to_owned() + "\nBefore adding escrow, make sure you booted a node under your account." + "\nWhen all your nodes got decomissioned, your escrow gets automatically returned.") .default_value("5000") .value_parser(clap::value_parser!(u64).range(0..100000)) ) .arg( Arg::new("email") .long("email") .help("email address must be supplied as contact point") .required(true) ) ) .subcommand(Command::new("inspect").about("inspect yourself or other opertors") .arg( Arg::new("wallet") .long("wallet") .help("specify wallet address of the operator") ) ) .subcommand(Command::new("kick").about("terminate a contract (refund up to 1 week)") .arg( Arg::new("contract") .long("contract") .help("unique ID of the contract you wish to kick") ) .arg( Arg::new("reason") .long("reason") .help("optionally mention why you made this decission") ) ) .subcommand(Command::new("ban-user").about("ban a user from your services") .arg( Arg::new("wallet") .long("wallet") .required(true) .help("wallet address of the user you are banning") ) ) .subcommand(Command::new("decom").about("announce that a node is going offline") .arg( Arg::new("pubkey") .long("pubkey") .help("public key of the node you are decomissioning") .required(true) ) .arg( Arg::new("days") .long("days") .help("days left till the node will go offline") .value_parser(clap::value_parser!(u64).range(7..365)) ) ) ) .subcommand(Command::new("packager") .about("show a list of software packagers") ) .subcommand(Command::new("account") .about("account and security data for this CLI") .subcommand(Command::new("show").about("show account data associated with this CLI"), ) .subcommand(Command::new("sign").about("sign a file using your DeTEE key") .arg( Arg::new("path") .help("supply path to your public SSH key") .required(true) ) ) .subcommand(Command::new("ssh-pubkey-path").about("define the SSH pubkey that you want to use") .arg( Arg::new("path") .help("supply path to your public SSH key") .required(true) ) ) .subcommand(Command::new("network").about("specify if you connect to testnet or staging") .arg( Arg::new("name") .help("the name of the network that you are connecting to") .required(true) ) ) ) }