1use crate::{
2 db::{self, DatabaseCidr, DatabasePeer},
3 ConfigFile, Interface, Path, ServerConfig,
4};
5use anyhow::{anyhow, Error};
6use clap::Parser;
7use colored::Colorize;
8use dialoguer::{theme::ColorfulTheme, Input};
9use indoc::printdoc;
10use innernet_publicip::Preference;
11use innernet_shared::{
12 prompts, CidrContents, Endpoint, IpNetExt, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
13};
14use ipnet::IpNet;
15use rusqlite::{params, Connection};
16use std::net::{IpAddr, SocketAddr};
17use wireguard_control::KeyPair;
18
19fn create_database<P: AsRef<Path>>(
20 database_path: P,
21) -> Result<Connection, Box<dyn std::error::Error>> {
22 let conn = Connection::open(&database_path)?;
23 conn.pragma_update(None, "foreign_keys", 1)?;
24 conn.execute(db::peer::CREATE_TABLE_SQL, params![])?;
25 conn.execute(db::association::CREATE_TABLE_SQL, params![])?;
26 conn.execute(db::cidr::CREATE_TABLE_SQL, params![])?;
27 conn.pragma_update(None, "user_version", db::CURRENT_VERSION)?;
28 log::debug!("set database version to db::CURRENT_VERSION");
29
30 Ok(conn)
31}
32
33#[derive(Debug, Default, Clone, PartialEq, Eq, Parser)]
34pub struct InitializeOpts {
35 #[clap(long)]
37 pub network_name: Option<Interface>,
38
39 #[clap(long)]
41 pub network_cidr: Option<IpNet>,
42
43 #[clap(long, conflicts_with = "auto_external_endpoint")]
45 pub external_endpoint: Option<Endpoint>,
46
47 #[clap(long = "auto-external-endpoint")]
49 pub auto_external_endpoint: bool,
50
51 #[clap(long)]
53 pub listen_port: Option<u16>,
54}
55
56struct DbInitData {
57 network_name: String,
58 network_cidr: IpNet,
59 server_cidr: IpNet,
60 our_ip: IpAddr,
61 public_key_base64: String,
62 endpoint: Endpoint,
63}
64
65fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(), Error> {
66 const SERVER_NAME: &str = "innernet-server";
67
68 let root_cidr = DatabaseCidr::create(
69 conn,
70 CidrContents {
71 name: db_init_data.network_name.clone(),
72 cidr: db_init_data.network_cidr,
73 parent: None,
74 },
75 )
76 .map_err(|_| anyhow!("failed to create root CIDR"))?;
77
78 let server_cidr = DatabaseCidr::create(
79 conn,
80 CidrContents {
81 name: SERVER_NAME.into(),
82 cidr: db_init_data.server_cidr,
83 parent: Some(root_cidr.id),
84 },
85 )
86 .map_err(|_| anyhow!("failed to create innernet-server CIDR"))?;
87
88 let _me = DatabasePeer::create(
89 conn,
90 PeerContents {
91 name: SERVER_NAME.parse().map_err(|e: &str| anyhow!(e))?,
92 ip: db_init_data.our_ip,
93 cidr_id: server_cidr.id,
94 public_key: db_init_data.public_key_base64,
95 endpoint: Some(db_init_data.endpoint),
96 is_admin: true,
97 is_disabled: false,
98 is_redeemed: true,
99 persistent_keepalive_interval: Some(PERSISTENT_KEEPALIVE_INTERVAL_SECS),
100 invite_expires: None,
101 candidates: vec![],
102 },
103 )
104 .map_err(|_| anyhow!("failed to create innernet peer."))?;
105
106 Ok(())
107}
108
109pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Error> {
110 let theme = ColorfulTheme::default();
111
112 innernet_shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(
113 |_| {
114 anyhow!(
115 "Failed to create config and database directories {}",
116 "(are you not running as root?)".bold()
117 )
118 },
119 )?;
120 printdoc!(
121 "\nTime to setup your innernet network.
122
123 Your network name can be any hostname-valid string, i.e. \"evilcorp\", and
124 your network CIDR should be in the RFC1918 IPv4 (10/8, 172.16/12, or 192.168/16),
125 or RFC4193 IPv6 (fd00::/8) ranges.
126
127 The external endpoint specified is a <host>:<port> string that is the address clients
128 will connect to. It's up to you to forward/open ports in your routers/firewalls
129 as needed.
130
131 For more usage instructions, see https://github.com/tonarino/innernet#usage
132 \n"
133 );
134
135 let name: Interface = if let Some(name) = opts.network_name {
136 name
137 } else {
138 Input::with_theme(&theme)
139 .with_prompt("Network name")
140 .interact()?
141 };
142
143 let root_cidr: IpNet = if let Some(cidr) = opts.network_cidr {
144 cidr
145 } else {
146 Input::with_theme(&theme)
147 .with_prompt("Network CIDR")
148 .with_initial_text("10.42.0.0/16")
149 .interact_text()?
151 };
152
153 let listen_port: u16 = if let Some(listen_port) = opts.listen_port {
154 listen_port
155 } else {
156 Input::with_theme(&theme)
157 .with_prompt("Listen port")
158 .default(51820)
159 .interact()
160 .map_err(|_| anyhow!("failed to get listen port."))?
161 };
162
163 log::info!("listen port: {}", listen_port);
164
165 let endpoint: Endpoint = if let Some(endpoint) = opts.external_endpoint {
166 endpoint
167 } else if opts.auto_external_endpoint {
168 let ip = innernet_publicip::get_any(Preference::Ipv4)
169 .ok_or_else(|| anyhow!("couldn't get external IP"))?;
170 SocketAddr::new(ip, listen_port).into()
171 } else {
172 let external_ip = prompts::ip_auto_detection_flow()?;
173 prompts::input_external_endpoint(external_ip, listen_port)?
174 };
175
176 let our_ip = root_cidr
177 .hosts()
178 .find(|ip| root_cidr.is_assignable(ip))
179 .unwrap();
180 let config_path = conf.config_path(&name);
181 let our_keypair = KeyPair::generate();
182
183 let config = ConfigFile {
184 private_key: our_keypair.private.to_base64(),
185 listen_port,
186 address: our_ip,
187 network_cidr_prefix: root_cidr.prefix_len(),
188 };
189 config.write_to_path(config_path)?;
190
191 let db_init_data = DbInitData {
192 network_name: name.to_string(),
193 network_cidr: root_cidr,
194 server_cidr: IpNet::new(our_ip, root_cidr.max_prefix_len())?,
195 our_ip,
196 public_key_base64: our_keypair.public.to_base64(),
197 endpoint,
198 };
199
200 let database_path = conf.database_path(&name);
204 let conn = create_database(&database_path).map_err(|_| {
205 anyhow!(
206 "failed to create database {}",
207 "(are you not running as root?)".bold()
208 )
209 })?;
210 populate_database(&conn, db_init_data)?;
211
212 println!(
213 "{} Created database at {}\n",
214 "[*]".dimmed(),
215 database_path.to_string_lossy().bold()
216 );
217 printdoc!(
218 "
219 {star} Setup finished.
220
221 Network {interface} has been {created}, but it's not started yet!
222
223 Your new network starts with only one peer: this innernet server. Next,
224 you'll want to create additional CIDRs and peers using the commands:
225
226 {wg_manage_server} {add_cidr} {interface}, and
227 {wg_manage_server} {add_peer} {interface}
228
229 See https://github.com/tonarino/innernet for more detailed instruction
230 on designing your network.
231
232 When you're ready to start the network, you can auto-start the server:
233
234 {systemctl_enable}{interface}
235
236 ",
237 star = "[*]".dimmed(),
238 interface = name.to_string().yellow(),
239 created = "created".green(),
240 wg_manage_server = "innernet-server".yellow(),
241 add_cidr = "add-cidr".yellow(),
242 add_peer = "add-peer".yellow(),
243 systemctl_enable = "systemctl enable --now innernet-server@".yellow(),
244 );
245
246 Ok(())
247}