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>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
enum Command {
|
||||
/// Install a new innernet config.
|
||||
#[structopt(alias = "redeem")]
|
||||
Install { config: PathBuf },
|
||||
Install {
|
||||
config: PathBuf,
|
||||
|
||||
#[structopt(flatten)]
|
||||
hosts: HostsOpt,
|
||||
},
|
||||
|
||||
/// Enumerate all innernet connections.
|
||||
#[structopt(alias = "list")]
|
||||
|
@ -60,11 +82,19 @@ enum Command {
|
|||
#[structopt(long, default_value = "60")]
|
||||
interval: u64,
|
||||
|
||||
#[structopt(flatten)]
|
||||
hosts: HostsOpt,
|
||||
|
||||
interface: Interface,
|
||||
},
|
||||
|
||||
/// 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]")
|
||||
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!(
|
||||
"{} updating {} with the latest peers.",
|
||||
"[*]".dimmed(),
|
||||
|
@ -139,12 +169,12 @@ fn update_hosts_file(interface: &str, peers: &Vec<Peer>) -> Result<(), Error> {
|
|||
&format!("{}.{}.wg", peer.contents.name, interface),
|
||||
);
|
||||
}
|
||||
hosts_builder.write()?;
|
||||
hosts_builder.write_to(hosts_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install(invite: &Path) -> Result<(), Error> {
|
||||
fn install(invite: &Path, hosts_file: Option<PathBuf>) -> Result<(), Error> {
|
||||
let theme = ColorfulTheme::default();
|
||||
shared::ensure_dirs_exist(&[*CLIENT_CONFIG_PATH])?;
|
||||
let mut config = InterfaceConfig::from_file(invite)?;
|
||||
|
@ -205,7 +235,7 @@ fn install(invite: &Path) -> Result<(), Error> {
|
|||
.set_private_key(keypair.private)
|
||||
.apply(&iface)?;
|
||||
|
||||
fetch(&iface, false)?;
|
||||
fetch(&iface, false, hosts_file)?;
|
||||
|
||||
if Confirm::with_theme(&theme)
|
||||
.with_prompt(&format!(
|
||||
|
@ -228,8 +258,12 @@ fn install(invite: &Path) -> Result<(), Error> {
|
|||
|
||||
{systemctl_enable}{interface}
|
||||
|
||||
See the documentation for more detailed instruction on managing your interface
|
||||
and your network.
|
||||
By default, innernet will write to your /etc/hosts file for peer name
|
||||
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(),
|
||||
|
@ -241,9 +275,13 @@ fn install(invite: &Path) -> Result<(), Error> {
|
|||
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 {
|
||||
fetch(interface, true)?;
|
||||
fetch(interface, true, hosts_path.clone())?;
|
||||
match loop_interval {
|
||||
Some(interval) => thread::sleep(interval),
|
||||
None => break,
|
||||
|
@ -253,7 +291,11 @@ fn up(interface: &str, loop_interval: Option<Duration>) -> Result<(), Error> {
|
|||
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 interface_up = if let Ok(interfaces) = DeviceInfo::enumerate() {
|
||||
interfaces.iter().any(|name| name == interface)
|
||||
|
@ -349,7 +391,9 @@ fn fetch(interface: &str, bring_up_interface: bool) -> Result<(), Error> {
|
|||
if device_config_changed {
|
||||
device_config_builder.apply(&interface)?;
|
||||
|
||||
update_hosts_file(interface, &peers)?;
|
||||
if let Some(path) = hosts_path {
|
||||
update_hosts_file(interface, path, &peers)?;
|
||||
}
|
||||
|
||||
println!(
|
||||
"\n{} updated interface {}\n",
|
||||
|
@ -724,18 +768,23 @@ fn run(opt: Opt) -> Result<(), Error> {
|
|||
});
|
||||
|
||||
match command {
|
||||
Command::Install { config } => install(&config)?,
|
||||
Command::Install { config, hosts } => install(&config, hosts.into())?,
|
||||
Command::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 {
|
||||
interface,
|
||||
daemon,
|
||||
hosts,
|
||||
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::AddPeer { interface } => add_peer(&interface)?,
|
||||
Command::AddCidr { interface } => add_cidr(&interface)?,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
fs::{self, File},
|
||||
fs::{self, File, OpenOptions},
|
||||
io::{BufRead, BufReader, Write},
|
||||
net::IpAddr,
|
||||
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
|
||||
/// name already, it will be replaced with the new list instead.
|
||||
pub fn write_to<P: AsRef<Path>>(&self, hosts_file: P) -> Result<()> {
|
||||
let hosts_file = hosts_file.as_ref();
|
||||
pub fn write_to<P: AsRef<Path>>(&self, hosts_path: P) -> Result<()> {
|
||||
let hosts_path = hosts_path.as_ref();
|
||||
let begin_marker = format!("# DO NOT EDIT {} BEGIN", &self.tag);
|
||||
let end_marker = format!("# DO NOT EDIT {} END", &self.tag);
|
||||
|
||||
let mut lines = match File::open(hosts_file) {
|
||||
Ok(file) => BufReader::new(file)
|
||||
.lines()
|
||||
.map(|line| line.unwrap())
|
||||
.collect::<Vec<_>>(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let hosts_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(hosts_path)?;
|
||||
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 end = lines.iter().position(|line| line.trim() == end_marker);
|
||||
|
@ -160,20 +162,20 @@ impl HostsBuilder {
|
|||
_ => {
|
||||
return Err(Box::new(Error(format!(
|
||||
"start or end marker missing in {:?}",
|
||||
&hosts_file
|
||||
&hosts_path
|
||||
))));
|
||||
},
|
||||
};
|
||||
|
||||
// The tempfile should be in the same filesystem as the hosts file.
|
||||
let hosts_dir = hosts_file
|
||||
let hosts_dir = hosts_path
|
||||
.parent()
|
||||
.expect("hosts file must be an absolute file path");
|
||||
let temp_dir = tempfile::Builder::new().tempdir_in(hosts_dir)?;
|
||||
let temp_path = temp_dir.path().join("hosts");
|
||||
|
||||
// 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)?;
|
||||
|
||||
|
@ -192,7 +194,7 @@ impl HostsBuilder {
|
|||
}
|
||||
|
||||
// Move the file atomically to avoid a partial state.
|
||||
fs::rename(&temp_path, &hosts_file)?;
|
||||
fs::rename(&temp_path, &hosts_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue