Split into modules
This commit is contained in:
parent
72ed117cb0
commit
f663a4f8b4
4 changed files with 158 additions and 137 deletions
61
src/api.rs
Normal file
61
src/api.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
const BASE_URL: &str = "https://api.azirevpn.com/v2";
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct Locations {
|
||||||
|
pub status: String,
|
||||||
|
pub locations: Vec<Location>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct Location {
|
||||||
|
pub name: String,
|
||||||
|
pub city: String,
|
||||||
|
pub country: String,
|
||||||
|
pub iso: String,
|
||||||
|
pub pool: String,
|
||||||
|
pub pubkey: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct Addresses {
|
||||||
|
pub status: String,
|
||||||
|
pub ipv4: WireguardConfigIpv4,
|
||||||
|
pub ipv6: WireguardConfigIpv6,
|
||||||
|
pub dns: Vec<IpAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct WireguardConfigIpv4 {
|
||||||
|
pub address: Ipv4Addr,
|
||||||
|
pub netmask: u8,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct WireguardConfigIpv6 {
|
||||||
|
pub address: Ipv6Addr,
|
||||||
|
pub netmask: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_locations() -> anyhow::Result<Locations> {
|
||||||
|
let url = format!("{}/locations", BASE_URL);
|
||||||
|
let response: Locations = ureq::get(&url).call()?.into_json()?;
|
||||||
|
debug!("response = {:?}", &response);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_ip(username: &str, token: &str, public_key: &str) -> anyhow::Result<Addresses> {
|
||||||
|
let url = format!("{}/ip/add", BASE_URL);
|
||||||
|
let response: Addresses = ureq::post(&url)
|
||||||
|
.send_form(&[
|
||||||
|
("username", username),
|
||||||
|
("token", token),
|
||||||
|
("key", public_key),
|
||||||
|
])?
|
||||||
|
.into_json()?;
|
||||||
|
debug!("response = {:?}", &response);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
18
src/dirs.rs
Normal file
18
src/dirs.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use directories::ProjectDirs;
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const QUALIFIER: &str = "com";
|
||||||
|
const ORGANIZATION: &str = "Ero-sennin";
|
||||||
|
const APPLICATION: &str = "AzireVPN";
|
||||||
|
|
||||||
|
static PROJECT_DIRS: OnceCell<ProjectDirs> = OnceCell::new();
|
||||||
|
|
||||||
|
pub(crate) fn get_data_dir() -> PathBuf {
|
||||||
|
let project_dirs = PROJECT_DIRS.get_or_init(|| {
|
||||||
|
ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
|
||||||
|
.expect("cannot get project data dir")
|
||||||
|
});
|
||||||
|
project_dirs.data_dir().to_path_buf()
|
||||||
|
}
|
||||||
67
src/keys.rs
Normal file
67
src/keys.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
use gethostname::gethostname;
|
||||||
|
use log::debug;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use crate::dirs::get_data_dir;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct WireguardKeyPair {
|
||||||
|
pub public_key: String,
|
||||||
|
pub private_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_keys(machine: Option<&PathBuf>) -> Result<WireguardKeyPair, anyhow::Error> {
|
||||||
|
let hostname: PathBuf;
|
||||||
|
let machine_subdir: &PathBuf = if let Some(machine) = machine {
|
||||||
|
machine
|
||||||
|
} else {
|
||||||
|
hostname = PathBuf::from(gethostname());
|
||||||
|
&hostname
|
||||||
|
};
|
||||||
|
let key_path = get_data_dir().join("keys").join(machine_subdir);
|
||||||
|
debug!("key path = {:?}", &key_path);
|
||||||
|
std::fs::create_dir_all(&key_path)?;
|
||||||
|
let private_key_path = key_path.join("key");
|
||||||
|
let private_key = if private_key_path.is_file() {
|
||||||
|
std::fs::read_to_string(private_key_path)?
|
||||||
|
} else {
|
||||||
|
let key = generate_private_key()?;
|
||||||
|
std::fs::write(private_key_path, key.as_bytes())?;
|
||||||
|
key
|
||||||
|
};
|
||||||
|
let public_key_path = key_path.join("pubkey");
|
||||||
|
let public_key = if public_key_path.is_file() {
|
||||||
|
std::fs::read_to_string(public_key_path)?
|
||||||
|
} else {
|
||||||
|
let key = generate_public_key(&private_key)?;
|
||||||
|
std::fs::write(public_key_path, key.as_bytes())?;
|
||||||
|
key
|
||||||
|
};
|
||||||
|
Ok(WireguardKeyPair {
|
||||||
|
private_key,
|
||||||
|
public_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_private_key() -> anyhow::Result<String> {
|
||||||
|
let privkey = process::Command::new("wg").arg("genkey").output()?.stdout;
|
||||||
|
Ok(String::from_utf8(privkey)?.trim_end().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_public_key(private_key: &str) -> anyhow::Result<String> {
|
||||||
|
let mut pubkey_cmd = process::Command::new("wg")
|
||||||
|
.arg("pubkey")
|
||||||
|
.stdin(process::Stdio::piped())
|
||||||
|
.stdout(process::Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
pubkey_cmd
|
||||||
|
.stdin
|
||||||
|
.as_mut()
|
||||||
|
.expect("no stdin")
|
||||||
|
.write_all(private_key.as_bytes())?;
|
||||||
|
let pubkey = pubkey_cmd.wait_with_output()?.stdout;
|
||||||
|
Ok(String::from_utf8(pubkey)?.trim_end().to_string())
|
||||||
|
}
|
||||||
149
src/main.rs
149
src/main.rs
|
|
@ -1,22 +1,15 @@
|
||||||
|
mod api;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
mod dirs;
|
||||||
|
mod keys;
|
||||||
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use directories::ProjectDirs;
|
|
||||||
use gethostname::gethostname;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
const BASE_URL: &str = "https://api.azirevpn.com/v2";
|
use crate::keys::{get_keys, WireguardKeyPair};
|
||||||
|
|
||||||
const QUALIFIER: &str = "com";
|
|
||||||
const ORGANIZATION: &str = "Ero-sennin";
|
|
||||||
const APPLICATION: &str = "AzireVPN";
|
|
||||||
|
|
||||||
static PROJECT_DIRS: OnceCell<ProjectDirs> = OnceCell::new();
|
|
||||||
|
|
||||||
/// AzireVPN client
|
/// AzireVPN client
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
|
@ -55,47 +48,6 @@ enum Command {
|
||||||
Config(ConfigOpts),
|
Config(ConfigOpts),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct Locations {
|
|
||||||
status: String,
|
|
||||||
locations: Vec<Location>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct Location {
|
|
||||||
name: String,
|
|
||||||
city: String,
|
|
||||||
country: String,
|
|
||||||
iso: String,
|
|
||||||
pool: String,
|
|
||||||
pubkey: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct WireguardConfig {
|
|
||||||
status: String,
|
|
||||||
ipv4: WireguardConfigIpv4,
|
|
||||||
ipv6: WireguardConfigIpv6,
|
|
||||||
dns: Vec<IpAddr>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct WireguardConfigIpv4 {
|
|
||||||
address: Ipv4Addr,
|
|
||||||
netmask: u8,
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct WireguardConfigIpv6 {
|
|
||||||
address: Ipv6Addr,
|
|
||||||
netmask: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct WireguardKeyPair {
|
|
||||||
public_key: String,
|
|
||||||
private_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), anyhow::Error> {
|
fn main() -> Result<(), anyhow::Error> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let opts = Opts::parse();
|
let opts = Opts::parse();
|
||||||
|
|
@ -106,16 +58,8 @@ fn main() -> Result<(), anyhow::Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_data_dir() -> PathBuf {
|
|
||||||
let project_dirs = PROJECT_DIRS.get_or_init(|| {
|
|
||||||
ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
|
|
||||||
.expect("cannot get project data dir")
|
|
||||||
});
|
|
||||||
project_dirs.data_dir().to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> {
|
fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> {
|
||||||
let locations: Locations = get_locations()?;
|
let locations = api::get_locations()?;
|
||||||
if opts.json {
|
if opts.json {
|
||||||
println!("{}", serde_json::to_string(&locations)?);
|
println!("{}", serde_json::to_string(&locations)?);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -135,7 +79,7 @@ fn list_locations(opts: &Opts) -> Result<(), anyhow::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Error> {
|
fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Error> {
|
||||||
let locations = get_locations()?;
|
let locations = api::get_locations()?;
|
||||||
let location = locations
|
let location = locations
|
||||||
.locations
|
.locations
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -144,22 +88,13 @@ fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Erro
|
||||||
debug!("location = {:?}", &location);
|
debug!("location = {:?}", &location);
|
||||||
let keys = get_keys(config_opts.machine.as_ref())?;
|
let keys = get_keys(config_opts.machine.as_ref())?;
|
||||||
debug!("keys = {:?}", &keys);
|
debug!("keys = {:?}", &keys);
|
||||||
let url = format!("{}/ip/add", BASE_URL);
|
let addresses = api::add_ip(&config_opts.username, &config_opts.token, &keys.public_key)?;
|
||||||
let config: WireguardConfig = ureq::post(&url)
|
|
||||||
.send_form(&[
|
|
||||||
("username", &config_opts.username),
|
|
||||||
("token", &config_opts.token),
|
|
||||||
("key", &keys.public_key),
|
|
||||||
])?
|
|
||||||
.into_json()?;
|
|
||||||
debug!("response = {:?}", &config);
|
|
||||||
debug!("config = {:?}", &config);
|
|
||||||
|
|
||||||
write_config(
|
write_config(
|
||||||
&mut std::io::stdout().lock(),
|
&mut std::io::stdout().lock(),
|
||||||
config_opts,
|
config_opts,
|
||||||
location,
|
location,
|
||||||
&config,
|
&addresses,
|
||||||
&keys,
|
&keys,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -167,8 +102,8 @@ fn get_config(_opts: &Opts, config_opts: &ConfigOpts) -> Result<(), anyhow::Erro
|
||||||
fn write_config(
|
fn write_config(
|
||||||
output: &mut dyn Write,
|
output: &mut dyn Write,
|
||||||
config_opts: &ConfigOpts,
|
config_opts: &ConfigOpts,
|
||||||
location: &Location,
|
location: &api::Location,
|
||||||
config: &WireguardConfig,
|
config: &api::Addresses,
|
||||||
keys: &WireguardKeyPair,
|
keys: &WireguardKeyPair,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
writeln!(output, "[Interface]")?;
|
writeln!(output, "[Interface]")?;
|
||||||
|
|
@ -193,7 +128,6 @@ fn write_config(
|
||||||
|
|
||||||
writeln!(output, "[Peer]")?;
|
writeln!(output, "[Peer]")?;
|
||||||
writeln!(output, "PublicKey = {}", &location.pubkey)?;
|
writeln!(output, "PublicKey = {}", &location.pubkey)?;
|
||||||
|
|
||||||
writeln!(output, "Endpoint = {}:51820", &location.pool)?;
|
writeln!(output, "Endpoint = {}:51820", &location.pool)?;
|
||||||
|
|
||||||
let allowed_ips: &[&str] = if config_opts.no_ipv6 {
|
let allowed_ips: &[&str] = if config_opts.no_ipv6 {
|
||||||
|
|
@ -221,62 +155,3 @@ where
|
||||||
writeln!(output)?;
|
writeln!(output)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_locations() -> Result<Locations, anyhow::Error> {
|
|
||||||
let url = format!("{}/locations", BASE_URL);
|
|
||||||
let locations: Locations = ureq::get(&url).call()?.into_json()?;
|
|
||||||
Ok(locations)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_keys(machine: Option<&PathBuf>) -> Result<WireguardKeyPair, anyhow::Error> {
|
|
||||||
let hostname: PathBuf;
|
|
||||||
let machine_subdir: &PathBuf = if let Some(machine) = machine {
|
|
||||||
machine
|
|
||||||
} else {
|
|
||||||
hostname = PathBuf::from(gethostname());
|
|
||||||
&hostname
|
|
||||||
};
|
|
||||||
let key_path = get_data_dir().join("keys").join(machine_subdir);
|
|
||||||
debug!("key path = {:?}", &key_path);
|
|
||||||
std::fs::create_dir_all(&key_path)?;
|
|
||||||
let private_key_path = key_path.join("key");
|
|
||||||
let private_key = if private_key_path.is_file() {
|
|
||||||
std::fs::read_to_string(private_key_path)?
|
|
||||||
} else {
|
|
||||||
let key = generate_private_key()?;
|
|
||||||
std::fs::write(private_key_path, key.as_bytes())?;
|
|
||||||
key
|
|
||||||
};
|
|
||||||
let public_key_path = key_path.join("pubkey");
|
|
||||||
let public_key = if public_key_path.is_file() {
|
|
||||||
std::fs::read_to_string(public_key_path)?
|
|
||||||
} else {
|
|
||||||
let key = generate_public_key(&private_key)?;
|
|
||||||
std::fs::write(public_key_path, key.as_bytes())?;
|
|
||||||
key
|
|
||||||
};
|
|
||||||
Ok(WireguardKeyPair {
|
|
||||||
private_key,
|
|
||||||
public_key,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_private_key() -> anyhow::Result<String> {
|
|
||||||
let privkey = process::Command::new("wg").arg("genkey").output()?.stdout;
|
|
||||||
Ok(String::from_utf8(privkey)?.trim_end().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_public_key(private_key: &str) -> anyhow::Result<String> {
|
|
||||||
let mut pubkey_cmd = process::Command::new("wg")
|
|
||||||
.arg("pubkey")
|
|
||||||
.stdin(process::Stdio::piped())
|
|
||||||
.stdout(process::Stdio::piped())
|
|
||||||
.spawn()?;
|
|
||||||
pubkey_cmd
|
|
||||||
.stdin
|
|
||||||
.as_mut()
|
|
||||||
.expect("no stdin")
|
|
||||||
.write_all(private_key.as_bytes())?;
|
|
||||||
let pubkey = pubkey_cmd.wait_with_output()?.stdout;
|
|
||||||
Ok(String::from_utf8(pubkey)?.trim_end().to_string())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue