use crate::{chmod, ensure_dirs_exist, Endpoint, Error, IoErrorContext, WrappedIoError};
use indoc::writedoc;
use ipnet::IpNet;
use serde::{Deserialize, Serialize};
use std::{
fs::{File, OpenOptions},
io::{self, Write},
net::SocketAddr,
path::{Path, PathBuf},
};
use wireguard_control::InterfaceName;
#[derive(Clone, Deserialize, Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct InterfaceConfig {
pub interface: InterfaceInfo,
pub server: ServerInfo,
}
#[derive(Clone, Deserialize, Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct InterfaceInfo {
pub network_name: String,
pub address: IpNet,
pub private_key: String,
pub listen_port: Option<u16>,
}
#[derive(Clone, Deserialize, Serialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct ServerInfo {
pub public_key: String,
pub external_endpoint: Endpoint,
pub internal_endpoint: SocketAddr,
}
impl InterfaceConfig {
pub fn write_to(
&self,
target_file: &mut File,
comments: bool,
mode: Option<u32>,
) -> Result<(), io::Error> {
if let Some(val) = mode {
chmod(target_file, val)?;
}
if comments {
writedoc!(
target_file,
r"
# This is an invitation file to an innernet network.
#
# To join, you must install innernet.
# See https://github.com/tonarino/innernet for instructions.
#
# If you have innernet, just run:
#
# innernet install <this file>
#
# Don't edit the contents below unless you love chaos and dysfunction.
"
)?;
}
target_file.write_all(toml::to_string(self).unwrap().as_bytes())?;
Ok(())
}
pub fn write_to_path<P: AsRef<Path>>(
&self,
path: P,
comments: bool,
mode: Option<u32>,
) -> Result<(), WrappedIoError> {
let path = path.as_ref();
let mut target_file = OpenOptions::new()
.create_new(true)
.write(true)
.open(path)
.with_path(path)?;
self.write_to(&mut target_file, comments, mode)
.with_path(path)
}
pub fn write_to_interface(
&self,
config_dir: &Path,
interface: &InterfaceName,
) -> Result<PathBuf, Error> {
let path = Self::build_config_file_path(config_dir, interface)?;
File::create(&path)
.with_path(&path)?
.write_all(toml::to_string(self).unwrap().as_bytes())?;
Ok(path)
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
Ok(toml::from_str(
&std::fs::read_to_string(&path).with_path(path)?,
)?)
}
pub fn from_interface(config_dir: &Path, interface: &InterfaceName) -> Result<Self, Error> {
let path = Self::build_config_file_path(config_dir, interface)?;
crate::warn_on_dangerous_mode(&path).with_path(&path)?;
Self::from_file(path)
}
pub fn get_path(config_dir: &Path, interface: &InterfaceName) -> PathBuf {
config_dir
.join(interface.to_string())
.with_extension("conf")
}
fn build_config_file_path(
config_dir: &Path,
interface: &InterfaceName,
) -> Result<PathBuf, WrappedIoError> {
ensure_dirs_exist(&[config_dir])?;
Ok(Self::get_path(config_dir, interface))
}
}
impl InterfaceInfo {
pub fn public_key(&self) -> Result<String, Error> {
Ok(wireguard_control::Key::from_base64(&self.private_key)?
.get_public()
.to_base64())
}
}