this shit works yo
This commit is contained in:
Generated
+115
@@ -26,6 +26,56 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.100"
|
version = "1.0.100"
|
||||||
@@ -206,6 +256,46 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color-eyre"
|
name = "color-eyre"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
@@ -233,6 +323,12 @@ dependencies = [
|
|||||||
"tracing-error",
|
"tracing-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -603,6 +699,12 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
@@ -800,6 +902,12 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owo-colors"
|
name = "owo-colors"
|
||||||
version = "4.2.3"
|
version = "4.2.3"
|
||||||
@@ -1369,6 +1477,12 @@ version = "2.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1382,6 +1496,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"futures",
|
"futures",
|
||||||
"lda-ipjs",
|
"lda-ipjs",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ publish = ["gitea"]
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
axum = { version = "0", features = ["macros"] }
|
axum = { version = "0", features = ["macros"] }
|
||||||
axum-extra = { version = "0", features = ["query"] }
|
axum-extra = { version = "0", features = ["query"] }
|
||||||
|
clap = { version = "4", features = ["derive"] }
|
||||||
color-eyre = "0"
|
color-eyre = "0"
|
||||||
futures = "0"
|
futures = "0"
|
||||||
macaddr = { version = "1", features = ["serde", "serde_std"] }
|
macaddr = { version = "1", features = ["serde", "serde_std"] }
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ try { $PSStyle.OutputRendering = 'Host' } catch {}
|
|||||||
|
|
||||||
. "$PSScriptRoot/lib.ps1"
|
. "$PSScriptRoot/lib.ps1"
|
||||||
|
|
||||||
|
$Pass = Get-DefaultPassword $Pass
|
||||||
|
|
||||||
function Get-DeployScript {
|
function Get-DeployScript {
|
||||||
param($DeployPreferred, $DeployTmp, $RemoteTmp, $RemotePath, $RestartFlag)
|
param($DeployPreferred, $DeployTmp, $RemoteTmp, $RemotePath, $RestartFlag)
|
||||||
return @"
|
return @"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ BIN=/root/.bin/wakey
|
|||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
procd_open_instance
|
procd_open_instance
|
||||||
procd_set_param command "$BIN"
|
procd_set_param command "$BIN" http --host :: --port 12012
|
||||||
procd_set_param respawn 5 1 0
|
procd_set_param respawn 5 1 0
|
||||||
procd_set_param stdout 1
|
procd_set_param stdout 1
|
||||||
procd_set_param stderr 1
|
procd_set_param stderr 1
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# Shared functions for wakey scripts
|
# Shared functions for wakey scripts
|
||||||
|
|
||||||
|
function Get-DefaultPassword {
|
||||||
|
param([string]$Password)
|
||||||
|
|
||||||
|
if ($Password) {
|
||||||
|
return $Password
|
||||||
|
}
|
||||||
|
|
||||||
|
$pwPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'ar_data/pw'
|
||||||
|
if (-not (Test-Path $pwPath)) {
|
||||||
|
throw "Password not provided and default file not found: $pwPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Get-Content -Raw $pwPath).Trim()
|
||||||
|
}
|
||||||
|
|
||||||
function Invoke-Ext {
|
function Invoke-Ext {
|
||||||
param($Exe, $Arguments, $Label)
|
param($Exe, $Arguments, $Label)
|
||||||
$displayArgs = $Arguments.Clone()
|
$displayArgs = $Arguments.Clone()
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ $ErrorActionPreference = "Stop"
|
|||||||
|
|
||||||
. "$PSScriptRoot/lib.ps1"
|
. "$PSScriptRoot/lib.ps1"
|
||||||
|
|
||||||
|
$password = Get-DefaultPassword $password
|
||||||
|
|
||||||
# Build tests and capture output
|
# Build tests and capture output
|
||||||
Write-Host "Building tests for $Package..." -ForegroundColor Cyan
|
Write-Host "Building tests for $Package..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
|||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
pub mod arpparse;
|
||||||
|
pub mod dhcpparse;
|
||||||
|
pub mod route;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use std::{io, net::SocketAddr};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use axum::Router;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
|
use wakey_core::{
|
||||||
|
DeviceFilters, DeviceQuery, DhcpLease, DhcpLeaseWithState, NeighborEntry, QueryInput, Status,
|
||||||
|
WakeResult, WakeTarget,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type StatusResponse = Status<NeighborEntry>;
|
||||||
|
|
||||||
|
pub async fn resolve_query(input: impl Into<String>) -> Result<DeviceQuery> {
|
||||||
|
Ok(match wakey_linux::devices::classify_query(input.into()).await {
|
||||||
|
QueryInput::Ip(ip_addr) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
ips: vec![ip_addr],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryInput::Mac(mac_addr) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
macs: vec![mac_addr],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryInput::Dev(dev) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
devs: vec![dev],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryInput::Nud(state) => DeviceQuery {
|
||||||
|
filter: DeviceFilters {
|
||||||
|
nuds: vec![state],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
QueryInput::Name(name) => DeviceQuery {
|
||||||
|
name: Some(name),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_status(query: DeviceQuery) -> Result<StatusResponse> {
|
||||||
|
let table = wakey_linux::devices::query_status(&query).await?;
|
||||||
|
Ok(Status {
|
||||||
|
name: query.name,
|
||||||
|
table,
|
||||||
|
filters: query.filter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_status_for_input(input: impl Into<String>) -> Result<StatusResponse> {
|
||||||
|
let query = resolve_query(input).await?;
|
||||||
|
get_status(query).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_leases(include_state: bool) -> Result<Vec<DhcpLeaseWithState>> {
|
||||||
|
let leases = wakey_linux::dhcp::read_dhcp_leases_with_names()
|
||||||
|
.await
|
||||||
|
.context("failed to read DHCP leases")?;
|
||||||
|
if include_state {
|
||||||
|
Ok(wakey_linux::dhcp::enrich_leases_with_nud_state(leases).await)
|
||||||
|
} else {
|
||||||
|
Ok(leases
|
||||||
|
.into_iter()
|
||||||
|
.map(|lease_line| DhcpLeaseWithState {
|
||||||
|
lease_line,
|
||||||
|
nud_state: None,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wake_targets(targets: Vec<WakeTarget>) -> Result<WakeResult> {
|
||||||
|
let result = wakey_linux::wake::wake_many(targets)
|
||||||
|
.await
|
||||||
|
.context("failed to send wake packets")?;
|
||||||
|
Ok(WakeResult { result })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wake_from_query(input: impl Into<String>) -> Result<WakeResult> {
|
||||||
|
let status = get_status_for_input(input).await?;
|
||||||
|
let targets = status
|
||||||
|
.table
|
||||||
|
.into_iter()
|
||||||
|
.map(|entry| WakeTarget {
|
||||||
|
ip: Some(entry.ip),
|
||||||
|
mac: entry.mac,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
wake_targets(targets).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_interfaces() -> Result<Vec<String>> {
|
||||||
|
Ok(wakey_linux::devices::devs_sorted().await)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_ips(name: impl AsRef<str>) -> Result<Vec<std::net::IpAddr>> {
|
||||||
|
Ok(wakey_linux::devices::get_ips(name.as_ref())
|
||||||
|
.await?
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn http_app(static_root: std::path::PathBuf) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.nest("/api", route::api_router())
|
||||||
|
.fallback_service(
|
||||||
|
axum::routing::get_service(
|
||||||
|
ServeDir::new(static_root)
|
||||||
|
.append_index_html_on_directories(true)
|
||||||
|
.precompressed_br()
|
||||||
|
.precompressed_deflate()
|
||||||
|
.precompressed_gzip()
|
||||||
|
.precompressed_zstd(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve_http(addr: SocketAddr, static_root: std::path::PathBuf) -> io::Result<()> {
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
|
axum::serve(listener, http_app(static_root).into_make_service()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve_http_from_current_exe(addr: SocketAddr) -> io::Result<()> {
|
||||||
|
let exe = std::env::current_exe()?;
|
||||||
|
let root = exe
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| io::Error::other("no parent dir"))?;
|
||||||
|
serve_http(addr, root.join("static")).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn leases_without_state(leases: Vec<DhcpLease>) -> Vec<DhcpLeaseWithState> {
|
||||||
|
leases
|
||||||
|
.into_iter()
|
||||||
|
.map(|lease_line| DhcpLeaseWithState {
|
||||||
|
lease_line,
|
||||||
|
nud_state: None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
use wakey_core::{NeighborState, WakeStatus};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolve_query_parses_ip() {
|
||||||
|
let query = resolve_query("192.168.1.10").await.expect("resolve query");
|
||||||
|
assert_eq!(query.filter.ips, vec![IpAddr::V4(Ipv4Addr::new(192, 168, 1, 10))]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolve_query_parses_mac() {
|
||||||
|
let query = resolve_query("aa:bb:cc:dd:ee:ff")
|
||||||
|
.await
|
||||||
|
.expect("resolve query");
|
||||||
|
assert_eq!(query.filter.macs.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn resolve_query_parses_nud() {
|
||||||
|
let query = resolve_query("reachable").await.expect("resolve query");
|
||||||
|
assert_eq!(query.filter.nuds, vec![NeighborState::Reachable]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leases_without_state_clears_nud_state() {
|
||||||
|
let leases = vec![DhcpLease {
|
||||||
|
expires_epoch: 1,
|
||||||
|
ip: IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||||
|
mac: "aa:bb:cc:dd:ee:ff".parse().expect("mac"),
|
||||||
|
name: Some("pc".into()),
|
||||||
|
}];
|
||||||
|
let out = leases_without_state(leases);
|
||||||
|
assert_eq!(out.len(), 1);
|
||||||
|
assert!(out[0].nud_state.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn wake_targets_marks_incomplete() {
|
||||||
|
let out = wake_targets(vec![WakeTarget {
|
||||||
|
ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
|
||||||
|
mac: None,
|
||||||
|
}])
|
||||||
|
.await
|
||||||
|
.expect("wake");
|
||||||
|
assert_eq!(out.result.len(), 1);
|
||||||
|
assert_eq!(out.result[0].status, WakeStatus::Incomplete);
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
-53
@@ -1,65 +1,126 @@
|
|||||||
//! braindead version v0.1.x
|
use std::net::{IpAddr, SocketAddr};
|
||||||
//!
|
|
||||||
//! # whats next
|
|
||||||
//!
|
|
||||||
//! for version 2 i hope to have:
|
|
||||||
//!
|
|
||||||
//! 1. idk reworked frontend;
|
|
||||||
//! 2. incorporate ip -j;
|
|
||||||
//! 3. small 1-5 second caching;
|
|
||||||
|
|
||||||
use axum::Router;
|
use clap::{Args, Parser, Subcommand};
|
||||||
use tokio::net::TcpListener;
|
use wakey_core::{DeviceFilters, DeviceQuery};
|
||||||
mod arpparse;
|
|
||||||
mod dhcpparse;
|
|
||||||
mod route;
|
|
||||||
mod utils;
|
|
||||||
use std::{env, io};
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[derive(Parser)]
|
||||||
#[tokio::main]
|
#[command(name = "wakey")]
|
||||||
async fn entry() -> io::Result<()> {
|
#[command(version, about = "Wakey service CLI and HTTP adapter")]
|
||||||
use crate::route::api_router;
|
struct Cli {
|
||||||
use axum::routing::get_service;
|
#[command(subcommand)]
|
||||||
use tower_http::services::ServeDir;
|
command: Command,
|
||||||
let exe = env::current_exe()?;
|
}
|
||||||
let root = exe
|
|
||||||
.parent()
|
|
||||||
.ok_or_else(|| io::Error::other("no parent dir"))?;
|
|
||||||
let static_dir = ServeDir::new(root.join("static"))
|
|
||||||
.append_index_html_on_directories(true)
|
|
||||||
.precompressed_br()
|
|
||||||
.precompressed_deflate()
|
|
||||||
.precompressed_gzip()
|
|
||||||
.precompressed_zstd();
|
|
||||||
let app = Router::new()
|
|
||||||
// .route("/home", get(home))
|
|
||||||
// .route("/", get(home_2))
|
|
||||||
// .merge(home_2_route())
|
|
||||||
// .route("/status", get(get_status_2))
|
|
||||||
.nest("/api", api_router())
|
|
||||||
.fallback_service(get_service(static_dir));
|
|
||||||
|
|
||||||
let port = TcpListener::bind("[::]:12012").await?;
|
#[derive(Subcommand)]
|
||||||
axum::serve(port, app.into_make_service()).await?;
|
enum Command {
|
||||||
Ok(())
|
Http(HttpArgs),
|
||||||
|
Status(StatusArgs),
|
||||||
|
Leases(LeasesArgs),
|
||||||
|
Wake(WakeArgs),
|
||||||
|
Devs,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct HttpArgs {
|
||||||
|
#[arg(long, default_value = "::")]
|
||||||
|
host: IpAddr,
|
||||||
|
#[arg(long, default_value_t = 12012)]
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct LeasesArgs {
|
||||||
|
#[arg(long)]
|
||||||
|
include_state: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct WakeArgs {
|
||||||
|
query: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct StatusArgs {
|
||||||
|
query: Option<String>,
|
||||||
|
#[arg(long)]
|
||||||
|
name: Option<String>,
|
||||||
|
#[arg(long = "ip")]
|
||||||
|
ips: Vec<std::net::IpAddr>,
|
||||||
|
#[arg(long = "dev")]
|
||||||
|
devs: Vec<String>,
|
||||||
|
#[arg(long = "nud")]
|
||||||
|
nuds: Vec<wakey_core::NeighborState>,
|
||||||
|
#[arg(long = "mac")]
|
||||||
|
macs: Vec<macaddr::MacAddr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_args_to_query(args: StatusArgs) -> wakey_core::DeviceQuery {
|
||||||
|
if let Some(query) = args.query.as_ref()
|
||||||
|
&& args.name.is_none()
|
||||||
|
&& args.ips.is_empty()
|
||||||
|
&& args.devs.is_empty()
|
||||||
|
&& args.nuds.is_empty()
|
||||||
|
&& args.macs.is_empty()
|
||||||
|
{
|
||||||
|
return DeviceQuery {
|
||||||
|
name: Some(query.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceQuery {
|
||||||
|
name: args.name.or(args.query),
|
||||||
|
filter: DeviceFilters {
|
||||||
|
ips: args.ips,
|
||||||
|
devs: args.devs,
|
||||||
|
nuds: args.nuds,
|
||||||
|
macs: args.macs,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
fn main() -> color_eyre::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
use std::net::ToSocketAddrs;
|
Err(anyhow::anyhow!(
|
||||||
color_eyre::install()?;
|
|
||||||
// use crate::arpparse::NUDState;
|
|
||||||
// println!("{}", NUDState::Reachable.to_string().to_lowercase());
|
|
||||||
println!("{:?}", "svuhuvshdv:331".to_socket_addrs());
|
|
||||||
// Err(Os { code: 11001, kind: Uncategorized, message: "No such host is known." })
|
|
||||||
Err(color_eyre::eyre::eyre!(
|
|
||||||
"OS not supported! run this on your ahh router!"
|
"OS not supported! run this on your ahh router!"
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn main() -> color_eyre::Result<()> {
|
#[tokio::main]
|
||||||
color_eyre::install()?;
|
async fn main() -> anyhow::Result<()> {
|
||||||
Ok(entry()?)
|
let cli = Cli::parse();
|
||||||
|
match cli.command {
|
||||||
|
Command::Http(args) => {
|
||||||
|
let addr = SocketAddr::new(args.host, args.port);
|
||||||
|
wakey::serve_http_from_current_exe(addr).await?;
|
||||||
|
}
|
||||||
|
Command::Status(args) => {
|
||||||
|
let query = status_args_to_query(args);
|
||||||
|
let status = if query.name.is_some()
|
||||||
|
&& query.filter.ips.is_empty()
|
||||||
|
&& query.filter.devs.is_empty()
|
||||||
|
&& query.filter.nuds.is_empty()
|
||||||
|
&& query.filter.macs.is_empty()
|
||||||
|
{
|
||||||
|
wakey::get_status_for_input(query.name.clone().unwrap_or_default()).await?
|
||||||
|
} else {
|
||||||
|
wakey::get_status(query).await?
|
||||||
|
};
|
||||||
|
println!("{}", serde_json::to_string_pretty(&status)?);
|
||||||
|
}
|
||||||
|
Command::Leases(args) => {
|
||||||
|
let leases = wakey::get_leases(args.include_state).await?;
|
||||||
|
println!("{}", serde_json::to_string_pretty(&leases)?);
|
||||||
|
}
|
||||||
|
Command::Wake(args) => {
|
||||||
|
let result = wakey::wake_from_query(args.query).await?;
|
||||||
|
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||||
|
}
|
||||||
|
Command::Devs => {
|
||||||
|
let devs = wakey::list_interfaces().await?;
|
||||||
|
println!("{}", serde_json::to_string_pretty(&devs)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-39
@@ -1,52 +1,24 @@
|
|||||||
use crate::route::error::ApiError;
|
use crate::route::error::ApiError;
|
||||||
use crate::utils::query::parser::{QueryType, parse_query};
|
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::{extract::Path, response::Redirect};
|
use axum::{extract::Path, response::Redirect};
|
||||||
use wakey_core::{DeviceFilters as Filters, DeviceQuery};
|
use wakey_core::DeviceQuery;
|
||||||
|
|
||||||
use crate::route::status::NamePath;
|
use crate::route::status::NamePath;
|
||||||
use crate::utils::query::get_ips;
|
|
||||||
|
|
||||||
// Smart redirect: accept IP, MAC, dev, or NUD state and redirect to /api/status accordingly
|
// Smart redirect: accept IP, MAC, dev, or NUD state and redirect to /api/status accordingly
|
||||||
pub async fn status_smart_redirect(
|
pub async fn status_smart_redirect(
|
||||||
Path(q): Path<String>,
|
Path(q): Path<String>,
|
||||||
) -> axum::response::Result<Redirect, impl IntoResponse> {
|
) -> axum::response::Result<Redirect, impl IntoResponse> {
|
||||||
// no less bullshit
|
let query: DeviceQuery = match crate::resolve_query(q).await {
|
||||||
let query = match parse_query(q).await {
|
Ok(query) => query,
|
||||||
QueryType::Ip(ip_addr) => DeviceQuery {
|
Err(e) => {
|
||||||
filter: Filters {
|
return Err(ApiError {
|
||||||
ips: vec![ip_addr],
|
error: e.to_string(),
|
||||||
..Default::default()
|
code: StatusCode::BAD_GATEWAY,
|
||||||
},
|
});
|
||||||
..Default::default()
|
}
|
||||||
},
|
|
||||||
QueryType::Mac(mac_addr) => DeviceQuery {
|
|
||||||
filter: Filters {
|
|
||||||
macs: vec![mac_addr],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryType::Dev(s) => DeviceQuery {
|
|
||||||
filter: Filters {
|
|
||||||
devs: vec![s],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryType::Nud(nudstate) => DeviceQuery {
|
|
||||||
filter: Filters {
|
|
||||||
nuds: vec![nudstate],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
QueryType::Name(n) => DeviceQuery {
|
|
||||||
name: Some(n),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
match serde_html_form::to_string(query) {
|
match serde_html_form::to_string(query) {
|
||||||
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
Ok(e) => Ok(Redirect::to(&format!("/api/status?{e}"))),
|
||||||
@@ -65,8 +37,8 @@ pub async fn status_redirect(Path(NamePath { name }): Path<NamePath>) -> Redirec
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
pub async fn ip(Path(name): Path<String>) -> impl IntoResponse {
|
||||||
get_ips(&name).await.map_or_else(
|
crate::get_ips(&name).await.map_or_else(
|
||||||
|e| ApiError::ise(e.to_string()).into_response(),
|
|e| ApiError::ise(e.to_string()).into_response(),
|
||||||
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
|ips| Json(ips).into_response(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-4
@@ -1,7 +1,10 @@
|
|||||||
use crate::utils::query::dev;
|
use crate::route::error::ApiError;
|
||||||
use axum::Json;
|
use axum::{Json, response::IntoResponse};
|
||||||
|
|
||||||
pub async fn devs_router() -> Json<Vec<String>> {
|
pub async fn devs_router() -> impl IntoResponse {
|
||||||
dev::devs_sorted().await.into()
|
match crate::list_interfaces().await {
|
||||||
|
Ok(devs) => Json(devs).into_response(),
|
||||||
|
Err(e) => ApiError::ise(e.to_string()).into_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Device listing endpoints
|
// Device listing endpoints
|
||||||
|
|||||||
+3
-13
@@ -1,8 +1,4 @@
|
|||||||
use crate::{
|
use crate::{route::error::ApiError, utils::parse::boolish_str};
|
||||||
dhcpparse::read_dhcp_leases_with_names,
|
|
||||||
route::error::ApiError,
|
|
||||||
utils::{parse::boolish_str, query::enrich_leases_with_nud_state},
|
|
||||||
};
|
|
||||||
use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
|
use axum::{Json, extract::Query, http::StatusCode, response::IntoResponse};
|
||||||
|
|
||||||
// DHCP lease endpoints
|
// DHCP lease endpoints
|
||||||
@@ -16,14 +12,8 @@ pub async fn get_dhcp_leases(
|
|||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let include_state = include_state.as_deref().map(boolish_str).unwrap_or(false);
|
let include_state = include_state.as_deref().map(boolish_str).unwrap_or(false);
|
||||||
|
|
||||||
match read_dhcp_leases_with_names().await {
|
match crate::get_leases(include_state).await {
|
||||||
Ok(leases_with_names) => {
|
Ok(leases) => (StatusCode::OK, Json(leases)).into_response(),
|
||||||
if !include_state {
|
|
||||||
return (StatusCode::OK, Json(leases_with_names)).into_response();
|
|
||||||
}
|
|
||||||
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
|
||||||
(StatusCode::OK, Json(out)).into_response()
|
|
||||||
}
|
|
||||||
Err(e) => ApiError {
|
Err(e) => ApiError {
|
||||||
error: e.to_string(),
|
error: e.to_string(),
|
||||||
code: StatusCode::BAD_GATEWAY,
|
code: StatusCode::BAD_GATEWAY,
|
||||||
|
|||||||
+3
-26
@@ -1,34 +1,11 @@
|
|||||||
use crate::route::error::ApiError;
|
use crate::route::error::ApiError;
|
||||||
use crate::utils::query::get_macs;
|
|
||||||
use axum::{Json, http::StatusCode, response::IntoResponse};
|
use axum::{Json, http::StatusCode, response::IntoResponse};
|
||||||
use axum_extra::extract::Query;
|
use axum_extra::extract::Query;
|
||||||
pub use wakey_core::{DeviceQuery, NamePath, Status};
|
pub use wakey_core::{DeviceQuery, NamePath, Status};
|
||||||
|
|
||||||
pub async fn get_status_json(
|
pub async fn get_status_json(Query(query): Query<DeviceQuery>) -> impl IntoResponse {
|
||||||
Query(DeviceQuery {
|
match crate::get_status(query).await {
|
||||||
name,
|
Ok(status) => (StatusCode::OK, Json(status)).into_response(),
|
||||||
filter: filters,
|
|
||||||
..
|
|
||||||
}): Query<DeviceQuery>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
match get_macs(
|
|
||||||
name.as_slice(),
|
|
||||||
&filters.ips,
|
|
||||||
&filters.devs,
|
|
||||||
&filters.nuds,
|
|
||||||
&filters.macs,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(table) => (
|
|
||||||
StatusCode::OK,
|
|
||||||
Json(Status {
|
|
||||||
name,
|
|
||||||
table,
|
|
||||||
filters,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
Err(error) => ApiError {
|
Err(error) => ApiError {
|
||||||
code: StatusCode::BAD_GATEWAY,
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: error
|
error: error
|
||||||
|
|||||||
+3
-50
@@ -1,60 +1,13 @@
|
|||||||
//! impls are at [`utils::wake::impl`](crate::utils::wake::r#impl) for some reason
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
/* use crate::arpparse::IpNeighLine;
|
|
||||||
use crate::route::api::Status; */
|
|
||||||
use crate::route::error::ApiError;
|
use crate::route::error::ApiError;
|
||||||
use crate::utils::wake::wake_one;
|
|
||||||
use axum::{extract::Json, http::StatusCode, response::IntoResponse};
|
use axum::{extract::Json, http::StatusCode, response::IntoResponse};
|
||||||
use futures::TryFutureExt;
|
pub use wakey_core::{WakeResult, WakeTarget};
|
||||||
use tokio::net::UdpSocket;
|
|
||||||
pub use wakey_core::{WakeResult, WakeTarget, WakeTargetResult};
|
|
||||||
|
|
||||||
pub async fn wake_multi(Json(req): Json<Vec<WakeTarget>>) -> impl IntoResponse {
|
pub async fn wake_multi(Json(req): Json<Vec<WakeTarget>>) -> impl IntoResponse {
|
||||||
match wake_multi_split(req).await {
|
match crate::wake_targets(req).await {
|
||||||
Ok(result) => (StatusCode::OK, Json(WakeResult { result })).into_response(),
|
Ok(result) => (StatusCode::OK, Json(result)).into_response(),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
let error = format!("Error: {}", error);
|
let error = format!("Error: {}", error);
|
||||||
ApiError::ise(error).into_response()
|
ApiError::ise(error).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// this is so bad
|
|
||||||
pub async fn wake_multi_split(
|
|
||||||
targets: impl IntoIterator<Item = WakeTarget>,
|
|
||||||
) -> io::Result<Vec<WakeTargetResult>> {
|
|
||||||
let sock = UdpSocket::bind("[::]:0")
|
|
||||||
.or_else(|_| UdpSocket::bind(":0"))
|
|
||||||
.await?;
|
|
||||||
sock.set_broadcast(true)?;
|
|
||||||
|
|
||||||
let iter = targets.into_iter().map(async |c| {
|
|
||||||
if !c.is_complete() {
|
|
||||||
WakeTargetResult::incomplete(c)
|
|
||||||
} else {
|
|
||||||
let t = c.try_into().expect("complete struct failed to try_into");
|
|
||||||
wake_one(&sock, t).await
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Ok(futures::future::join_all(iter).await)
|
|
||||||
}
|
|
||||||
/* #[derive(Debug, Serialize)]
|
|
||||||
pub struct WakeStatusLine {
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub status: IpNeighLine, // most powerful find of the century
|
|
||||||
pub wake_status: WakeTargetStatus,
|
|
||||||
}
|
|
||||||
pub type WakeStatus = Status<WakeStatusLine>; */
|
|
||||||
// /// return status BUT plus a indicator of i sent a wake.
|
|
||||||
// pub async fn wake_status(
|
|
||||||
// Query(DeviceQuery {
|
|
||||||
// name,
|
|
||||||
// ip,
|
|
||||||
// dev,
|
|
||||||
// nud,
|
|
||||||
// mac,
|
|
||||||
// ..
|
|
||||||
// }): Query<DeviceQuery>,
|
|
||||||
// ) -> impl IntoResponse {
|
|
||||||
// }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user