use crate::*; use anyhow::anyhow; use clap::Parser; use db::DatabaseCidr; use dialoguer::{theme::ColorfulTheme, Input}; use indoc::printdoc; use publicip::Preference; use rusqlite::{params, Connection}; use shared::{prompts, CidrContents, Endpoint, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS}; use wireguard_control::KeyPair; fn create_database>( database_path: P, ) -> Result> { let conn = Connection::open(&database_path)?; conn.pragma_update(None, "foreign_keys", &1)?; conn.execute(db::peer::CREATE_TABLE_SQL, params![])?; conn.execute(db::association::CREATE_TABLE_SQL, params![])?; conn.execute(db::cidr::CREATE_TABLE_SQL, params![])?; conn.pragma_update(None, "user_version", &db::CURRENT_VERSION)?; log::debug!("set database version to db::CURRENT_VERSION"); Ok(conn) } #[derive(Debug, Default, Clone, PartialEq, Parser)] pub struct InitializeOpts { /// The network name (ex: evilcorp) #[clap(long)] pub network_name: Option, /// The network CIDR (ex: 10.42.0.0/16) #[clap(long)] pub network_cidr: Option, /// This server's external endpoint (ex: 100.100.100.100:51820) #[clap(long, conflicts_with = "auto-external-endpoint")] pub external_endpoint: Option, /// Auto-resolve external endpoint #[clap(long = "auto-external-endpoint")] pub auto_external_endpoint: bool, /// Port to listen on (for the WireGuard interface) #[clap(long)] pub listen_port: Option, } struct DbInitData { network_name: String, network_cidr: IpNetwork, server_cidr: IpNetwork, our_ip: IpAddr, public_key_base64: String, endpoint: Endpoint, } fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(), Error> { const SERVER_NAME: &str = "innernet-server"; let root_cidr = DatabaseCidr::create( conn, CidrContents { name: db_init_data.network_name.clone(), cidr: db_init_data.network_cidr, parent: None, }, ) .map_err(|_| anyhow!("failed to create root CIDR"))?; let server_cidr = DatabaseCidr::create( conn, CidrContents { name: SERVER_NAME.into(), cidr: db_init_data.server_cidr, parent: Some(root_cidr.id), }, ) .map_err(|_| anyhow!("failed to create innernet-server CIDR"))?; let _me = DatabasePeer::create( conn, PeerContents { name: SERVER_NAME.parse().map_err(|e: &str| anyhow!(e))?, ip: db_init_data.our_ip, cidr_id: server_cidr.id, public_key: db_init_data.public_key_base64, endpoint: Some(db_init_data.endpoint), is_admin: true, is_disabled: false, is_redeemed: true, persistent_keepalive_interval: Some(PERSISTENT_KEEPALIVE_INTERVAL_SECS), invite_expires: None, candidates: vec![], }, ) .map_err(|_| anyhow!("failed to create innernet peer."))?; Ok(()) } pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Error> { let theme = ColorfulTheme::default(); shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(|_| { anyhow!( "Failed to create config and database directories {}", "(are you not running as root?)".bold() ) })?; printdoc!( "\nTime to setup your innernet network. Your network name can be any hostname-valid string, i.e. \"evilcorp\", and your network CIDR should be in the RFC1918 IPv4 (10/8, 172.16/12, or 192.168/16), or RFC4193 IPv6 (fd00::/8) ranges. The external endpoint specified is a : string that is the address clients will connect to. It's up to you to forward/open ports in your routers/firewalls as needed. For more usage instructions, see https://github.com/tonarino/innernet#usage \n" ); let name: Interface = if let Some(name) = opts.network_name { name } else { Input::with_theme(&theme) .with_prompt("Network name") .interact()? }; let root_cidr: IpNetwork = if let Some(cidr) = opts.network_cidr { cidr } else { Input::with_theme(&theme) .with_prompt("Network CIDR") .with_initial_text("10.42.0.0/16") .interact()? }; let listen_port: u16 = if let Some(listen_port) = opts.listen_port { listen_port } else { Input::with_theme(&theme) .with_prompt("Listen port") .default(51820) .interact() .map_err(|_| anyhow!("failed to get listen port."))? }; log::info!("listen port: {}", listen_port); let endpoint: Endpoint = if let Some(endpoint) = opts.external_endpoint { endpoint } else if opts.auto_external_endpoint { let ip = publicip::get_any(Preference::Ipv4) .ok_or_else(|| anyhow!("couldn't get external IP"))?; SocketAddr::new(ip, listen_port).into() } else { prompts::ask_endpoint(listen_port)? }; let our_ip = root_cidr .iter() .find(|ip| root_cidr.is_assignable(*ip)) .unwrap(); let server_cidr = IpNetwork::new(our_ip, root_cidr.max_prefix())?; let config_path = conf.config_path(&name); let our_keypair = KeyPair::generate(); let config = ConfigFile { private_key: our_keypair.private.to_base64(), listen_port, address: our_ip, network_cidr_prefix: root_cidr.prefix(), }; config.write_to_path(&config_path)?; let db_init_data = DbInitData { network_name: name.to_string(), network_cidr: root_cidr, server_cidr, our_ip, public_key_base64: our_keypair.public.to_base64(), endpoint, }; // TODO(bschwind) - Clean up the config file and database // if any errors occur in these init calls. let database_path = conf.database_path(&name); let conn = create_database(&database_path).map_err(|_| { anyhow!( "failed to create database {}", "(are you not running as root?)".bold() ) })?; populate_database(&conn, db_init_data)?; println!( "{} Created database at {}\n", "[*]".dimmed(), database_path.to_string_lossy().bold() ); printdoc!( " {star} Setup finished. Network {interface} has been {created}, but it's not started yet! Your new network starts with only one peer: this innernet server. Next, you'll want to create additional CIDRs and peers using the commands: {wg_manage_server} {add_cidr} {interface}, and {wg_manage_server} {add_peer} {interface} See https://github.com/tonarino/innernet for more detailed instruction on designing your network. When you're ready to start the network, you can auto-start the server: {systemctl_enable}{interface} ", star = "[*]".dimmed(), interface = name.to_string().yellow(), created = "created".green(), wg_manage_server = "innernet-server".yellow(), add_cidr = "add-cidr".yellow(), add_peer = "add-peer".yellow(), systemctl_enable = "systemctl enable --now innernet-server@".yellow(), ); Ok(()) }