Compare commits
23
Commits
v0.1.6
...
cfba08c47a
No files matched your search
+7
-2
@@ -1,5 +1,10 @@
|
|||||||
|
[target.armv7-unknown-linux-musleabihf]
|
||||||
|
linker = "rust-lld"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
target = "armv7-unknown-linux-musleabihf"
|
target = "armv7-unknown-linux-musleabihf"
|
||||||
|
|
||||||
[target.armv7-unknown-linux-musleabihf]
|
[alias]
|
||||||
linker = "rust-lld"
|
ldabr = "b -r --target=target.armv7-unknown-linux-musleabihf"
|
||||||
|
t = "test -- --nocapture --test-threads=1"
|
||||||
|
tdebug = "test -- --nocapture --test-threads=1 --show-output"
|
||||||
File renamed without changes.
@@ -1,8 +1,10 @@
|
|||||||
you should use block code to refence code, like so:
|
you should use block code to refence code, like so:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
hello_world!(println);
|
hello_world!(println);
|
||||||
// indented lines
|
// indented lines
|
||||||
```
|
```
|
||||||
|
|
||||||
in the chat. Else it fucks up formatting.
|
in the chat. Else it fucks up formatting.
|
||||||
|
|
||||||
We work towards clean, maintainable design. im not the best at this thing.
|
We work towards clean, maintainable design. im not the best at this thing.
|
||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf",
|
"rust-analyzer.cargo.target": "armv7-unknown-linux-musleabihf",
|
||||||
"rust-analyzer.diagnostics.disabled": ["unlinked-file"]
|
"rust-analyzer.diagnostics.disabled": ["unlinked-file"]
|
||||||
}
|
}
|
||||||
Generated
+135
-3
@@ -32,6 +32,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -189,6 +195,12 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.41"
|
version = "0.4.41"
|
||||||
@@ -610,6 +622,22 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lda-ipjs"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"futures",
|
||||||
|
"macaddr",
|
||||||
|
"rtnetlink",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
|
"strum",
|
||||||
|
"thiserror 2.0.16",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.175"
|
version = "0.2.175"
|
||||||
@@ -669,6 +697,66 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-core"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4"
|
||||||
|
dependencies = [
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-packet-route"
|
||||||
|
version = "0.25.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ec2f5b6839be2a19d7fa5aab5bc444380f6311c2b693551cb80f45caaa7b5ef"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-proto"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-sys",
|
||||||
|
"thiserror 2.0.16",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "netlink-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -705,6 +793,12 @@ version = "4.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
|
checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@@ -767,6 +861,23 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rtnetlink"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08fd15aa4c64c34d0b3178e45ec6dad313a9f02b193376d501668a7950264bb7"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"netlink-packet-core",
|
||||||
|
"netlink-packet-route",
|
||||||
|
"netlink-proto",
|
||||||
|
"netlink-sys",
|
||||||
|
"nix",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.26"
|
version = "0.1.26"
|
||||||
@@ -999,13 +1110,33 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.16"
|
version = "2.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 2.0.16",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1179,19 +1310,20 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wakey"
|
name = "wakey"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"futures",
|
"futures",
|
||||||
|
"lda-ipjs",
|
||||||
"macaddr",
|
"macaddr",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_html_form",
|
"serde_html_form",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"strum",
|
"strum",
|
||||||
"thiserror",
|
"thiserror 2.0.16",
|
||||||
"tokio",
|
"tokio",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|||||||
+16
-3
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wakey"
|
name = "wakey"
|
||||||
version = "0.1.5"
|
version = "0.1.6"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = ["gitea"]
|
publish = ["gitea"]
|
||||||
|
|
||||||
@@ -16,7 +16,13 @@ serde_json = "1.0.143"
|
|||||||
serde_with = { version = "3.14.0", features = ["json"] }
|
serde_with = { version = "3.14.0", features = ["json"] }
|
||||||
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
|
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
|
||||||
thiserror = "2.0.16"
|
thiserror = "2.0.16"
|
||||||
tokio = { version = "1.47.1", features = ["fs", "process", "rt-multi-thread", "io-util"] }
|
tokio = { version = "1.47.1", features = [
|
||||||
|
"fs",
|
||||||
|
"process",
|
||||||
|
"rt-multi-thread",
|
||||||
|
"io-util",
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@@ -25,4 +31,11 @@ strip = true
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["very-smart-parsing"]
|
default = ["very-smart-parsing"]
|
||||||
very-smart-parsing = [] # this is the a-bit-redundant parse thing that copilot made
|
very-smart-parsing = [
|
||||||
|
] # this is the a-bit-redundant parse thing that copilot made
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["ipjs"]
|
||||||
|
|
||||||
|
[dependencies.lda-ipjs]
|
||||||
|
path = "ipjs"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "lda-ipjs"
|
||||||
|
description = "ip -j show schemas"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
macaddr = { version = "1.0.1", features = ["serde", "serde_std"] }
|
||||||
|
strum = { version = "0.27.2", features = ["derive", "strum_macros"] }
|
||||||
|
serde_json = "1.0.143"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_with = { version = "3.14.0", features = ["json"] }
|
||||||
|
thiserror = "2.0.16"
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
tokio = { version = "1.47.1", features = ["fs", "process", "rt-multi-thread", "io-util", "macros"] }
|
||||||
|
rtnetlink = "0.18.1"
|
||||||
|
futures = "0.3.31"
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
trait IpCommand {
|
||||||
|
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,20 @@
|
|||||||
|
//! low
|
||||||
|
//!
|
||||||
|
//! # lda-ipjs
|
||||||
|
//!
|
||||||
|
//! this package will represent all my needs with the all the subcommands of ip -j.
|
||||||
|
//!
|
||||||
|
//! ## what i need
|
||||||
|
//!
|
||||||
|
//! ```console
|
||||||
|
//! ip -j neigh show
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! i also need to see devices and idk MAYBE maybe not MAYBE UHHHHHH maybe broadcast
|
||||||
|
//!
|
||||||
|
//! LOWK if this were to be calls to kernel or some bullshit then PLEASE because doing ts parsing its hell cuh
|
||||||
|
|
||||||
|
pub mod subcommands;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
// i want a generalized way to build and call
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// ip address [ show [ dev IFNAME ] [ scope SCOPE-ID ] [ master DEVICE ]
|
||||||
|
// [ type TYPE ] [ to PREFIX ] [ FLAG-LIST ]
|
||||||
|
// [ label LABEL ] [up] [ vrf NAME ] ]
|
||||||
|
// fuck is this mean
|
||||||
|
|
||||||
|
// do i need all this? do i need anything but `ip -j a show dev br-lan`?
|
||||||
|
|
||||||
|
// TYPE := { vlan | veth | vcan | vxcan | dummy | ifb | macvlan | macvtap |
|
||||||
|
// bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | lowpan |
|
||||||
|
// gre | gretap | erspan | ip6gre | ip6gretap | ip6erspan | vti |
|
||||||
|
// nlmon | can | bond_slave | ipvlan | geneve | bridge_slave |
|
||||||
|
// hsr | macsec | netdevsim }
|
||||||
|
// FLAG-LIST := [ FLAG-LIST ] FLAG
|
||||||
|
// FLAG := [ permanent | dynamic | secondary | primary |
|
||||||
|
// [-]tentative | [-]deprecated | [-]dadfailed | temporary |
|
||||||
|
// CONFFLAG-LIST ]
|
||||||
|
// CONFFLAG-LIST := [ CONFFLAG-LIST ] CONFFLAG
|
||||||
|
// CONFFLAG := [ home | nodad | mngtmpaddr | noprefixroute | autojoin ]
|
||||||
|
|
||||||
|
// prefix seems to be a cidr. both 6 and 4 works. idfk dog
|
||||||
|
|
||||||
|
use super::AddrOutput;
|
||||||
|
|
||||||
|
pub async fn get(dev: Option<&str>) -> anyhow::Result<Vec<AddrOutput>> {
|
||||||
|
let mut cmd = tokio::process::Command::new("ip");
|
||||||
|
cmd.args(["-j", "address", "show"]);
|
||||||
|
|
||||||
|
if let Some(d) = dev {
|
||||||
|
cmd.args(["dev", d]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd.output().await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
anyhow::bail!(String::from_utf8_lossy(&output.stderr).into_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(serde_json::from_slice(&output.stdout)?)
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//! ts
|
||||||
|
//!
|
||||||
|
//! deals with both ip a (addroutput) and ip l (commonoutput)
|
||||||
|
//!
|
||||||
|
//! lowk why its free but its indirection and its ass
|
||||||
|
|
||||||
|
pub mod json;
|
||||||
|
|
||||||
|
use crate::utils::serialize::mac::option_mac;
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// i dont include what i dont know about (almost all ts)
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
pub struct AddrOutput {
|
||||||
|
pub ifindex: u32,
|
||||||
|
pub ifname: String,
|
||||||
|
/// i imagine UP or DOWN, unknown
|
||||||
|
pub operstate: String,
|
||||||
|
// 6 has a serde and the enum doesnt? why. (serializing ts is ass although... im not given an array. they string formatted ts)
|
||||||
|
#[serde(with = "option_mac", default)]
|
||||||
|
pub address: Option<MacAddr>,
|
||||||
|
#[serde(default)] // i wish we have intellisense for this... fuck you metaprogramming
|
||||||
|
pub addr_info: Vec<AddrInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// i be copying
|
||||||
|
// Raw JSON shape from ip -j -4 address show
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct AddrInfo {
|
||||||
|
pub family: Option<String>,
|
||||||
|
|
||||||
|
pub local: Option<String>,
|
||||||
|
pub prefixlen: Option<u8>,
|
||||||
|
|
||||||
|
pub broadcast: Option<String>,
|
||||||
|
|
||||||
|
pub scope: Option<String>,
|
||||||
|
pub label: Option<String>,
|
||||||
|
// many more exist; we only take what we need
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod address;
|
||||||
|
pub mod neighbor;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//! idk what to put here
|
||||||
|
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use super::{NUDState, NeighborItem};
|
||||||
|
|
||||||
|
// loose translation of [wakey::utils::query::macs::get_mac]
|
||||||
|
// i think ill write tokio::process every time tho (for this if let thing) because iterate through all ts youll have to as str and all the hooplas.
|
||||||
|
// it all turns to live osstr tho so ts just for my own sanity
|
||||||
|
// thiserror? anyhow
|
||||||
|
|
||||||
|
// what is vro sayin
|
||||||
|
// ahh. instead of using [wakey::utils::cmd::exec_command] which is ass we jus write everything out. so i dont have to .as_str() so often.
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
ip: Option<IpAddr>,
|
||||||
|
dev: Option<&str>,
|
||||||
|
nud: &[NUDState],
|
||||||
|
) -> anyhow::Result<Vec<NeighborItem>> {
|
||||||
|
let mut cmd = tokio::process::Command::new("ip");
|
||||||
|
cmd.args(["-j", "neigh", "show"]);
|
||||||
|
|
||||||
|
if let Some(ip) = ip {
|
||||||
|
cmd.arg(ip.to_canonical().to_string());
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(dev) = dev {
|
||||||
|
cmd.args(["dev", dev]);
|
||||||
|
}
|
||||||
|
for nud in nud {
|
||||||
|
cmd.arg("nud");
|
||||||
|
cmd.arg(nud.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd.output().await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
bail!(String::from_utf8_lossy(&output.stderr).into_owned())
|
||||||
|
} else {
|
||||||
|
Ok(serde_json::from_slice(&output.stdout)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//! ```bash
|
||||||
|
//! ip -j n s
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! yes. this is a real call.
|
||||||
|
|
||||||
|
pub mod json;
|
||||||
|
pub mod nl;
|
||||||
|
|
||||||
|
use crate::utils::serialize::mac::option_mac;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use strum::{Display, EnumString};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct NeighborInput {
|
||||||
|
/// supports only the last item (ignore), `to` keyword is optional
|
||||||
|
pub to: Option<IpAddr>,
|
||||||
|
/// supports only one item (it complains if multiple)
|
||||||
|
pub dev: Option<String>, // im all for simplicity
|
||||||
|
/// takes multiple, has to have `nud` before bro or it will think you `to`
|
||||||
|
pub nud: Vec<NUDState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// as input this must be lowercase. as output it is uppercase
|
||||||
|
/// docs for items come from a random ahh man website idk
|
||||||
|
#[derive(
|
||||||
|
Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, Serialize, Deserialize, Default,
|
||||||
|
)]
|
||||||
|
#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
|
||||||
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
|
pub enum NUDState {
|
||||||
|
/// the neighbour entry is valid forever and can
|
||||||
|
/// be only be removed administratively.
|
||||||
|
Permanent,
|
||||||
|
/// the neighbour entry is valid. No attempts to
|
||||||
|
/// validate this entry will be made but it can
|
||||||
|
/// be removed when its lifetime expires.
|
||||||
|
Noarp,
|
||||||
|
/// the neighbour entry is valid until the
|
||||||
|
/// reachability timeout expires.
|
||||||
|
Reachable,
|
||||||
|
/// the neighbour entry is valid but suspicious.
|
||||||
|
/// This option to ip neigh does not change the
|
||||||
|
/// neighbour state if it was valid and the
|
||||||
|
/// address is not changed by this command.
|
||||||
|
Stale,
|
||||||
|
/// this is a pseudo state used when initially
|
||||||
|
/// creating a neighbour entry or after trying to
|
||||||
|
/// remove it before it becomes free to do so.
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// the neighbour entry has not (yet) been
|
||||||
|
/// validated/resolved.
|
||||||
|
Incomplete,
|
||||||
|
/// neighbor entry validation is currently
|
||||||
|
/// delayed.
|
||||||
|
Delay,
|
||||||
|
/// neighbor is being probed.
|
||||||
|
Probe,
|
||||||
|
/// max number of probes exceeded without
|
||||||
|
/// success, neighbor validation has ultimately
|
||||||
|
/// failed.
|
||||||
|
Failed,
|
||||||
|
|
||||||
|
Other(u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// everything i see
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct NeighborItem {
|
||||||
|
#[serde(rename(deserialize = "dst"))]
|
||||||
|
pub ip: IpAddr,
|
||||||
|
pub dev: String,
|
||||||
|
#[serde(with = "option_mac", default, rename(deserialize = "lladdr"))]
|
||||||
|
pub mac: Option<MacAddr>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub state: Vec<NUDState>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
//! this is purely experimental. im not doing ts no mo
|
||||||
|
|
||||||
|
// hallo
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
net::IpAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use macaddr::MacAddr;
|
||||||
|
use rtnetlink::packet_route::{
|
||||||
|
AddressFamily,
|
||||||
|
link::LinkAttribute,
|
||||||
|
neighbour::{NeighbourAddress, NeighbourAttribute, NeighbourState},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{NUDState, NeighborItem};
|
||||||
|
|
||||||
|
// dont you love https://github.com/rust-netlink/rtnetlink/blob/main/examples/get_neighbours.rs
|
||||||
|
pub async fn get(
|
||||||
|
ip: Option<IpAddr>,
|
||||||
|
dev: Option<&str>,
|
||||||
|
nud: &[NUDState],
|
||||||
|
) -> anyhow::Result<Vec<NeighborItem>> {
|
||||||
|
let (gip, gdev, gnud) = (ip, dev, nud);
|
||||||
|
let (conn, handle, _) = rtnetlink::new_connection()?;
|
||||||
|
tokio::spawn(conn); // every time?
|
||||||
|
let mut neighbor_data = handle.neighbours().get().execute();
|
||||||
|
let nudset: HashSet<&NUDState> = HashSet::from_iter(gnud);
|
||||||
|
let mut ball: HashMap<u32, String> = HashMap::new();
|
||||||
|
let mut result = vec![];
|
||||||
|
'big: while let Some(neighbour_message_item) = neighbor_data.try_next().await? {
|
||||||
|
// Filter by address family
|
||||||
|
if !matches!(
|
||||||
|
neighbour_message_item.header.family,
|
||||||
|
AddressFamily::Inet | AddressFamily::Inet6
|
||||||
|
) || matches!(neighbour_message_item.header.state, NeighbourState::Noarp)
|
||||||
|
// copilot says this to match ip -j n s
|
||||||
|
{
|
||||||
|
continue 'big;
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = vec![
|
||||||
|
neighbour_message_item
|
||||||
|
.header
|
||||||
|
.state
|
||||||
|
.try_into()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
]; // ONE ITEM. why tf ts design json.
|
||||||
|
let mut ip = None;
|
||||||
|
let mut mac = None;
|
||||||
|
|
||||||
|
for neigh_attr in neighbour_message_item.attributes {
|
||||||
|
match neigh_attr {
|
||||||
|
NeighbourAttribute::Destination(neighbour_address) => match neighbour_address {
|
||||||
|
NeighbourAddress::Inet(ipv4_addr) => ip = Some(ipv4_addr.into()),
|
||||||
|
NeighbourAddress::Inet6(ipv6_addr) => ip = Some(ipv6_addr.into()),
|
||||||
|
_ => continue 'big,
|
||||||
|
},
|
||||||
|
NeighbourAttribute::LinkLocalAddress(items) => {
|
||||||
|
mac = match items.len() {
|
||||||
|
6 => items.first_chunk::<6>().map(|&e| MacAddr::from(e)),
|
||||||
|
8 => items.first_chunk::<8>().map(|&e| MacAddr::from(e)),
|
||||||
|
_ => continue 'big,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exquisite
|
||||||
|
let dev = if let Some(cached) = ball.get(&neighbour_message_item.header.ifindex) {
|
||||||
|
Some(cached.clone())
|
||||||
|
} else {
|
||||||
|
// Query and cache
|
||||||
|
let name = handle
|
||||||
|
.link()
|
||||||
|
.get()
|
||||||
|
.match_index(neighbour_message_item.header.ifindex)
|
||||||
|
.execute()
|
||||||
|
.try_next()
|
||||||
|
.await?
|
||||||
|
.and_then(|a| {
|
||||||
|
a.attributes.into_iter().find_map(|attr| match attr {
|
||||||
|
LinkAttribute::IfName(name) => Some(name),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(ref n) = name {
|
||||||
|
ball.insert(neighbour_message_item.header.ifindex, n.clone());
|
||||||
|
}
|
||||||
|
name
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((ip, dev)) = ip.zip(dev) else {
|
||||||
|
continue 'big;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
// low block
|
||||||
|
if let Some(fip) = gip
|
||||||
|
&& fip != ip
|
||||||
|
{
|
||||||
|
continue 'big;
|
||||||
|
}
|
||||||
|
if let Some(fdev) = gdev
|
||||||
|
&& dev != fdev
|
||||||
|
{
|
||||||
|
continue 'big;
|
||||||
|
}
|
||||||
|
if !nudset.is_empty() && !nudset.contains(&state[0]) {
|
||||||
|
continue 'big;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(NeighborItem {
|
||||||
|
ip,
|
||||||
|
dev,
|
||||||
|
mac,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(result) // now i need another pass to filter out the uh.
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<NeighbourState> for NUDState {
|
||||||
|
fn try_from(value: NeighbourState) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
NeighbourState::Incomplete => Ok(Self::Incomplete),
|
||||||
|
NeighbourState::Reachable => Ok(Self::Reachable),
|
||||||
|
NeighbourState::Stale => Ok(Self::Stale),
|
||||||
|
NeighbourState::Delay => Ok(Self::Delay),
|
||||||
|
NeighbourState::Probe => Ok(Self::Probe),
|
||||||
|
NeighbourState::Failed => Ok(Self::Failed),
|
||||||
|
NeighbourState::Noarp => Ok(Self::Noarp),
|
||||||
|
NeighbourState::Permanent => Ok(Self::Permanent),
|
||||||
|
NeighbourState::None => Ok(Self::None),
|
||||||
|
NeighbourState::Other(e) => Ok(Self::Other(e)),
|
||||||
|
_ => Err(u16::MAX), // idk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error = u16;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use std::iter::Fuse;
|
||||||
|
|
||||||
|
/// wrap ts into a [Fuse][std::iter::Fuse] or something
|
||||||
|
pub struct Real<A: Clone, B: Iterator<Item = A>> {
|
||||||
|
prepend: A,
|
||||||
|
iter: B,
|
||||||
|
my_turn: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Clone, B: Iterator<Item = A>> Real<A, B> {
|
||||||
|
pub fn new(prepend: A, iter: B) -> Self {
|
||||||
|
Self {
|
||||||
|
prepend,
|
||||||
|
iter,
|
||||||
|
my_turn: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn fuse(prepend: A, iter: B) -> Fuse<Self> {
|
||||||
|
Self::new(prepend, iter).fuse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Clone, B: Iterator<Item = A>> Iterator for Real<A, B> {
|
||||||
|
type Item = A;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let a = if self.my_turn {
|
||||||
|
Some(self.prepend.clone())
|
||||||
|
} else {
|
||||||
|
self.iter.next()
|
||||||
|
};
|
||||||
|
self.my_turn = !self.my_turn;
|
||||||
|
a
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod iter;
|
||||||
|
pub mod serialize;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
use macaddr::MacAddr;
|
||||||
|
use serde::{self, Deserialize, Deserializer, de::Error as DeError};
|
||||||
|
use serde::{Serialize, Serializer, de};
|
||||||
|
|
||||||
|
pub fn serialize_macs<S>(macs: &[MacAddr], serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let strings: Vec<String> = macs.iter().map(|m| m.to_string()).collect();
|
||||||
|
serde::Serialize::serialize(&strings, serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize a MacAddr as a string
|
||||||
|
pub fn serialize<S>(mac: &MacAddr, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&mac.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialize a MacAddr from a string
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<MacAddr, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = <String as serde::Deserialize>::deserialize(deserializer)?;
|
||||||
|
s.parse::<MacAddr>().map_err(DeError::custom)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod option_mac {
|
||||||
|
use super::*;
|
||||||
|
/// serialize an [`Option<MacAddr>`]
|
||||||
|
pub fn serialize<S: Serializer>(bro: &Option<MacAddr>, ser: S) -> Result<S::Ok, S::Error> {
|
||||||
|
Option::<String>::serialize(&bro.as_ref().map(ToString::to_string), ser)
|
||||||
|
}
|
||||||
|
/// deserialize an [`Option<MacAddr>`]
|
||||||
|
pub fn deserialize<'de, D>(des: D) -> Result<Option<MacAddr>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Option::<&str>::deserialize(des)?
|
||||||
|
.map(str::parse)
|
||||||
|
.transpose()
|
||||||
|
.map_err(de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod mac;
|
||||||
|
pub mod vec;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use serde::de;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum OneOrMany<T> {
|
||||||
|
One(T),
|
||||||
|
Many(Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec_from_strs<'de, D, T>(des: D) -> Result<Vec<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
T: std::str::FromStr,
|
||||||
|
T::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let raw: OneOrMany<String> = OneOrMany::<String>::deserialize(des)?;
|
||||||
|
let mut out = Vec::new();
|
||||||
|
match raw {
|
||||||
|
OneOrMany::One(s) => {
|
||||||
|
let t = s.trim();
|
||||||
|
if !t.is_empty() {
|
||||||
|
out.push(t.parse().map_err(de::Error::custom)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OneOrMany::Many(vs) => {
|
||||||
|
for s in vs {
|
||||||
|
let t = s.trim();
|
||||||
|
if t.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.push(t.parse().map_err(de::Error::custom)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use lda_ipjs::subcommands::neighbor;
|
||||||
|
|
||||||
|
#[tokio::test] // ← Use tokio::test instead of manual #[tokio::main]
|
||||||
|
async fn ball1() -> anyhow::Result<()> {
|
||||||
|
let result = neighbor::nl::get(None, None, &[]).await?;
|
||||||
|
println!("netlink results: {:?}", result);
|
||||||
|
Ok(()) // ← Don't force error, let it succeed
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball2() -> anyhow::Result<()> {
|
||||||
|
let result = neighbor::json::get(None, None, &[]).await?;
|
||||||
|
println!("json results: {:?}", result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this to debug the raw JSON
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball_raw_json() -> anyhow::Result<()> {
|
||||||
|
let output = tokio::process::Command::new("ip")
|
||||||
|
.args(["-j", "neigh", "show"])
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let json = String::from_utf8_lossy(&output.stdout);
|
||||||
|
println!("Raw JSON:\n{}", json);
|
||||||
|
|
||||||
|
// Try to parse it
|
||||||
|
let parsed: Result<Vec<neighbor::NeighborItem>, _> = serde_json::from_slice(&output.stdout);
|
||||||
|
match parsed {
|
||||||
|
Ok(items) => println!("Parsed {} items", items.len()),
|
||||||
|
Err(e) => println!("Parse error: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check what fields actually exist in the JSON
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball_field_analysis() -> anyhow::Result<()> {
|
||||||
|
let output = tokio::process::Command::new("ip")
|
||||||
|
.args(["-j", "neigh", "show"])
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let raw: Vec<serde_json::Value> = serde_json::from_slice(&output.stdout)?;
|
||||||
|
|
||||||
|
println!("Found {} neighbor entries", raw.len());
|
||||||
|
|
||||||
|
// Collect all unique field names across all entries
|
||||||
|
let mut all_fields = std::collections::HashSet::new();
|
||||||
|
for (i, entry) in raw.iter().enumerate() {
|
||||||
|
if let Some(obj) = entry.as_object() {
|
||||||
|
println!("\nEntry {}: {} fields", i, obj.len());
|
||||||
|
for (key, value) in obj {
|
||||||
|
all_fields.insert(key.clone());
|
||||||
|
println!(" {}: {} = {:?}", key, value.type_name(), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n=== All unique fields seen ===");
|
||||||
|
for field in &all_fields {
|
||||||
|
println!(" - {}", field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper trait to get type name for JSON values
|
||||||
|
trait TypeName {
|
||||||
|
fn type_name(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeName for serde_json::Value {
|
||||||
|
fn type_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
serde_json::Value::Null => "null",
|
||||||
|
serde_json::Value::Bool(_) => "bool",
|
||||||
|
serde_json::Value::Number(_) => "number",
|
||||||
|
serde_json::Value::String(_) => "string",
|
||||||
|
serde_json::Value::Array(_) => "array",
|
||||||
|
serde_json::Value::Object(_) => "object",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test filtering logic
|
||||||
|
#[tokio::test]
|
||||||
|
async fn ball_compare_backends() -> anyhow::Result<()> {
|
||||||
|
println!("=== JSON Backend ===");
|
||||||
|
let json_result = neighbor::json::get(None, None, &[]).await?;
|
||||||
|
println!("Got {} entries from JSON", json_result.len());
|
||||||
|
|
||||||
|
println!("\n=== Netlink Backend ===");
|
||||||
|
let nl_result = neighbor::nl::get(None, None, &[]).await?;
|
||||||
|
println!("Got {} entries from netlink", nl_result.len());
|
||||||
|
|
||||||
|
// Compare counts
|
||||||
|
if json_result.len() != nl_result.len() {
|
||||||
|
println!(
|
||||||
|
"\n⚠️ Count mismatch! JSON: {}, Netlink: {}",
|
||||||
|
json_result.len(),
|
||||||
|
nl_result.len()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("\n✅ Both backends returned same count");
|
||||||
|
}
|
||||||
|
let a: HashSet<neighbor::NeighborItem> = HashSet::from_iter(json_result);
|
||||||
|
let b: HashSet<neighbor::NeighborItem> = HashSet::from_iter(nl_result);
|
||||||
|
println!(
|
||||||
|
"istg {len1} == {len2} or else",
|
||||||
|
len1 = a.len(),
|
||||||
|
len2 = b.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cidr_filter() {
|
||||||
|
unimplemented!("never. i aint add what i dont need")
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# taking back my code AHHHH
|
||||||
|
|
||||||
|
this is going to be lengthy. ci is spotless but idk how it works cuz powershell. this is good but i cant afford VPN on the router; we will need to do some bullshit.
|
||||||
|
|
||||||
|
## overview
|
||||||
|
|
||||||
|
we need a:
|
||||||
|
|
||||||
|
- small ahh bin in the router.
|
||||||
|
- forwards a shell and some functions.
|
||||||
|
- may be shell calling fns maybe some unsafe c. i cant unsafe c.
|
||||||
|
- big ahh bin in the vps.
|
||||||
|
- connect to the small ahh bin thru the wide ass internet.
|
||||||
|
- forwards some APIs.
|
||||||
|
- static site
|
||||||
|
- now need no hosts; apis can be called anywhere anyway.
|
||||||
|
- do offer these in the big ahh bins tho lets not miss out some shit.
|
||||||
|
- automation
|
||||||
|
- gitea
|
||||||
|
- on the vps or the uh idk.
|
||||||
|
- ABSOLUTELY can auto build main:latest every day / other day.
|
||||||
|
- and releases.
|
||||||
|
- router
|
||||||
|
- a script to pull the tar. from actions artifacts or from releases. (lua or shell yo)
|
||||||
|
- a script for the bin and the init.d scripts for these two.
|
||||||
|
- vps
|
||||||
|
- a systemd service (ima use ai for ts ngl)
|
||||||
|
- an auto updater. maybe we overkill and dnf. maybe go easy and git clone + cargo install. maybe we pull from release or nightly. idk
|
||||||
|
|
||||||
|
i think idk
|
||||||
|
|
||||||
|
## router
|
||||||
|
|
||||||
|
### current
|
||||||
|
|
||||||
|
everything.
|
||||||
|
|
||||||
|
we have some functions that call ip neigh a bajillion times.
|
||||||
|
we have some other functions that read the same file/folder in /sys or sum shit.
|
||||||
|
we have a function that broadcast to a set ahh address.
|
||||||
|
|
||||||
|
we have some routes; and some data structures acting as schema for them ahh. we dont do posts with json but gets with query strings for some ahh reasons.
|
||||||
|
|
||||||
|
these are carryover from the early days.
|
||||||
|
|
||||||
|
also from the early days are the js.
|
||||||
|
|
||||||
|
static was 100% ai with 0 intervention.
|
||||||
|
|
||||||
|
we have a search bar of sort that hooks through a smart endpoint that searches for everything.
|
||||||
|
|
||||||
|
we have some bullshit. and two buttons to check and wake. that pings `/api/smart/{query}` and `/wake?name=query`. why.
|
||||||
|
we have the result of the wake and or status and the errors in the first text box.
|
||||||
|
|
||||||
|
the list of cliets with names connected to the router the second box.
|
||||||
|
thats cool.
|
||||||
|
|
||||||
|
### changes?
|
||||||
|
|
||||||
|
idk changes.
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
START=50 # Run before Tailscale (typically START~80)
|
START=50 # Run before Tailscale (typically START~80)
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
LOGFILE=/var/log/update_tailscale.log
|
LOGFILE=/var/log/update_tailscale.log
|
||||||
MAX_RETRIES=10
|
MAX_RETRIES=10
|
||||||
RETRY_COUNT=0
|
RETRY_COUNT=0
|
||||||
|
|
||||||
# shellcheck disable=SC2016
|
# shellcheck disable=SC2016
|
||||||
sh -c '
|
sh -c '
|
||||||
while [ "$RETRY_COUNT" -lt "$MAX_RETRIES" ]; do
|
while [ "$RETRY_COUNT" -lt "$MAX_RETRIES" ]; do
|
||||||
if fn; then
|
if fn; then
|
||||||
echo "[update_tailscale] Success at $(date)" >>"$LOGFILE"
|
echo "[update_tailscale] Success at $(date)" >>"$LOGFILE"
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ NAME=wakey
|
|||||||
BIN=/root/.bin/wakey
|
BIN=/root/.bin/wakey
|
||||||
|
|
||||||
start_service() {
|
start_service() {
|
||||||
procd_open_instance
|
procd_open_instance
|
||||||
procd_set_param command "$BIN"
|
procd_set_param command "$BIN"
|
||||||
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
|
||||||
procd_close_instance
|
procd_close_instance
|
||||||
}
|
}
|
||||||
|
|
||||||
stop_service() {
|
stop_service() {
|
||||||
: # procd manages the process; nothing to do here
|
: # procd manages the process; nothing to do here
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ pids="$(pidof wakey 2>/dev/null)"
|
|||||||
|
|
||||||
# 2) Fallback: ps | awk (avoid matching awk/grep themselves)
|
# 2) Fallback: ps | awk (avoid matching awk/grep themselves)
|
||||||
if [ -z "$pids" ]; then
|
if [ -z "$pids" ]; then
|
||||||
pids="$(ps w 2>/dev/null | awk '/\/\.bin\/wakey/ && $0 !~ /awk/ {print $1}')"
|
pids="$(ps w 2>/dev/null | awk '/\/\.bin\/wakey/ && $0 !~ /awk/ {print $1}')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -n "$pids" ] || exit 0
|
[ -n "$pids" ] || exit 0
|
||||||
@@ -18,7 +18,7 @@ kill -TERM "$pids" 2>/dev/null || true
|
|||||||
usleep 250000 # uhhh sleep is stupid
|
usleep 250000 # uhhh sleep is stupid
|
||||||
remain=""
|
remain=""
|
||||||
for p in $pids; do
|
for p in $pids; do
|
||||||
kill -0 "$p" 2>/dev/null && remain="$remain $p"
|
kill -0 "$p" 2>/dev/null && remain="$remain $p"
|
||||||
done
|
done
|
||||||
[ -z "$remain" ] || kill -KILL "$remain" 2>/dev/null || true
|
[ -z "$remain" ] || kill -KILL "$remain" 2>/dev/null || true
|
||||||
|
|
||||||
|
|||||||
@@ -3,22 +3,22 @@
|
|||||||
set -e
|
set -e
|
||||||
# 0th step from lda is to move the pre assed resolv.conf to the real resolv.conf
|
# 0th step from lda is to move the pre assed resolv.conf to the real resolv.conf
|
||||||
if [ -f /tmp/resolv.conf ]; then
|
if [ -f /tmp/resolv.conf ]; then
|
||||||
cp -af /tmp/resolv.conf /etc/resolv.conf
|
cp -af /tmp/resolv.conf /etc/resolv.conf
|
||||||
elif [ -f /rom/etc/resolv.conf ]; then
|
elif [ -f /rom/etc/resolv.conf ]; then
|
||||||
cp -af /rom/etc/resolv.conf /etc/resolv.conf
|
cp -af /rom/etc/resolv.conf /etc/resolv.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -x "$(which tailscale)" ]; then
|
if [ -x "$(which tailscale)" ]; then
|
||||||
tailscale update
|
tailscale update
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 1. Fetch the latest version number from Tailscale's site
|
# 1. Fetch the latest version number from Tailscale's site
|
||||||
LATEST=$(curl -s https://pkgs.tailscale.com/stable/ | grep -Eo 'tailscale_[0-9\.]+_arm.tgz' | head -n1)
|
LATEST=$(curl -s https://pkgs.tailscale.com/stable/ | grep -Eo 'tailscale_[0-9\.]+_arm.tgz' | head -n1)
|
||||||
|
|
||||||
[ -z "$LATEST" ] && {
|
[ -z "$LATEST" ] && {
|
||||||
echo "❌ Couldn't find latest ARM binary."
|
echo "❌ Couldn't find latest ARM binary."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
URL="https://pkgs.tailscale.com/stable/${LATEST}"
|
URL="https://pkgs.tailscale.com/stable/${LATEST}"
|
||||||
@@ -26,8 +26,8 @@ URL="https://pkgs.tailscale.com/stable/${LATEST}"
|
|||||||
cd /var/tmp
|
cd /var/tmp
|
||||||
|
|
||||||
if [ -f "$LATEST" ]; then
|
if [ -f "$LATEST" ]; then
|
||||||
echo "file exists"
|
echo "file exists"
|
||||||
rm "$LATEST"
|
rm "$LATEST"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Downloading $LATEST..."
|
echo "Downloading $LATEST..."
|
||||||
|
|||||||
@@ -24,113 +24,113 @@ STAGING="$TMPDIR/wakey-rootfs.$$.$ARCH"
|
|||||||
|
|
||||||
log() { echo "[update_wakey] $*"; }
|
log() { echo "[update_wakey] $*"; }
|
||||||
fail() {
|
fail() {
|
||||||
echo "[update_wakey] ERROR: $*" >&2
|
echo "[update_wakey] ERROR: $*" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
http_get() {
|
http_get() {
|
||||||
# http_get <url>
|
# http_get <url>
|
||||||
if command -v uclient-fetch >/dev/null 2>&1; then
|
if command -v uclient-fetch >/dev/null 2>&1; then
|
||||||
# uclient-fetch supports -O -, write to stdout
|
# uclient-fetch supports -O -, write to stdout
|
||||||
uclient-fetch -O - "$1" 2>/dev/null || return 1
|
uclient-fetch -O - "$1" 2>/dev/null || return 1
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
wget ${WAKEY_INSECURE:+--no-check-certificate} -qO- "$1" || return 1
|
wget ${WAKEY_INSECURE:+--no-check-certificate} -qO- "$1" || return 1
|
||||||
elif command -v curl >/dev/null 2>&1; then
|
elif command -v curl >/dev/null 2>&1; then
|
||||||
if [ -n "${WAKEY_INSECURE:-}" ]; then
|
if [ -n "${WAKEY_INSECURE:-}" ]; then
|
||||||
curl -fsSL -k "$1" || return 1
|
curl -fsSL -k "$1" || return 1
|
||||||
else
|
else
|
||||||
curl -fsSL "$1" || return 1
|
curl -fsSL "$1" || return 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch() {
|
fetch() {
|
||||||
# fetch <url> <out>
|
# fetch <url> <out>
|
||||||
if command -v uclient-fetch >/dev/null 2>&1; then
|
if command -v uclient-fetch >/dev/null 2>&1; then
|
||||||
uclient-fetch -O "$2" "$1" || return 1
|
uclient-fetch -O "$2" "$1" || return 1
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
wget ${WAKEY_INSECURE:+--no-check-certificate} -O "$2" "$1" || return 1
|
wget ${WAKEY_INSECURE:+--no-check-certificate} -O "$2" "$1" || return 1
|
||||||
elif command -v curl >/dev/null 2>&1; then
|
elif command -v curl >/dev/null 2>&1; then
|
||||||
if [ -n "${WAKEY_INSECURE:-}" ]; then
|
if [ -n "${WAKEY_INSECURE:-}" ]; then
|
||||||
curl -fSL -k -o "$2" "$1" || return 1
|
curl -fSL -k -o "$2" "$1" || return 1
|
||||||
else
|
else
|
||||||
curl -fSL -o "$2" "$1" || return 1
|
curl -fSL -o "$2" "$1" || return 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
latest_asset_url() {
|
latest_asset_url() {
|
||||||
# prints URL to stdout, returns 0 on success (public release only)
|
# prints URL to stdout, returns 0 on success (public release only)
|
||||||
HOST=$1 OWNER=$2 REPO=$3 ARCH=$4
|
HOST=$1 OWNER=$2 REPO=$3 ARCH=$4
|
||||||
API="https://$HOST/api/v1/repos/$OWNER/$REPO/releases/latest"
|
API="https://$HOST/api/v1/repos/$OWNER/$REPO/releases/latest"
|
||||||
RESP=$(http_get "$API") || return 1
|
RESP=$(http_get "$API") || return 1
|
||||||
# Extract the first browser_download_url ending with our ARCH .tgz (BusyBox-friendly)
|
# Extract the first browser_download_url ending with our ARCH .tgz (BusyBox-friendly)
|
||||||
echo "$RESP" |
|
echo "$RESP" |
|
||||||
grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*'"$ARCH"'\.tgz"' |
|
grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*'"$ARCH"'\.tgz"' |
|
||||||
head -n1 |
|
head -n1 |
|
||||||
cut -d '"' -f 4
|
cut -d '"' -f 4
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
[ -f "$FLAG" ] && return
|
[ -f "$FLAG" ] && return
|
||||||
URL="${WAKEY_TGZ_URL:-}"
|
URL="${WAKEY_TGZ_URL:-}"
|
||||||
if [ -z "$URL" ]; then
|
if [ -z "$URL" ]; then
|
||||||
HOST=${WAKEY_HOST:-git.ldlda.com}
|
HOST=${WAKEY_HOST:-git.ldlda.com}
|
||||||
OWNER=${WAKEY_OWNER:-lda}
|
OWNER=${WAKEY_OWNER:-lda}
|
||||||
REPO=${WAKEY_REPO:-wakey}
|
REPO=${WAKEY_REPO:-wakey}
|
||||||
if [ -n "${WAKEY_VERSION:-}" ]; then
|
if [ -n "${WAKEY_VERSION:-}" ]; then
|
||||||
FILE="wakey-rootfs-${WAKEY_VERSION}-${ARCH}.tgz"
|
FILE="wakey-rootfs-${WAKEY_VERSION}-${ARCH}.tgz"
|
||||||
URL="https://$HOST/$OWNER/$REPO/releases/download/${WAKEY_VERSION}/$FILE"
|
URL="https://$HOST/$OWNER/$REPO/releases/download/${WAKEY_VERSION}/$FILE"
|
||||||
else
|
else
|
||||||
URL=$(latest_asset_url "$HOST" "$OWNER" "$REPO" "$ARCH") || fail "unable to resolve latest asset URL"
|
URL=$(latest_asset_url "$HOST" "$OWNER" "$REPO" "$ARCH") || fail "unable to resolve latest asset URL"
|
||||||
[ -n "$URL" ] || fail "no asset URL found for arch $ARCH"
|
[ -n "$URL" ] || fail "no asset URL found for arch $ARCH"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "fetching $URL"
|
log "fetching $URL"
|
||||||
if ! fetch "$URL" "$TMPFILE"; then
|
if ! fetch "$URL" "$TMPFILE"; then
|
||||||
fail "download failed"
|
fail "download failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "installing"
|
log "installing"
|
||||||
mkdir -p "$TMPDIR" "$STAGING"
|
mkdir -p "$TMPDIR" "$STAGING"
|
||||||
tar -xz -f "$TMPFILE" -C "$STAGING" || fail "extract failed"
|
tar -xz -f "$TMPFILE" -C "$STAGING" || fail "extract failed"
|
||||||
|
|
||||||
# Ensure execute bits on staged files we know should be executable
|
# Ensure execute bits on staged files we know should be executable
|
||||||
for f in \
|
for f in \
|
||||||
"$STAGING/etc/init.d/"* \
|
"$STAGING/etc/init.d/"* \
|
||||||
"$STAGING/etc/ldlda_help/"*.sh \
|
"$STAGING/etc/ldlda_help/"*.sh \
|
||||||
"$STAGING/root/.bin/wakey" \
|
"$STAGING/root/.bin/wakey" \
|
||||||
"$STAGING/root/.bin/kill_wakey.sh" \
|
"$STAGING/root/.bin/kill_wakey.sh" \
|
||||||
"$STAGING/root/.bin/remote_deploy_wakey.sh"; do
|
"$STAGING/root/.bin/remote_deploy_wakey.sh"; do
|
||||||
[ -e "$f" ] && chmod +x "$f" 2>/dev/null || true
|
[ -e "$f" ] && chmod +x "$f" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Normalize line endings for shell scripts (avoid CRLF issues on OpenWrt)
|
# Normalize line endings for shell scripts (avoid CRLF issues on OpenWrt)
|
||||||
for f in \
|
for f in \
|
||||||
"$STAGING/etc/init.d/"* \
|
"$STAGING/etc/init.d/"* \
|
||||||
"$STAGING/etc/ldlda_help/"*.sh \
|
"$STAGING/etc/ldlda_help/"*.sh \
|
||||||
"$STAGING/root/.bin/"*.sh; do
|
"$STAGING/root/.bin/"*.sh; do
|
||||||
[ -f "$f" ] && sed -i 's/\r$//' "$f" 2>/dev/null || true
|
[ -f "$f" ] && sed -i 's/\r$//' "$f" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
|
|
||||||
# Copy staged tree into /
|
# Copy staged tree into /
|
||||||
tar -C "$STAGING" -cf - . | tar -C / -xpf - || fail "install copy failed"
|
tar -C "$STAGING" -cf - . | tar -C / -xpf - || fail "install copy failed"
|
||||||
|
|
||||||
rm -f "$TMPFILE"
|
rm -f "$TMPFILE"
|
||||||
rm -rf "$STAGING"
|
rm -rf "$STAGING"
|
||||||
|
|
||||||
if [ -f /etc/init.d/wakey ]; then
|
if [ -f /etc/init.d/wakey ]; then
|
||||||
/etc/init.d/wakey enable || true
|
/etc/init.d/wakey enable || true
|
||||||
/etc/init.d/wakey restart || /etc/init.d/wakey start || true
|
/etc/init.d/wakey restart || /etc/init.d/wakey start || true
|
||||||
fi
|
fi
|
||||||
touch "$FLAG"
|
touch "$FLAG"
|
||||||
log "done"
|
log "done"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
+246
-31
@@ -1,11 +1,21 @@
|
|||||||
"thanks chatgpt"
|
"thanks chatgpt"
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from functools import wraps
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from pprint import pprint
|
||||||
|
from typing import Callable, TypedDict, overload
|
||||||
|
|
||||||
|
|
||||||
root = Path(__file__).parent.parent
|
root = Path(__file__).parent.parent
|
||||||
static = root / "static" # change "assets" to your folder
|
static = root / "static"
|
||||||
out_rs = root / "src" / "assets.rs"
|
src = root / "src"
|
||||||
|
out_rs = src / "assets.rs"
|
||||||
|
|
||||||
|
assert 'name = "wakey"' in (root / "Cargo.toml").read_text(encoding="utf-8"), (
|
||||||
|
"uhh how do i explain this"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def sanitize(name: str) -> str:
|
def sanitize(name: str) -> str:
|
||||||
@@ -27,39 +37,244 @@ def indent(text: str, n: int) -> str:
|
|||||||
return "\n".join(pad + line if line.strip() else line for line in text.splitlines())
|
return "\n".join(pad + line if line.strip() else line for line in text.splitlines())
|
||||||
|
|
||||||
|
|
||||||
class RsAssetFile:
|
class RsAsset(ABC):
|
||||||
def __init__(self, path: Path):
|
path: Path
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def plain(self) -> str: ...
|
||||||
|
@abstractmethod
|
||||||
|
def macroed(self) -> str: ...
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetFile(RsAsset):
|
||||||
|
def __init__(self, path: Path, name: str | None = None):
|
||||||
self.path = path
|
self.path = path
|
||||||
|
if name is None:
|
||||||
|
self.name = sanitize(self.path.name).upper()
|
||||||
|
else:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
def __str__(self):
|
def _apply_template(self, template):
|
||||||
const_name = sanitize(self.path.name).upper()
|
return template.format(
|
||||||
return (
|
const_name=self.name,
|
||||||
f"pub const {const_name}: &str = "
|
relative_path=self.path.relative_to(src, walk_up=True).as_posix(),
|
||||||
f'include_str!("{self.path.relative_to(out_rs.parent, walk_up=True).as_posix()}");'
|
|
||||||
)
|
|
||||||
# specify walk_up to have .. in yo path
|
|
||||||
|
|
||||||
|
|
||||||
class RsAssetModule:
|
|
||||||
def __init__(self, folder: Path, body_only: bool = False):
|
|
||||||
self.folder = folder
|
|
||||||
self.full = not body_only
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
files = [str(RsAssetFile(s)) for s in self.folder.iterdir() if s.is_file()]
|
|
||||||
subs = [str(RsAssetModule(s)) for s in self.folder.iterdir() if s.is_dir()]
|
|
||||||
|
|
||||||
body = "\n".join(files + subs)
|
|
||||||
return (
|
|
||||||
f"pub mod {sanitize(self.folder.name).lower()} {{" * self.full
|
|
||||||
+ f"\n{indent(body, 4*self.full)}\n"
|
|
||||||
+ self.full * "}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plain_template = 'pub const {const_name}: &str = include_str!("{relative_path}");'
|
||||||
|
|
||||||
|
macroed_template = 'file {const_name} "{relative_path}"'
|
||||||
|
|
||||||
|
def plain(self):
|
||||||
|
return self._apply_template(self.plain_template)
|
||||||
|
|
||||||
|
def macroed(self):
|
||||||
|
return self._apply_template(self.macroed_template)
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetModule(RsAsset):
|
||||||
|
def __init__(self, path: Path, name: str | None = None):
|
||||||
|
self.path = path
|
||||||
|
if name is None:
|
||||||
|
self.name = sanitize(self.path.name).lower()
|
||||||
|
else:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
plain_template = "pub mod {sanitized_name} {{\n{indented_body}\n}}"
|
||||||
|
|
||||||
|
macroed_template = "folder {sanitized_name} {{\n{indented_body}\n}}"
|
||||||
|
|
||||||
|
def plain(self):
|
||||||
|
return self._apply_template(self.plain_template, lambda it: it.plain())
|
||||||
|
|
||||||
|
def macroed(self):
|
||||||
|
return self._apply_template(self.macroed_template, lambda it: it.macroed())
|
||||||
|
|
||||||
|
def _apply_template(self, template: str, renderer: Callable[[RsAsset], str]):
|
||||||
|
body = self._process_body(self.path, renderer)
|
||||||
|
return template.format(
|
||||||
|
sanitized_name=self.name,
|
||||||
|
indented_body=indent(body, 4),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_body(path: Path, renderer: Callable[[RsAsset], str]):
|
||||||
|
body = RsAssetModule.iterate_assets(path)
|
||||||
|
return "\n".join(map(renderer, body))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def iterate_assets(path: Path) -> "Iterable[RsAssetFile | RsAssetModule]":
|
||||||
|
subs = []
|
||||||
|
for f in path.iterdir():
|
||||||
|
if f.is_file():
|
||||||
|
yield RsAssetFile(f)
|
||||||
|
elif f.is_dir():
|
||||||
|
subs.append(RsAssetModule(f))
|
||||||
|
yield from subs
|
||||||
|
|
||||||
|
|
||||||
|
# i meant plain
|
||||||
|
def render_pain(ass: RsAsset) -> str:
|
||||||
|
"""
|
||||||
|
fym i have to deal with all RsAsset subclasses
|
||||||
|
|
||||||
|
whats the point then. unless there is a `Intermediate Representation` whats the point then.
|
||||||
|
"""
|
||||||
|
match ass:
|
||||||
|
case RsAssetRoot() as r:
|
||||||
|
return r.plain()
|
||||||
|
case RsAssetModule() as folder:
|
||||||
|
return folder.plain()
|
||||||
|
case RsAssetFile() as file:
|
||||||
|
return file.plain()
|
||||||
|
case _:
|
||||||
|
raise TypeError("who are you?")
|
||||||
|
|
||||||
|
|
||||||
|
def render_macro(ass: RsAsset) -> str:
|
||||||
|
"""
|
||||||
|
ts is ridiculous
|
||||||
|
"""
|
||||||
|
match ass:
|
||||||
|
case RsAssetRoot() as r:
|
||||||
|
return r.macroed()
|
||||||
|
case RsAssetModule() as folder:
|
||||||
|
return folder.macroed()
|
||||||
|
case RsAssetFile() as file:
|
||||||
|
return file.macroed()
|
||||||
|
case _:
|
||||||
|
raise TypeError("who are you?")
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetModule2(TypedDict):
|
||||||
|
name: str
|
||||||
|
path: Path
|
||||||
|
children: "list[RsAssetModule2 | RsAssetFile2]"
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetFile2(TypedDict):
|
||||||
|
name: str
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def dictionary(im: "RsAssetRoot") -> RsAssetModule2: ...
|
||||||
|
@overload
|
||||||
|
def dictionary(im: RsAssetModule) -> RsAssetModule2: ...
|
||||||
|
@overload
|
||||||
|
def dictionary(im: RsAssetFile) -> RsAssetFile2: ...
|
||||||
|
@overload
|
||||||
|
def dictionary(im: RsAssetFile | RsAssetModule) -> RsAssetFile2 | RsAssetModule2: ...
|
||||||
|
|
||||||
|
|
||||||
|
def dictionary(im: RsAsset) -> RsAssetModule2 | RsAssetFile2:
|
||||||
|
match im:
|
||||||
|
case RsAssetRoot() as r:
|
||||||
|
return RsAssetModule2(
|
||||||
|
name="ROOT",
|
||||||
|
path=r.path,
|
||||||
|
children=list(map(dictionary, RsAssetModule.iterate_assets(r.path))),
|
||||||
|
)
|
||||||
|
case RsAssetModule() as folder:
|
||||||
|
return RsAssetModule2(
|
||||||
|
name=sanitize(folder.path.name).lower(),
|
||||||
|
path=folder.path,
|
||||||
|
children=list(
|
||||||
|
map(dictionary, RsAssetModule.iterate_assets(folder.path))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case RsAssetFile() as file:
|
||||||
|
return RsAssetFile2(
|
||||||
|
name=sanitize(file.path.name).upper(),
|
||||||
|
path=file.path,
|
||||||
|
)
|
||||||
|
case _:
|
||||||
|
raise TypeError("who are you?")
|
||||||
|
|
||||||
|
|
||||||
|
lda_macro = """
|
||||||
|
macro_rules! hehe {
|
||||||
|
// Folder
|
||||||
|
(folder $name:ident { $($children:tt)* } $($rest:tt)*) => {
|
||||||
|
pub mod $name {
|
||||||
|
hehe!{$($children)*}
|
||||||
|
}
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// File
|
||||||
|
(file $name:ident $file:literal $($rest:tt)*) => {
|
||||||
|
pub const $name: &str = include_str!($file);
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// Base case
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
header = "// generated with ./scripts/map_static.py"
|
||||||
|
|
||||||
|
|
||||||
|
def announce_yourself(*pa, **pkw):
|
||||||
|
def wpr[**P, R](f: Callable[P, R]) -> Callable[P, R]:
|
||||||
|
@wraps(f)
|
||||||
|
def wpd(*a: P.args, **k: P.kwargs) -> R:
|
||||||
|
print(*pa, **pkw)
|
||||||
|
return f(*a, **k)
|
||||||
|
|
||||||
|
return wpd
|
||||||
|
|
||||||
|
return wpr
|
||||||
|
|
||||||
|
|
||||||
|
class RsAssetRoot(RsAsset):
|
||||||
|
plain_template = """{header}
|
||||||
|
|
||||||
|
{body}
|
||||||
|
"""
|
||||||
|
macroed_template = """{header}
|
||||||
|
{lda_macro}
|
||||||
|
hehe! {{
|
||||||
|
{indented_body}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, asset_root: Path) -> None:
|
||||||
|
self.path = asset_root
|
||||||
|
self._w = RsAssetModule(asset_root)
|
||||||
|
|
||||||
|
def plain(self) -> str:
|
||||||
|
body = RsAssetModule._process_body(self.path, lambda it: it.plain())
|
||||||
|
return self.plain_template.format(
|
||||||
|
header=header,
|
||||||
|
body=body,
|
||||||
|
)
|
||||||
|
|
||||||
|
def macroed(self) -> str:
|
||||||
|
body = RsAssetModule._process_body(self.path, lambda it: it.macroed())
|
||||||
|
return self.macroed_template.format(
|
||||||
|
header=header,
|
||||||
|
lda_macro=lda_macro,
|
||||||
|
indented_body=indent(body, 4),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
asset_root = RsAssetRoot(static)
|
||||||
|
|
||||||
|
pprint(dictionary(asset_root))
|
||||||
|
|
||||||
|
|
||||||
|
def render_ir(node: RsAssetModule2 | RsAssetFile2) -> str:
|
||||||
|
"gemini 3 pro"
|
||||||
|
if "children" in node:
|
||||||
|
body = "\n".join(map(render_ir, node["children"])) # type: ignore[typeddict-item]
|
||||||
|
if node["name"] == "ROOT":
|
||||||
|
return f"{header}\n\n{body}\n"
|
||||||
|
return f"pub mod {node['name']} {{\n{indent(body, 4)}\n}}"
|
||||||
|
|
||||||
|
rel = node["path"].relative_to(src, walk_up=True).as_posix()
|
||||||
|
return f'pub const {node["name"]}: &str = include_str!("{rel}");'
|
||||||
|
|
||||||
|
|
||||||
|
rs_code = render_macro(asset_root)
|
||||||
|
|
||||||
# generate
|
# generate
|
||||||
rs_code = "// generated with ./scripts/map_static.py\n" + str(
|
|
||||||
RsAssetModule(static, True)
|
|
||||||
)
|
|
||||||
out_rs.write_text(rs_code, encoding="utf-8")
|
out_rs.write_text(rs_code, encoding="utf-8")
|
||||||
print(f"written to {out_rs}")
|
print(f"written to {out_rs.relative_to(Path.cwd(), walk_up=True)}")
|
||||||
@@ -10,32 +10,32 @@ INIT="/etc/init.d/wakey"
|
|||||||
KILL="/root/.bin/kill_wakey.sh"
|
KILL="/root/.bin/kill_wakey.sh"
|
||||||
|
|
||||||
if [ -z "$BIN_TMP" ] || [ -z "$DEST" ]; then
|
if [ -z "$BIN_TMP" ] || [ -z "$DEST" ]; then
|
||||||
echo "usage: $0 <bin_tmp> <dest_path> [restart_flag]" >&2
|
echo "usage: $0 <bin_tmp> <dest_path> [restart_flag]" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -f "$BIN_TMP" ] || {
|
[ -f "$BIN_TMP" ] || {
|
||||||
echo "tmp binary not found: $BIN_TMP" >&2
|
echo "tmp binary not found: $BIN_TMP" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
chmod +x "$BIN_TMP" || true
|
chmod +x "$BIN_TMP" || true
|
||||||
|
|
||||||
if [ "$RESTART" = "1" ]; then
|
if [ "$RESTART" = "1" ]; then
|
||||||
if [ -x "$INIT" ]; then
|
if [ -x "$INIT" ]; then
|
||||||
"$INIT" stop || true
|
"$INIT" stop || true
|
||||||
elif [ -x "$KILL" ]; then
|
elif [ -x "$KILL" ]; then
|
||||||
sh "$KILL" || true
|
sh "$KILL" || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mv -f "$BIN_TMP" "$DEST"
|
mv -f "$BIN_TMP" "$DEST"
|
||||||
|
|
||||||
if [ "$RESTART" = "1" ]; then
|
if [ "$RESTART" = "1" ]; then
|
||||||
if [ -x "$INIT" ]; then
|
if [ -x "$INIT" ]; then
|
||||||
"$INIT" start || "$INIT" restart || true
|
"$INIT" start || "$INIT" restart || true
|
||||||
else
|
else
|
||||||
nohup "$DEST" >/dev/null 2>&1 &
|
nohup "$DEST" >/dev/null 2>&1 &
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
# Test ipjs on remote ARM device
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$Package = "lda-ipjs",
|
||||||
|
[string]$TestName = "",
|
||||||
|
[ValidateSet("debug", "release")]
|
||||||
|
[string]$BuildProfile = "debug",
|
||||||
|
[string]$password,
|
||||||
|
[switch]$Verbose,
|
||||||
|
[string]$RemoteTestPath = "/root/.bin/test",
|
||||||
|
[string]$RemoteHost = "[email protected]"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
# Build tests and capture output
|
||||||
|
Write-Host "Building tests for $Package..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Stream cargo output anc convert to text
|
||||||
|
$cargoOutput = cargo test --no-run -p $Package --target armv7-unknown-linux-musleabihf $(if ($BuildProfile -eq "release") { "-r" }) 2>&1 |
|
||||||
|
ForEach-Object {
|
||||||
|
$line = if ($_ -is [System.Management.Automation.ErrorRecord]) { $_.ToString() } else { $_ }
|
||||||
|
if ($Verbose) {
|
||||||
|
Write-Host $line
|
||||||
|
}
|
||||||
|
$line # Pass through to capture
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse test binary paths from cargo output
|
||||||
|
$testBinaries = $cargoOutput |
|
||||||
|
Select-String -Pattern "Executable.*\((.+)\)" |
|
||||||
|
ForEach-Object { $_.Matches.Groups[1].Value } |
|
||||||
|
Get-Item
|
||||||
|
|
||||||
|
if ($testBinaries.Count -eq 0) {
|
||||||
|
Write-Error "No test binaries found!"
|
||||||
|
Write-Host "Cargo output:" -ForegroundColor Yellow
|
||||||
|
$cargoOutput | ForEach-Object { Write-Host $_ }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Found $($testBinaries.Count) test $($testBinaries.Count -eq 1 ? "binary" : "binaries")" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Run each test binary
|
||||||
|
foreach ($testBinary in $testBinaries) {
|
||||||
|
Write-Host "`nTesting: $($testBinary.Name)" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Copy to target
|
||||||
|
pscp.exe -batch -scp -pw $password $testBinary.FullName ${RemoteHost}:$RemoteTestPath | Out-Null
|
||||||
|
|
||||||
|
# Run on target
|
||||||
|
$testArgs = "$(if ($Verbose) {"--nocapture --show-output"})"
|
||||||
|
if ($TestName) {
|
||||||
|
$testArgs = "$TestName $testArgs"
|
||||||
|
}
|
||||||
|
|
||||||
|
plink -batch -ssh $RemoteHost -pw $password "chmod +x $RemoteTestPath && $RemoteTestPath $testArgs"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`nDone!" -ForegroundColor Green
|
||||||
+9
-5
@@ -41,7 +41,7 @@ pub struct IpNeighLine {
|
|||||||
|
|
||||||
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
// NUDState custom Deserialize now lives in arpparse/impl.rs; use serde_with OneOrMany for Vec
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize)]
|
#[derive(Debug, PartialEq, Eq, EnumString, Display, Clone, Copy, Hash, serde::Serialize, Default)]
|
||||||
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
#[strum(serialize_all = "UPPERCASE", ascii_case_insensitive)]
|
||||||
#[serde(rename_all = "UPPERCASE")]
|
#[serde(rename_all = "UPPERCASE")]
|
||||||
pub enum NUDState {
|
pub enum NUDState {
|
||||||
@@ -63,10 +63,7 @@ pub enum NUDState {
|
|||||||
/// neighbour state if it was valid and the
|
/// neighbour state if it was valid and the
|
||||||
/// address is not changed by this command.
|
/// address is not changed by this command.
|
||||||
Stale,
|
Stale,
|
||||||
/// this is a pseudo state used when initially
|
|
||||||
/// creating a neighbour entry or after trying to
|
|
||||||
/// remove it before it becomes free to do so.
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// the neighbour entry has not (yet) been
|
/// the neighbour entry has not (yet) been
|
||||||
/// validated/resolved.
|
/// validated/resolved.
|
||||||
@@ -80,6 +77,13 @@ pub enum NUDState {
|
|||||||
/// success, neighbor validation has ultimately
|
/// success, neighbor validation has ultimately
|
||||||
/// failed.
|
/// failed.
|
||||||
Failed,
|
Failed,
|
||||||
|
|
||||||
|
/// this is a pseudo state used when initially
|
||||||
|
/// creating a neighbour entry or after trying to
|
||||||
|
/// remove it before it becomes free to do so.
|
||||||
|
#[serde(other)]
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NUDState {
|
impl NUDState {
|
||||||
|
|||||||
+28
-9
@@ -1,12 +1,31 @@
|
|||||||
// generated with ./scripts/map_static.py
|
// generated with ./scripts/map_static.py
|
||||||
|
|
||||||
pub const HOME_2_HTML: &str = include_str!("../static/home_2.html");
|
macro_rules! hehe {
|
||||||
pub mod home_2 {
|
// Folder
|
||||||
pub const DOM_JS: &str = include_str!("../static/home_2/dom.js");
|
(folder $name:ident { $($children:tt)* } $($rest:tt)*) => {
|
||||||
pub const LEASES_JS: &str = include_str!("../static/home_2/leases.js");
|
pub mod $name {
|
||||||
pub const MAIN_JS: &str = include_str!("../static/home_2/main.js");
|
hehe!{$($children)*}
|
||||||
pub const STATUS_JS: &str = include_str!("../static/home_2/status.js");
|
}
|
||||||
pub const STYLES_CSS: &str = include_str!("../static/home_2/styles.css");
|
hehe!{$($rest)*}
|
||||||
pub const UTILS_JS: &str = include_str!("../static/home_2/utils.js");
|
};
|
||||||
pub const WAKE_JS: &str = include_str!("../static/home_2/wake.js");
|
// File
|
||||||
|
(file $name:ident $file:literal $($rest:tt)*) => {
|
||||||
|
pub const $name: &str = include_str!($file);
|
||||||
|
hehe!{$($rest)*}
|
||||||
|
};
|
||||||
|
// Base case
|
||||||
|
() => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
hehe! {
|
||||||
|
file HOME_2_HTML "../static/home_2.html"
|
||||||
|
folder home_2 {
|
||||||
|
file DOM_JS "../static/home_2/dom.js"
|
||||||
|
file LEASES_JS "../static/home_2/leases.js"
|
||||||
|
file MAIN_JS "../static/home_2/main.js"
|
||||||
|
file STATUS_JS "../static/home_2/status.js"
|
||||||
|
file STYLES_CSS "../static/home_2/styles.css"
|
||||||
|
file UTILS_JS "../static/home_2/utils.js"
|
||||||
|
file WAKE_JS "../static/home_2/wake.js"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,7 @@ async fn entry() -> io::Result<()> {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
fn main() -> color_eyre::Result<()> {
|
fn main() -> color_eyre::Result<()> {
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
// use crate::arpparse::NUDState;
|
// use crate::arpparse::NUDState;
|
||||||
// println!("{}", NUDState::Reachable.to_string().to_lowercase());
|
// println!("{}", NUDState::Reachable.to_string().to_lowercase());
|
||||||
|
|||||||
+5
-12
@@ -49,12 +49,10 @@ pub async fn status_smart_redirect(
|
|||||||
};
|
};
|
||||||
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}"))),
|
||||||
Err(e) => Err((
|
Err(e) => Err(ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
error: e.to_string(),
|
||||||
Json(ApiError {
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: e.to_string(),
|
}),
|
||||||
}),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,12 +65,7 @@ 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(
|
get_ips(&name).await.map_or_else(
|
||||||
|e| {
|
|e| ApiError::ise(e.to_string()).into_response(),
|
||||||
ApiError {
|
|
||||||
error: e.to_string(),
|
|
||||||
}
|
|
||||||
.into_response()
|
|
||||||
},
|
|
||||||
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
|ips| Json(ips.collect::<Vec<_>>()).into_response(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
+5
-7
@@ -26,12 +26,10 @@ pub async fn get_dhcp_leases(Query(raw): Query<DhcpLeasesQueryRaw>) -> impl Into
|
|||||||
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
let out = enrich_leases_with_nud_state(leases_with_names).await;
|
||||||
(StatusCode::OK, Json(out)).into_response()
|
(StatusCode::OK, Json(out)).into_response()
|
||||||
}
|
}
|
||||||
Err(e) => (
|
Err(e) => ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
error: e.to_string(),
|
||||||
Json(ApiError {
|
code: StatusCode::BAD_GATEWAY,
|
||||||
error: e.to_string(),
|
}
|
||||||
}),
|
.into_response(),
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+13
-1
@@ -7,11 +7,23 @@ use serde::Serialize;
|
|||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct ApiError {
|
pub struct ApiError {
|
||||||
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
|
pub code: StatusCode,
|
||||||
pub error: String,
|
pub error: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ApiError {
|
||||||
|
/// [StatusCode::INTERNAL_SERVER_ERROR]
|
||||||
|
pub const fn ise(error: String) -> Self {
|
||||||
|
Self {
|
||||||
|
code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoResponse for ApiError {
|
impl IntoResponse for ApiError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(self)).into_response()
|
(self.code, Json(self)).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ pub mod status;
|
|||||||
pub mod wake;
|
pub mod wake;
|
||||||
|
|
||||||
use crate::assets;
|
use crate::assets;
|
||||||
|
use crate::dhcpparse::load_mac_name_cache;
|
||||||
use crate::route::api::ip;
|
use crate::route::api::ip;
|
||||||
use crate::route::api::status_redirect;
|
use crate::route::api::status_redirect;
|
||||||
use crate::route::api::status_smart_redirect;
|
use crate::route::api::status_smart_redirect;
|
||||||
@@ -14,6 +15,8 @@ use crate::route::dhcp::get_dhcp_leases;
|
|||||||
use crate::route::status::get_status_json;
|
use crate::route::status::get_status_json;
|
||||||
use crate::route::wake::wake_multi;
|
use crate::route::wake::wake_multi;
|
||||||
|
|
||||||
|
use axum::Json;
|
||||||
|
use axum::response::IntoResponse;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum::{Router, http::header, response::Html, routing::get};
|
use axum::{Router, http::header, response::Html, routing::get};
|
||||||
|
|
||||||
@@ -57,4 +60,13 @@ pub fn api_router() -> Router {
|
|||||||
.route("/devs", get(devs_router))
|
.route("/devs", get(devs_router))
|
||||||
.route("/wake", post(wake_multi))
|
.route("/wake", post(wake_multi))
|
||||||
.route("/ips/{name}", get(ip))
|
.route("/ips/{name}", get(ip))
|
||||||
|
.route(
|
||||||
|
"/mac-cache",
|
||||||
|
get(|| async {
|
||||||
|
match load_mac_name_cache().await {
|
||||||
|
Ok(h) => Json(h).into_response(),
|
||||||
|
Err(e) => e.to_string().into_response(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
+5
-15
@@ -47,14 +47,6 @@ pub struct Filters {
|
|||||||
pub macs: Vec<MacAddr>,
|
pub macs: Vec<MacAddr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deprecated = "use ApiError"]
|
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Debug, Serialize, Default)]
|
|
||||||
pub struct StatusError {
|
|
||||||
pub name: Option<String>,
|
|
||||||
pub error: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_status_json(
|
pub async fn get_status_json(
|
||||||
Query(DeviceQuery {
|
Query(DeviceQuery {
|
||||||
name,
|
name,
|
||||||
@@ -94,13 +86,11 @@ pub async fn get_status_json(
|
|||||||
)
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
Err(error) => (
|
Err(error) => ApiError {
|
||||||
StatusCode::BAD_GATEWAY,
|
code: StatusCode::BAD_GATEWAY,
|
||||||
Json(ApiError {
|
error: error.to_string(),
|
||||||
error: error.to_string(),
|
}
|
||||||
}),
|
.into_response(),
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Status endpoints
|
// Status endpoints
|
||||||
+1
-1
@@ -19,7 +19,7 @@ pub async fn _ping_ip_2<T: ToSocketAddrs>(_addr: T) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
|
pub async fn _ping_ip_3<T: Into<IpAddr>>(addr: T) -> u8 {
|
||||||
match get_mac(Some(addr.into()), None, None).await {
|
match get_mac(Some(addr.into()), None, &[] as &[NUDState]).await {
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
Ok(l) => l
|
Ok(l) => l
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
+5
-14
@@ -48,7 +48,7 @@ pub async fn get_macs(
|
|||||||
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
|
let ip_m: HashSet<IpAddr> = futures::future::try_join_all(
|
||||||
machine_names
|
machine_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| async { get_ips(c.as_ref()).await }),
|
.map(|c| get_ips(c.as_ref())),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -72,13 +72,8 @@ pub async fn get_macs(
|
|||||||
} else {
|
} else {
|
||||||
devs.iter().next().map(AsRef::as_ref)
|
devs.iter().next().map(AsRef::as_ref)
|
||||||
};
|
};
|
||||||
let opt_state = if state.len() > 1 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
state.iter().next().copied()
|
|
||||||
};
|
|
||||||
|
|
||||||
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, opt_dev, opt_state);
|
let run_one = |to_ip: Option<IpAddr>| get_mac(to_ip, opt_dev, state);
|
||||||
|
|
||||||
let mut ip_filtered = if let Some(something) = ip_all {
|
let mut ip_filtered = if let Some(something) = ip_all {
|
||||||
if something.len() == 1 {
|
if something.len() == 1 {
|
||||||
@@ -97,7 +92,6 @@ pub async fn get_macs(
|
|||||||
// Apply additional filters if any were provided
|
// Apply additional filters if any were provided
|
||||||
if !devs.is_empty() || !macs.is_empty() || !state.is_empty() {
|
if !devs.is_empty() || !macs.is_empty() || !state.is_empty() {
|
||||||
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
|
let devset: HashSet<_> = devs.iter().map(AsRef::as_ref).collect();
|
||||||
let nudset: HashSet<_> = state.iter().collect();
|
|
||||||
let macset: HashSet<_> = macs.iter().collect();
|
let macset: HashSet<_> = macs.iter().collect();
|
||||||
|
|
||||||
ip_filtered.retain(|entry| {
|
ip_filtered.retain(|entry| {
|
||||||
@@ -105,13 +99,10 @@ pub async fn get_macs(
|
|||||||
let dev_ok =
|
let dev_ok =
|
||||||
devset.is_empty() || entry.dev.as_deref().is_some_and(|d| devset.contains(d));
|
devset.is_empty() || entry.dev.as_deref().is_some_and(|d| devset.contains(d));
|
||||||
|
|
||||||
// NUD filter: always present, simple check
|
|
||||||
let nud_ok = nudset.is_empty() || nudset.contains(&entry.state);
|
|
||||||
|
|
||||||
// MAC filter: if we're filtering by MAC, entry must have a MAC AND it must be in the set
|
// MAC filter: if we're filtering by MAC, entry must have a MAC AND it must be in the set
|
||||||
let mac_ok = macset.is_empty() || entry.mac.is_some_and(|m| macset.contains(&m));
|
let mac_ok = macset.is_empty() || entry.mac.is_some_and(|m| macset.contains(&m));
|
||||||
|
|
||||||
dev_ok && nud_ok && mac_ok
|
dev_ok && mac_ok
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
Ok(ip_filtered)
|
Ok(ip_filtered)
|
||||||
@@ -121,7 +112,7 @@ pub async fn get_macs(
|
|||||||
pub async fn get_mac(
|
pub async fn get_mac(
|
||||||
ip: Option<IpAddr>,
|
ip: Option<IpAddr>,
|
||||||
dev: Option<&str>,
|
dev: Option<&str>,
|
||||||
state: Option<NUDState>,
|
state: &[NUDState],
|
||||||
) -> Result<Vec<IpNeighLine>> {
|
) -> Result<Vec<IpNeighLine>> {
|
||||||
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
let mut args: Vec<String> = vec!["neigh".into(), "show".into()];
|
||||||
if let Some(ip) = ip {
|
if let Some(ip) = ip {
|
||||||
@@ -132,7 +123,7 @@ pub async fn get_mac(
|
|||||||
args.push("dev".into());
|
args.push("dev".into());
|
||||||
args.push(d.to_string());
|
args.push(d.to_string());
|
||||||
}
|
}
|
||||||
if let Some(nud) = state {
|
for nud in state {
|
||||||
args.push("nud".into());
|
args.push("nud".into());
|
||||||
args.push(nud.as_ip_neigh_arg().into());
|
args.push(nud.as_ip_neigh_arg().into());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ impl From<WakeTargetResult> for RouteWakeResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RouteWakeTarget {
|
impl RouteWakeTarget {
|
||||||
pub fn to_incomplete(self) -> RouteWakeResult {
|
pub const fn to_incomplete(self) -> RouteWakeResult {
|
||||||
RouteWakeResult {
|
RouteWakeResult {
|
||||||
target: self,
|
target: self,
|
||||||
status: RouteWakeStatus::Incomplete,
|
status: RouteWakeStatus::Incomplete,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_incomplete(&self) -> bool {
|
pub const fn is_incomplete(&self) -> bool {
|
||||||
!matches!(
|
!matches!(
|
||||||
self,
|
self,
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -22,21 +22,21 @@ pub enum WakeStatus {
|
|||||||
WrongSize,
|
WrongSize,
|
||||||
}
|
}
|
||||||
impl WakeTarget {
|
impl WakeTarget {
|
||||||
fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
const fn _new(ip: IpAddr, mac: MacAddr) -> Self {
|
||||||
Self { ip, mac }
|
Self { ip, mac }
|
||||||
}
|
}
|
||||||
fn good(self) -> WakeTargetResult {
|
const fn good(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::Success)
|
WakeTargetResult::new(self, WakeStatus::Success)
|
||||||
}
|
}
|
||||||
fn bad(self) -> WakeTargetResult {
|
const fn bad(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
WakeTargetResult::new(self, WakeStatus::WrongSize)
|
||||||
}
|
}
|
||||||
fn errored(self) -> WakeTargetResult {
|
const fn errored(self) -> WakeTargetResult {
|
||||||
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
WakeTargetResult::new(self, WakeStatus::NonexistentAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl WakeTargetResult {
|
impl WakeTargetResult {
|
||||||
fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
const fn new(target: WakeTarget, status: WakeStatus) -> Self {
|
||||||
Self { target, status }
|
Self { target, status }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user