Initial working version, lots of unwrap, etc.
Also contains some dev helpers.
This commit is contained in:
parent
a62b3c37af
commit
a273947c27
16 changed files with 1253 additions and 238 deletions
997
Cargo.lock
generated
997
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
16
Cargo.toml
16
Cargo.toml
|
@ -2,13 +2,25 @@
|
|||
name = "wgvirtipd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cidr = { version = "0" }
|
||||
cidr = { version = "0", features = ["serde"] }
|
||||
bytes = { version = "1" }
|
||||
serde = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
regex = "1"
|
||||
tokio = { version = "1", features=["full"] }
|
||||
clap = { version = "4", features=["derive"] }
|
||||
hyper = "0"
|
||||
warp = "0"
|
||||
anyhow = "1.0.75"
|
||||
serde_ini = { git = "https://github.com/devyn/serde-ini" }
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
serde_with = "3.5.0"
|
||||
urlencoding = "2.1.3"
|
||||
|
||||
[build-dependencies]
|
||||
chrono = "0.4.31"
|
||||
git2 = "0.18.1"
|
||||
|
|
22
build.rs
Normal file
22
build.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use git2::Repository;
|
||||
use std::{env, fs, io::Write};
|
||||
|
||||
fn main() {
|
||||
let outdir = env::var("OUT_DIR").unwrap();
|
||||
let outfile = format!("{}/buildinfo.txt", outdir);
|
||||
|
||||
let repo = Repository::open(env::current_dir().unwrap()).unwrap();
|
||||
let head = repo.head().unwrap();
|
||||
let head_name = head.shorthand().unwrap();
|
||||
let commit = head.peel_to_commit().unwrap();
|
||||
let commit_id = commit.id();
|
||||
let now = chrono::Local::now();
|
||||
|
||||
let mut fh = fs::File::create(&outfile).unwrap();
|
||||
write!(
|
||||
fh,
|
||||
r#""wgvirtipd {} ({}) on {}""#,
|
||||
commit_id, head_name, now
|
||||
)
|
||||
.ok();
|
||||
}
|
1
dev/.gitignore
vendored
1
dev/.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
config/vm*.conf
|
||||
config/vm*.html
|
||||
docker-compose.yaml
|
||||
|
|
3
dev/config/check.sh
Executable file
3
dev/config/check.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exit 0
|
13
dev/config/index.html.tmpl
Normal file
13
dev/config/index.html.tmpl
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>Test - {{ item.item }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
This server is {{ item.item }} and reachable on wg0:{{ item.ip }}.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
65
dev/config/keepalived.conf.tmpl
Normal file
65
dev/config/keepalived.conf.tmpl
Normal file
|
@ -0,0 +1,65 @@
|
|||
! Configuration File for keepalived
|
||||
|
||||
global_defs {
|
||||
notification_email {
|
||||
root@localhost
|
||||
}
|
||||
notification_email_from keepalived@localhost
|
||||
smtp_server 127.0.0.1
|
||||
smtp_connect_timeout 30
|
||||
router_id LVS_DEVEL
|
||||
vrrp_skip_check_adv_addr
|
||||
vrrp_garp_interval 0
|
||||
vrrp_gna_interval 0
|
||||
}
|
||||
|
||||
vrrp_script check {
|
||||
script /etc/keepalived/check.sh
|
||||
interval 1
|
||||
timeout 1
|
||||
rise 5
|
||||
fall 5
|
||||
}
|
||||
|
||||
vrrp_instance VI_1 {
|
||||
state {{ item.keepalived_state }}
|
||||
interface wg0
|
||||
priority {{ item.keepalived_priority }}
|
||||
|
||||
virtual_router_id 51
|
||||
advert_int 1
|
||||
virtual_ipaddress {
|
||||
{{ keepalived_ip }}/{{ mask_bits }} dev wg0 label wg0:0
|
||||
}
|
||||
unicast_src_ip {{ item.ip }}
|
||||
unicast_peer {
|
||||
{% for iitem in keypairs %}
|
||||
{% if iitem.item != item.item %}
|
||||
{{ iitem.ip }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
authentication {
|
||||
auth_type PASS
|
||||
auth_pass password
|
||||
}
|
||||
|
||||
track_script {
|
||||
check
|
||||
}
|
||||
|
||||
notify_master /etc/keepalived/master.sh
|
||||
}
|
||||
|
||||
virtual_server {{ keepalived_ip }} 8080 {
|
||||
delay_loop 6
|
||||
lb_algo rr
|
||||
lb_kind NAT
|
||||
protocol TCP
|
||||
|
||||
real_server {{ item.ip }} 8080 {
|
||||
TCP_CHECK {
|
||||
connect_timeout 10
|
||||
}
|
||||
}
|
||||
}
|
2
dev/config/lighttpd.conf
Normal file
2
dev/config/lighttpd.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
server.document-root = "/var/www/"
|
||||
server.port = 8080
|
3
dev/config/master.sh
Executable file
3
dev/config/master.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "$(date) I am now master" >> /var/log/_keepalived.log
|
|
@ -8,7 +8,12 @@ services:
|
|||
context: ./server/
|
||||
volumes:
|
||||
- ./config/{{ item.item }}-wg0.conf:/etc/wireguard/wg0.conf
|
||||
- ../target/x86_64-unknown-linux-musl/debug/wgvirtipd:/usr/local/bin/wgvirtipd
|
||||
- ./config/{{ item.item }}-keepalived.conf:/etc/keepalived/keepalived.conf
|
||||
- ./config/check.sh:/etc/keepalived/check.sh
|
||||
- ./config/master.sh:/etc/keepalived/master.sh
|
||||
- ./config/lighttpd.conf:/etc/lighttpd/lighttpd.conf
|
||||
- ./config/{{ item.item }}-index.html:/var/www/index.html
|
||||
- ../target/x86_64-unknown-linux-musl/debug:/opt/wgvirtipd
|
||||
networks:
|
||||
- default
|
||||
expose:
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
FROM alpine
|
||||
|
||||
RUN apk add --no-cache wireguard-tools-wg-quick
|
||||
RUN apk add --no-cache \
|
||||
wireguard-tools-wg-quick \
|
||||
keepalived \
|
||||
lighttpd \
|
||||
curl \
|
||||
&& ln -s /opt/wgvirtipd/wgvirtipd /usr/local/bin/wgvirtipd
|
||||
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ]
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
lighthttpd_conf=/etc/lighttpd/lighttpd.conf
|
||||
wg-quick up wg0
|
||||
lighttpd -tt -f "$lighthttpd_conf" && lighttpd -f "$lighthttpd_conf" || true
|
||||
|
||||
"$@"
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
mask_bits: 24
|
||||
base_ip: 10.2.0.0
|
||||
port: 51871
|
||||
keepalived_ip: 10.2.0.100
|
||||
tasks:
|
||||
- name: generate keypair
|
||||
shell: |
|
||||
|
@ -13,11 +14,19 @@
|
|||
pub="$(echo "$priv" | wg pubkey)"
|
||||
base_ip="{{ base_ip }}"
|
||||
my_ip="$(echo "$base_ip" | sed 's/0$/{{ item }}/')"
|
||||
if [[ {{item}} -eq 1 ]]; then
|
||||
state=MASTER
|
||||
else
|
||||
state=BACKUP
|
||||
fi
|
||||
priority=$((100 - {{ item }}))
|
||||
jq --null-input \
|
||||
--arg priv "$priv" \
|
||||
--arg pub "$pub" \
|
||||
--arg my_ip "$my_ip" \
|
||||
'{"private_key": $priv, "public_key": $pub, "item": "vm{{ item }}", "ip": $my_ip}'
|
||||
--arg state "$state" \
|
||||
--arg priority "$priority" \
|
||||
'{"private_key": $priv, "public_key": $pub, "item": "vm{{ item }}", "ip": $my_ip, "keepalived_state": $state, "keepalived_priority": $priority}'
|
||||
with_items: ["1", "2", "3", "4"]
|
||||
register: keypairs_
|
||||
- set_fact:
|
||||
|
@ -30,6 +39,14 @@
|
|||
src: ./config/wg0.conf.tmpl
|
||||
dest: ./config/{{ item.item }}-wg0.conf
|
||||
with_items: "{{ keypairs }}"
|
||||
- template:
|
||||
src: ./config/keepalived.conf.tmpl
|
||||
dest: ./config/{{ item.item }}-keepalived.conf
|
||||
with_items: "{{ keypairs }}"
|
||||
- template:
|
||||
src: ./config/index.html.tmpl
|
||||
dest: ./config/{{ item.item }}-index.html
|
||||
with_items: "{{ keypairs }}"
|
||||
- template:
|
||||
src: ./docker-compose.yaml.tmpl
|
||||
dest: ./docker-compose.yaml
|
||||
|
|
68
src/event.rs
Normal file
68
src/event.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cidr::IpInet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
type WgLink = String;
|
||||
type WgPeer = String;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct EventInfo {
|
||||
link: WgLink,
|
||||
cidr: IpInet,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum Event {
|
||||
Add(EventInfo),
|
||||
Del(EventInfo),
|
||||
}
|
||||
|
||||
struct EventParseError<T>(T);
|
||||
|
||||
impl FromStr for Event {
|
||||
type Err = EventParseError<&'static str>;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let re = regex::Regex::new(r"^(Deleted )?\d+: ([a-z0-9]+)\s+inet ((\d+\.?){4}(/\d+)?).*$")
|
||||
.unwrap();
|
||||
|
||||
if let Some(captures) = re.captures(s) {
|
||||
let is_add = captures.get(1).is_none();
|
||||
let _is_delete = captures.get(1).is_some();
|
||||
let link: WgLink = captures.get(2).unwrap().as_str().into();
|
||||
let cidr: IpInet = IpInet::from_str(captures.get(3).unwrap().as_str()).unwrap();
|
||||
if is_add {
|
||||
Ok(Event::Add(EventInfo { link, cidr }))
|
||||
} else {
|
||||
Ok(Event::Del(EventInfo { link, cidr }))
|
||||
}
|
||||
} else {
|
||||
Err(Self::Err {
|
||||
0: "Line couldn't be parsed as event",
|
||||
})
|
||||
}
|
||||
|
||||
//if re_send_add.is_match(s) {
|
||||
// send_add = true;
|
||||
//} else if re_send_del.is_match(s) {
|
||||
// send_del = true;
|
||||
//}
|
||||
//if send_add | send_del {
|
||||
// cidr_ = re_extract_ip.captures(s).unwrap().get(1).unwrap().as_str();
|
||||
// public_key = send_daemon_get_public_key(&iface).await;
|
||||
//} else {
|
||||
// return Err<Self::Err>;
|
||||
//}
|
||||
//if send_add {
|
||||
// let request = hyper::Request::builder()
|
||||
// .method(hyper::Method::PATCH)
|
||||
// .uri(format!("http://{peer}:port/wireguard/{iface}/peer/{public_key}/address", peer=cidr_, iface=iface, public_key=public_key))
|
||||
// .body(hyper::Body::empty()).unwrap();
|
||||
// hyper::Client::new().request(request).await.unwrap();
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
type EventStore = Arc<Mutex<Vec<Event>>>;
|
125
src/main.rs
125
src/main.rs
|
@ -1,6 +1,14 @@
|
|||
use clap::Parser;
|
||||
use std::str::FromStr;
|
||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||
use warp::Filter;
|
||||
use wg::Peer;
|
||||
|
||||
mod event;
|
||||
mod wg;
|
||||
|
||||
type WgLink = String;
|
||||
type WgPeer = String;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
|
@ -8,12 +16,9 @@ struct CliArguments {
|
|||
#[arg(short, long)]
|
||||
addr: std::net::SocketAddr,
|
||||
#[arg(short, long)]
|
||||
link: String,
|
||||
link: WgLink,
|
||||
}
|
||||
|
||||
type WgLink = String;
|
||||
type WgPeer = String;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RejectCommandFailedToExecute;
|
||||
impl warp::reject::Reject for RejectCommandFailedToExecute {}
|
||||
|
@ -154,9 +159,106 @@ async fn wg_del_address(
|
|||
}
|
||||
}
|
||||
|
||||
async fn send_daemon_get_public_key(iface: &WgLink) -> WgPeer {
|
||||
let mut command = tokio::process::Command::new("wg");
|
||||
command.arg("show").arg(&iface).arg("public-key");
|
||||
println!("command = {:?}", command);
|
||||
let output = command.output().await.unwrap();
|
||||
std::str::from_utf8(output.stdout.as_ref())
|
||||
.unwrap()
|
||||
.trim_end_matches("\n")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
async fn send_daemon(iface: WgLink, port: u16, peers: Vec<Peer>) -> () {
|
||||
let re_send_add = regex::Regex::new(r"^\d+:.*\n$").unwrap();
|
||||
let re_send_del = regex::Regex::new(r"^Deleted \d+:.*\n$").unwrap();
|
||||
let re_extract_ip = regex::Regex::new(r"^.*inet ((\d+\.?){4}(/\d+)?).*\n$").unwrap();
|
||||
let mut command = tokio::process::Command::new("ip");
|
||||
command.arg("monitor").arg("address").arg("dev").arg(&iface);
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
println!("{:?}", command);
|
||||
let mut child = command.spawn().unwrap();
|
||||
let stdout = child.stdout.take().unwrap();
|
||||
let mut reader = BufReader::new(stdout);
|
||||
|
||||
loop {
|
||||
let mut buffer = String::new();
|
||||
let mut send_add = false;
|
||||
let mut send_del = false;
|
||||
|
||||
let cidr_: String;
|
||||
let public_key;
|
||||
|
||||
reader.read_line(&mut buffer).await.unwrap();
|
||||
if re_send_add.is_match(&buffer) {
|
||||
send_add = true;
|
||||
} else if re_send_del.is_match(&buffer) {
|
||||
send_del = true;
|
||||
}
|
||||
if send_add | send_del {
|
||||
cidr_ = urlencoding::encode(
|
||||
re_extract_ip
|
||||
.captures(&buffer)
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str(),
|
||||
)
|
||||
.into();
|
||||
public_key = send_daemon_get_public_key(&iface).await;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if send_add {
|
||||
for peer in peers.iter() {
|
||||
let peer_ip = peer.allowed_ips.get(0).unwrap();
|
||||
let u = format!(
|
||||
"http://{peer}:{port}/wireguard/{iface}/peer/{public_key}/address",
|
||||
peer = peer_ip,
|
||||
port = port,
|
||||
iface = iface,
|
||||
public_key = public_key
|
||||
);
|
||||
println!("{:?}", u);
|
||||
let request = hyper::Request::builder()
|
||||
.method(hyper::Method::POST)
|
||||
.uri(&u)
|
||||
.body(hyper::Body::from(cidr_.clone()))
|
||||
.unwrap();
|
||||
let r = hyper::Client::new().request(request).await;
|
||||
println!("{:?}", r);
|
||||
}
|
||||
} else if send_del {
|
||||
for peer in peers.iter() {
|
||||
let peer_ip = peer.allowed_ips.get(0).unwrap();
|
||||
let u = format!(
|
||||
"http://{peer}:{port}/wireguard/{iface}/peer/{public_key}/address/{cidr}",
|
||||
peer = peer_ip,
|
||||
port = port,
|
||||
iface = iface,
|
||||
public_key = public_key,
|
||||
cidr = &cidr_
|
||||
);
|
||||
println!("{:?}", u);
|
||||
let request = hyper::Request::builder()
|
||||
.method(hyper::Method::DELETE)
|
||||
.uri(&u)
|
||||
.body(hyper::Body::empty())
|
||||
.unwrap();
|
||||
let r = hyper::Client::new().request(request).await;
|
||||
println!("{:?}", r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BUILD_INFO: &str = include!(concat!(env!("OUT_DIR"), "/buildinfo.txt"));
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args = CliArguments::parse();
|
||||
let config = wg::Wg::from_file(&args.link).await;
|
||||
|
||||
let base = warp::path("wireguard")
|
||||
.and(warp::path::param::<WgLink>())
|
||||
|
@ -167,7 +269,13 @@ async fn main() {
|
|||
.and(warp::path::end())
|
||||
.and(warp::post())
|
||||
.and(warp::body::bytes().map(|b: bytes::Bytes| {
|
||||
cidr::IpInet::from_str(std::str::from_utf8(b.as_ref()).unwrap().trim()).unwrap()
|
||||
cidr::IpInet::from_str(
|
||||
urlencoding::decode(std::str::from_utf8(b.as_ref()).unwrap().trim())
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}))
|
||||
.and_then(wg_add_address);
|
||||
let address_del = base
|
||||
|
@ -177,5 +285,10 @@ async fn main() {
|
|||
.and_then(wg_del_address);
|
||||
let routes = address_add.or(address_del);
|
||||
|
||||
warp::serve(routes).run(args.addr).await;
|
||||
println!("{}", BUILD_INFO);
|
||||
|
||||
let (r0, r1) = tokio::join!(
|
||||
warp::serve(routes).run(args.addr),
|
||||
send_daemon(args.link, args.addr.port(), config.peers),
|
||||
);
|
||||
}
|
||||
|
|
141
src/wg.rs
Normal file
141
src/wg.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
use cidr::IpInet;
|
||||
use serde::de::{self, Error as _, MapAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_with::formats::CommaSeparator;
|
||||
use serde_with::StringWithSeparator;
|
||||
use serde_with::{serde_as, Map, Seq};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn de_one_many<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: serde::Deserialize<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneMany<T> {
|
||||
One(T),
|
||||
Vec(Vec<T>),
|
||||
}
|
||||
match OneMany::<T>::deserialize(deserializer)? {
|
||||
OneMany::One(t) => Ok(vec![t]),
|
||||
OneMany::Vec(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
|
||||
fn de_one_many_none<'de, T, D>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: serde::Deserialize<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum OneManyNone<T> {
|
||||
None,
|
||||
One(T),
|
||||
Vec(Vec<T>),
|
||||
}
|
||||
match OneManyNone::<T>::deserialize(deserializer)? {
|
||||
OneManyNone::None => Ok(None),
|
||||
OneManyNone::One(t) => Ok(Some(vec![t])),
|
||||
OneManyNone::Vec(v) => Ok(Some(v)),
|
||||
}
|
||||
}
|
||||
|
||||
fn de_host<'de, D>(deserializer: D) -> Result<(url::Host<String>, u16), D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let v = String::deserialize(deserializer)?;
|
||||
let u = url::Url::parse(format!("a://{}", v).as_ref()).unwrap();
|
||||
let host = match u.host().unwrap() {
|
||||
url::Host::Domain(s) => url::Host::Domain(String::from(s)),
|
||||
url::Host::Ipv4(a) => url::Host::Ipv4(a),
|
||||
url::Host::Ipv6(a) => url::Host::Ipv6(a),
|
||||
};
|
||||
let port = u.port().unwrap();
|
||||
return Ok((host, port));
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Peer {
|
||||
pub public_key: String,
|
||||
pub preshared_key: Option<String>,
|
||||
#[serde(rename = "AllowedIPs")]
|
||||
#[serde_as(as = "StringWithSeparator::<CommaSeparator, IpInet>")]
|
||||
pub allowed_ips: Vec<IpInet>,
|
||||
#[serde(deserialize_with = "de_host")]
|
||||
pub endpoint: (url::Host, u16),
|
||||
pub persistent_keepalive: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct Interface {
|
||||
pub private_key: String,
|
||||
pub listen_port: Option<u16>,
|
||||
pub fw_mark: Option<u32>,
|
||||
// wg-quick
|
||||
#[serde_as(as = "StringWithSeparator::<CommaSeparator, IpInet>")]
|
||||
pub address: Vec<IpInet>, // TODO address can actually be left out with normal wg set link
|
||||
#[serde(rename = "DNS")]
|
||||
pub dns: Option<url::Host>,
|
||||
#[serde(rename = "MTU")]
|
||||
pub mtu: Option<u32>,
|
||||
pub table: Option<String>,
|
||||
pub pre_up: Option<String>,
|
||||
pub post_up: Option<String>,
|
||||
pub pre_down: Option<String>,
|
||||
pub post_down: Option<String>,
|
||||
pub save_config: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
enum PeerOrInterface {
|
||||
Peer(Peer),
|
||||
Interface(Interface),
|
||||
}
|
||||
|
||||
type _Wg = Vec<PeerOrInterface>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Wg {
|
||||
pub interface: Interface,
|
||||
pub peers: Vec<Peer>,
|
||||
}
|
||||
|
||||
impl Wg {
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
let w: _Wg = serde_ini::de::from_str(s).unwrap();
|
||||
let mut interface: Option<Interface> = None;
|
||||
let mut peers: Vec<Peer> = vec![];
|
||||
for i in w {
|
||||
match i {
|
||||
PeerOrInterface::Interface(i) => interface = Some(i),
|
||||
PeerOrInterface::Peer(p) => peers.push(p),
|
||||
}
|
||||
}
|
||||
let interface = interface.unwrap();
|
||||
Self { interface, peers }
|
||||
}
|
||||
|
||||
pub async fn from_file(path: impl AsRef<Path>) -> Self {
|
||||
let mut p: &Path = path.as_ref();
|
||||
let mut new_p;
|
||||
if p.parent() == Some(Path::new("")) {
|
||||
new_p = PathBuf::new();
|
||||
new_p.push("/etc/wireguard");
|
||||
new_p.push(format!("{}.conf", p.to_str().unwrap()));
|
||||
p = &new_p;
|
||||
}
|
||||
let s = tokio::fs::read_to_string(p).await.unwrap();
|
||||
Self::from_str(&s)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue