client: changeable hosts file settings
Introduces `--hosts-path [PATH]` and `--no-write-hosts` options in `innernet`. This could be further improved to have a persistent setting in a config file i.e. /etc/innernet.conf (which doesn't currently exist). Fixes #6pull/36/head
parent
f0018c1052
commit
296cd7b496
|
@ -30,11 +30,33 @@ struct Opt {
|
||||||
command: Option<Command>,
|
command: Option<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
struct HostsOpt {
|
||||||
|
/// The path to write hosts to.
|
||||||
|
#[structopt(long, default_value = "/etc/hosts")]
|
||||||
|
hosts_path: PathBuf,
|
||||||
|
|
||||||
|
/// Don't write to any hosts files. This takes precedence over --hosts-path.
|
||||||
|
#[structopt(long)]
|
||||||
|
no_write_hosts: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HostsOpt> for Option<PathBuf> {
|
||||||
|
fn from(opt: HostsOpt) -> Self {
|
||||||
|
(!opt.no_write_hosts).then(|| opt.hosts_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
enum Command {
|
enum Command {
|
||||||
/// Install a new innernet config.
|
/// Install a new innernet config.
|
||||||
#[structopt(alias = "redeem")]
|
#[structopt(alias = "redeem")]
|
||||||
Install { config: PathBuf },
|
Install {
|
||||||
|
config: PathBuf,
|
||||||
|
|
||||||
|
#[structopt(flatten)]
|
||||||
|
hosts: HostsOpt,
|
||||||
|
},
|
||||||
|
|
||||||
/// Enumerate all innernet connections.
|
/// Enumerate all innernet connections.
|
||||||
#[structopt(alias = "list")]
|
#[structopt(alias = "list")]
|
||||||
|
@ -60,11 +82,19 @@ enum Command {
|
||||||
#[structopt(long, default_value = "60")]
|
#[structopt(long, default_value = "60")]
|
||||||
interval: u64,
|
interval: u64,
|
||||||
|
|
||||||
|
#[structopt(flatten)]
|
||||||
|
hosts: HostsOpt,
|
||||||
|
|
||||||
interface: Interface,
|
interface: Interface,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Fetch and update your local interface with the latest peer list.
|
/// Fetch and update your local interface with the latest peer list.
|
||||||
Fetch { interface: Interface },
|
Fetch {
|
||||||
|
interface: Interface,
|
||||||
|
|
||||||
|
#[structopt(flatten)]
|
||||||
|
hosts: HostsOpt,
|
||||||
|
},
|
||||||
|
|
||||||
/// Bring down the interface (equivalent to "wg-quick down [interface]")
|
/// Bring down the interface (equivalent to "wg-quick down [interface]")
|
||||||
Down { interface: Interface },
|
Down { interface: Interface },
|
||||||
|
@ -125,7 +155,7 @@ impl std::error::Error for ClientError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_hosts_file(interface: &str, peers: &Vec<Peer>) -> Result<(), Error> {
|
fn update_hosts_file(interface: &str, hosts_path: PathBuf, peers: &Vec<Peer>) -> Result<(), Error> {
|
||||||
println!(
|
println!(
|
||||||
"{} updating {} with the latest peers.",
|
"{} updating {} with the latest peers.",
|
||||||
"[*]".dimmed(),
|
"[*]".dimmed(),
|
||||||
|
@ -139,12 +169,12 @@ fn update_hosts_file(interface: &str, peers: &Vec<Peer>) -> Result<(), Error> {
|
||||||
&format!("{}.{}.wg", peer.contents.name, interface),
|
&format!("{}.{}.wg", peer.contents.name, interface),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
hosts_builder.write()?;
|
hosts_builder.write_to(hosts_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install(invite: &Path) -> Result<(), Error> {
|
fn install(invite: &Path, hosts_file: Option<PathBuf>) -> Result<(), Error> {
|
||||||
let theme = ColorfulTheme::default();
|
let theme = ColorfulTheme::default();
|
||||||
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
|
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
|
||||||
let mut config = InterfaceConfig::from_file(invite)?;
|
let mut config = InterfaceConfig::from_file(invite)?;
|
||||||
|
@ -205,7 +235,7 @@ fn install(invite: &Path) -> Result<(), Error> {
|
||||||
.set_private_key(keypair.private)
|
.set_private_key(keypair.private)
|
||||||
.apply(&iface)?;
|
.apply(&iface)?;
|
||||||
|
|
||||||
fetch(&iface, false)?;
|
fetch(&iface, false, hosts_file)?;
|
||||||
|
|
||||||
if Confirm::with_theme(&theme)
|
if Confirm::with_theme(&theme)
|
||||||
.with_prompt(&format!(
|
.with_prompt(&format!(
|
||||||
|
@ -228,8 +258,12 @@ fn install(invite: &Path) -> Result<(), Error> {
|
||||||
|
|
||||||
{systemctl_enable}{interface}
|
{systemctl_enable}{interface}
|
||||||
|
|
||||||
See the documentation for more detailed instruction on managing your interface
|
By default, innernet will write to your /etc/hosts file for peer name
|
||||||
and your network.
|
resolution. To disable this behavior, use the --no-write-hosts or --write-hosts [PATH]
|
||||||
|
options.
|
||||||
|
|
||||||
|
See the manpage or innernet GitHub repo for more detailed instruction on managing your
|
||||||
|
interface and network. Have fun!
|
||||||
|
|
||||||
",
|
",
|
||||||
star = "[*]".dimmed(),
|
star = "[*]".dimmed(),
|
||||||
|
@ -241,9 +275,13 @@ fn install(invite: &Path) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(interface: &str, loop_interval: Option<Duration>) -> Result<(), Error> {
|
fn up(
|
||||||
|
interface: &str,
|
||||||
|
loop_interval: Option<Duration>,
|
||||||
|
hosts_path: Option<PathBuf>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
fetch(interface, true)?;
|
fetch(interface, true, hosts_path.clone())?;
|
||||||
match loop_interval {
|
match loop_interval {
|
||||||
Some(interval) => thread::sleep(interval),
|
Some(interval) => thread::sleep(interval),
|
||||||
None => break,
|
None => break,
|
||||||
|
@ -253,7 +291,11 @@ fn up(interface: &str, loop_interval: Option<Duration>) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(interface: &str, bring_up_interface: bool) -> Result<(), Error> {
|
fn fetch(
|
||||||
|
interface: &str,
|
||||||
|
bring_up_interface: bool,
|
||||||
|
hosts_path: Option<PathBuf>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let config = InterfaceConfig::from_interface(interface)?;
|
let config = InterfaceConfig::from_interface(interface)?;
|
||||||
let interface_up = if let Ok(interfaces) = DeviceInfo::enumerate() {
|
let interface_up = if let Ok(interfaces) = DeviceInfo::enumerate() {
|
||||||
interfaces.iter().any(|name| name == interface)
|
interfaces.iter().any(|name| name == interface)
|
||||||
|
@ -349,7 +391,9 @@ fn fetch(interface: &str, bring_up_interface: bool) -> Result<(), Error> {
|
||||||
if device_config_changed {
|
if device_config_changed {
|
||||||
device_config_builder.apply(&interface)?;
|
device_config_builder.apply(&interface)?;
|
||||||
|
|
||||||
update_hosts_file(interface, &peers)?;
|
if let Some(path) = hosts_path {
|
||||||
|
update_hosts_file(interface, path, &peers)?;
|
||||||
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"\n{} updated interface {}\n",
|
"\n{} updated interface {}\n",
|
||||||
|
@ -724,18 +768,23 @@ fn run(opt: Opt) -> Result<(), Error> {
|
||||||
});
|
});
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
Command::Install { config } => install(&config)?,
|
Command::Install { config, hosts } => install(&config, hosts.into())?,
|
||||||
Command::Show {
|
Command::Show {
|
||||||
short,
|
short,
|
||||||
tree,
|
tree,
|
||||||
interface,
|
interface,
|
||||||
} => show(short, tree, interface)?,
|
} => show(short, tree, interface)?,
|
||||||
Command::Fetch { interface } => fetch(&interface, false)?,
|
Command::Fetch { interface, hosts } => fetch(&interface, false, hosts.into())?,
|
||||||
Command::Up {
|
Command::Up {
|
||||||
interface,
|
interface,
|
||||||
daemon,
|
daemon,
|
||||||
|
hosts,
|
||||||
interval,
|
interval,
|
||||||
} => up(&interface, daemon.then(|| Duration::from_secs(interval)))?,
|
} => up(
|
||||||
|
&interface,
|
||||||
|
daemon.then(|| Duration::from_secs(interval)),
|
||||||
|
hosts.into(),
|
||||||
|
)?,
|
||||||
Command::Down { interface } => wg::down(&interface)?,
|
Command::Down { interface } => wg::down(&interface)?,
|
||||||
Command::AddPeer { interface } => add_peer(&interface)?,
|
Command::AddPeer { interface } => add_peer(&interface)?,
|
||||||
Command::AddCidr { interface } => add_cidr(&interface)?,
|
Command::AddCidr { interface } => add_cidr(&interface)?,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt,
|
fmt,
|
||||||
fs::{self, File},
|
fs::{self, File, OpenOptions},
|
||||||
io::{BufRead, BufReader, Write},
|
io::{BufRead, BufReader, Write},
|
||||||
net::IpAddr,
|
net::IpAddr,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -127,18 +127,20 @@ impl HostsBuilder {
|
||||||
|
|
||||||
/// Inserts a new section to the specified hosts file. If there is a section with the same tag
|
/// Inserts a new section to the specified hosts file. If there is a section with the same tag
|
||||||
/// name already, it will be replaced with the new list instead.
|
/// name already, it will be replaced with the new list instead.
|
||||||
pub fn write_to<P: AsRef<Path>>(&self, hosts_file: P) -> Result<()> {
|
pub fn write_to<P: AsRef<Path>>(&self, hosts_path: P) -> Result<()> {
|
||||||
let hosts_file = hosts_file.as_ref();
|
let hosts_path = hosts_path.as_ref();
|
||||||
let begin_marker = format!("# DO NOT EDIT {} BEGIN", &self.tag);
|
let begin_marker = format!("# DO NOT EDIT {} BEGIN", &self.tag);
|
||||||
let end_marker = format!("# DO NOT EDIT {} END", &self.tag);
|
let end_marker = format!("# DO NOT EDIT {} END", &self.tag);
|
||||||
|
|
||||||
let mut lines = match File::open(hosts_file) {
|
let hosts_file = OpenOptions::new()
|
||||||
Ok(file) => BufReader::new(file)
|
.create(true)
|
||||||
.lines()
|
.read(true)
|
||||||
.map(|line| line.unwrap())
|
.write(true)
|
||||||
.collect::<Vec<_>>(),
|
.open(hosts_path)?;
|
||||||
_ => Vec::new(),
|
let mut lines = BufReader::new(hosts_file)
|
||||||
};
|
.lines()
|
||||||
|
.map(|line| line.unwrap())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let begin = lines.iter().position(|line| line.trim() == begin_marker);
|
let begin = lines.iter().position(|line| line.trim() == begin_marker);
|
||||||
let end = lines.iter().position(|line| line.trim() == end_marker);
|
let end = lines.iter().position(|line| line.trim() == end_marker);
|
||||||
|
@ -160,20 +162,20 @@ impl HostsBuilder {
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Box::new(Error(format!(
|
return Err(Box::new(Error(format!(
|
||||||
"start or end marker missing in {:?}",
|
"start or end marker missing in {:?}",
|
||||||
&hosts_file
|
&hosts_path
|
||||||
))));
|
))));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// The tempfile should be in the same filesystem as the hosts file.
|
// The tempfile should be in the same filesystem as the hosts file.
|
||||||
let hosts_dir = hosts_file
|
let hosts_dir = hosts_path
|
||||||
.parent()
|
.parent()
|
||||||
.expect("hosts file must be an absolute file path");
|
.expect("hosts file must be an absolute file path");
|
||||||
let temp_dir = tempfile::Builder::new().tempdir_in(hosts_dir)?;
|
let temp_dir = tempfile::Builder::new().tempdir_in(hosts_dir)?;
|
||||||
let temp_path = temp_dir.path().join("hosts");
|
let temp_path = temp_dir.path().join("hosts");
|
||||||
|
|
||||||
// Copy the existing hosts file to preserve permissions.
|
// Copy the existing hosts file to preserve permissions.
|
||||||
fs::copy(&hosts_file, &temp_path)?;
|
fs::copy(&hosts_path, &temp_path)?;
|
||||||
|
|
||||||
let mut file = File::create(&temp_path)?;
|
let mut file = File::create(&temp_path)?;
|
||||||
|
|
||||||
|
@ -192,7 +194,7 @@ impl HostsBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the file atomically to avoid a partial state.
|
// Move the file atomically to avoid a partial state.
|
||||||
fs::rename(&temp_path, &hosts_file)?;
|
fs::rename(&temp_path, &hosts_path)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue