sub-logic is own function
- lda
This commit is contained in:
@@ -1,17 +1,186 @@
|
||||
# wakey
|
||||
|
||||
router shit
|
||||
`wakey` is a Wake-on-LAN and LAN-observability tool for a Linux router.
|
||||
|
||||
## whatever i did must be BEAUTIFUL
|
||||
It started as a small web UI running on-device. It is now being reshaped into a
|
||||
service-first project with:
|
||||
|
||||
read [in vietnamese](https://ldlda.com/blogs/else/ssh-ax3000cv2-update-firmware-0-10-2)
|
||||
- a reusable core model
|
||||
- a Linux/OpenWrt adapter layer
|
||||
- a CLI for operators
|
||||
- a temporary legacy HTTP/static adapter during migration
|
||||
|
||||
## now i NEED the split arch to be on
|
||||
## What it does
|
||||
|
||||
like RN rn rn so i can have web outside and the lil agent can be sitting here configuring itself (Parse/append to rc.local files? add to init.d? fuhh)
|
||||
`wakey` currently focuses on a small set of router-side jobs:
|
||||
|
||||
I NEED IT
|
||||
- inspect LAN neighbor state
|
||||
- read DHCP lease data
|
||||
- merge those facts into a higher-level device inventory
|
||||
- list useful interface/broadcast information
|
||||
- send Wake-on-LAN packets
|
||||
|
||||
I NEEED IT
|
||||
In practice that means commands like:
|
||||
|
||||
I NEEEEEED it.
|
||||
```sh
|
||||
wakey status bedroom-pc
|
||||
wakey leases --include-state
|
||||
wakey devs
|
||||
wakey wake bedroom-pc
|
||||
wakey wake --mac aa:bb:cc:dd:ee:ff
|
||||
wakey http --host :: --port 12012
|
||||
```
|
||||
|
||||
## Workspace layout
|
||||
|
||||
- `wakey-core`
|
||||
- shared types and parsing
|
||||
- device, neighbor, DHCP, interface, and wake models
|
||||
- `wakey-linux`
|
||||
- Linux/OpenWrt adapter
|
||||
- DHCP lease loading, interface summaries, neighbor lookup, WoL sending
|
||||
- `wakey`
|
||||
- service layer, CLI, and temporary HTTP/static adapter
|
||||
- `ipjs`
|
||||
- typed wrappers around Linux `ip -j ...` data
|
||||
- JSON-first, with optional experimental netlink backends
|
||||
|
||||
## Current architecture
|
||||
|
||||
Inside the `wakey` crate:
|
||||
|
||||
- `src/service`
|
||||
- the real use-case layer
|
||||
- status, leases, inventory, interfaces, wake, and query resolution
|
||||
- `src/http`
|
||||
- temporary legacy HTTP/static adapter
|
||||
- compatibility mapping for the current `/static` client
|
||||
- `src/legacy`
|
||||
- transitional compatibility wrappers kept during the migration
|
||||
|
||||
The long-term direction is:
|
||||
|
||||
- keep the service layer stable
|
||||
- keep HTTP as an adapter, not the architecture
|
||||
- eventually move toward an agent + control-plane model
|
||||
|
||||
## CLI
|
||||
|
||||
`wakey` is usable as a local/operator CLI.
|
||||
|
||||
### Status
|
||||
|
||||
Show device status rows using a free-form selector:
|
||||
|
||||
```sh
|
||||
wakey status bedroom-pc
|
||||
```
|
||||
|
||||
Or use explicit filters:
|
||||
|
||||
```sh
|
||||
wakey status --dev br-lan --nud reachable
|
||||
wakey status --mac aa:bb:cc:dd:ee:ff
|
||||
wakey status --json
|
||||
```
|
||||
|
||||
### Leases
|
||||
|
||||
Show DHCP leases:
|
||||
|
||||
```sh
|
||||
wakey leases
|
||||
wakey leases --include-state
|
||||
wakey leases --include-state --json
|
||||
```
|
||||
|
||||
### Wake
|
||||
|
||||
Query mode:
|
||||
|
||||
```sh
|
||||
wakey wake bedroom-pc
|
||||
```
|
||||
|
||||
Explicit/manual mode:
|
||||
|
||||
```sh
|
||||
wakey wake --mac aa:bb:cc:dd:ee:ff
|
||||
wakey wake --mac aa:bb:cc:dd:ee:ff --ip 192.168.1.255
|
||||
wakey wake --mac aa:bb:cc:dd:ee:ff --json
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- query mode and explicit `--mac/--ip` mode are mutually exclusive
|
||||
- `--ip` requires `--mac`
|
||||
- `--mac` without `--ip` fans out to interface broadcast targets
|
||||
|
||||
### Interfaces
|
||||
|
||||
Show condensed interface summaries:
|
||||
|
||||
```sh
|
||||
wakey devs
|
||||
wakey devs br-lan
|
||||
wakey devs --up
|
||||
wakey devs --json
|
||||
```
|
||||
|
||||
### Temporary HTTP adapter
|
||||
|
||||
The old web/static app can still be served during migration:
|
||||
|
||||
```sh
|
||||
wakey http --host :: --port 12012
|
||||
```
|
||||
|
||||
This should be treated as a compatibility surface, not the long-term product
|
||||
shape.
|
||||
|
||||
## Tests
|
||||
|
||||
This repo has two useful testing modes:
|
||||
|
||||
- local compile checks
|
||||
- live on-device tests against the real router/runtime environment
|
||||
|
||||
### Local
|
||||
|
||||
```sh
|
||||
cargo check
|
||||
cargo test --no-run
|
||||
cargo clippy --all-targets --all-features -- -D warnings
|
||||
```
|
||||
|
||||
### On-device
|
||||
|
||||
Some integration tests are intentionally `#[ignore]` because they use real
|
||||
router state. Use the PowerShell helper:
|
||||
|
||||
```powershell
|
||||
./scripts/test_remote.ps1 -Package wakey -BinaryFilter integration_live_services -Ignored -NoCapture
|
||||
./scripts/test_remote.ps1 -Package wakey -BinaryFilter integration_inventory -Ignored -NoCapture
|
||||
```
|
||||
|
||||
You can further narrow execution with `-Filter` to select individual Rust test
|
||||
functions inside a test binary.
|
||||
|
||||
## Build target
|
||||
|
||||
This project is primarily aimed at an OpenWrt/Linux ARM router target. The
|
||||
workspace is commonly built for:
|
||||
|
||||
```text
|
||||
armv7-unknown-linux-musleabihf
|
||||
```
|
||||
|
||||
Some crates and tests are Linux-specific by design.
|
||||
|
||||
## Notes
|
||||
|
||||
- `ipjs` is JSON-first by default; experimental netlink paths exist where they
|
||||
are worth keeping.
|
||||
- the current web client is temporary and kept alive through explicit
|
||||
compatibility mapping
|
||||
- the service layer is the part intended to survive the migration
|
||||
|
||||
@@ -43,6 +43,51 @@ This folder has small helpers for build, CI, and router install. Keep it simple;
|
||||
- Uploads to `<RemotePath>.tmp` then atomically moves into place; `-Restart` restarts the service; `-Quiet` silences MOTD.
|
||||
- `package_rootfs.ps1` — Produces `dist/wakey-rootfs-<version>-<target>.tgz` with `/root/.bin/wakey` and `/etc/init.d/*`.
|
||||
- `publish.ps1` — Optional version bump + build + tag (and `cargo publish` only if you pass `-Publish`).
|
||||
- `test_remote.ps1` — Build ARM Linux test binaries locally, upload them to the router, and run them there.
|
||||
|
||||
## Remote test runner
|
||||
|
||||
`test_remote.ps1` is the main way to run Linux/router-specific tests that do not
|
||||
make sense on Windows.
|
||||
|
||||
Basic examples:
|
||||
|
||||
```powershell
|
||||
./scripts/test_remote.ps1 -Package wakey
|
||||
./scripts/test_remote.ps1 -Package wakey -BinaryFilter integration_live_services -Ignored -NoCapture
|
||||
./scripts/test_remote.ps1 -Package wakey -BinaryFilter integration_live_services -Filter status_real_router_default_query_succeeds -Ignored -NoCapture
|
||||
./scripts/test_remote.ps1 -AllPackages
|
||||
```
|
||||
|
||||
Important semantics:
|
||||
|
||||
- `-BinaryFilter`
|
||||
- selects which compiled Rust test executable(s) to run
|
||||
- `-Filter`
|
||||
- filters test functions inside a selected Rust test binary
|
||||
- `-Ignored`
|
||||
- runs `#[ignore]` tests
|
||||
- `-IncludeIgnored`
|
||||
- includes ignored tests in addition to normal ones
|
||||
- `-List`
|
||||
- lists tests inside the remote binary instead of executing them
|
||||
|
||||
Useful flags:
|
||||
|
||||
- `-BuildProfile debug|release`
|
||||
- `-Exact`
|
||||
- `-NoCapture`
|
||||
- `-ShowOutput`
|
||||
- `-Threads <n>`
|
||||
- `-RemoteHost root@<router-ip>`
|
||||
- `-RemoteTestPath /tmp/tmp/wakey-test`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- test executables are discovered via Cargo JSON messages, not regexing human output
|
||||
- binaries are batch-uploaded per package run
|
||||
- each package run gets a unique remote temp directory for safer concurrent use
|
||||
- packages with no test executables are skipped instead of treated as failures
|
||||
|
||||
## CI (Gitea)
|
||||
|
||||
|
||||
+112
-15
@@ -1,7 +1,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use macaddr::MacAddr;
|
||||
use std::net::IpAddr;
|
||||
use wakey_core::{WakeResult, WakeTarget};
|
||||
use wakey_core::{InterfaceSummary, WakeResult, WakeTarget};
|
||||
|
||||
use crate::service::interfaces::get_interface_summaries;
|
||||
use crate::service::inventory::resolve_devices;
|
||||
@@ -24,25 +24,14 @@ pub async fn wake_from_query(input: impl Into<String>) -> Result<WakeResult> {
|
||||
///
|
||||
/// This is used by explicit manual wake mode when only a MAC address is supplied.
|
||||
pub async fn broadcast_wake_targets(mac: MacAddr) -> Result<Vec<WakeTarget>> {
|
||||
Ok(get_interface_summaries()
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|iface| iface.addrs.into_iter())
|
||||
.filter_map(|addr| addr.broadcast)
|
||||
.map(|ip| WakeTarget {
|
||||
ip: Some(IpAddr::V4(ip)),
|
||||
mac: Some(mac),
|
||||
})
|
||||
.collect())
|
||||
let interfaces = get_interface_summaries().await?;
|
||||
broadcast_wake_targets_from_interfaces(&interfaces, mac)
|
||||
}
|
||||
|
||||
/// Wake a device explicitly by MAC, optionally targeting a specific IP/broadcast.
|
||||
pub async fn wake_explicit(mac: MacAddr, ip: Option<IpAddr>) -> Result<WakeResult> {
|
||||
let targets = match ip {
|
||||
Some(ip) => vec![WakeTarget {
|
||||
ip: Some(ip),
|
||||
mac: Some(mac),
|
||||
}],
|
||||
Some(ip) => explicit_wake_targets_for_ip(mac, ip),
|
||||
None => broadcast_wake_targets(mac).await?,
|
||||
};
|
||||
wake_targets(targets).await
|
||||
@@ -65,3 +54,111 @@ pub async fn resolve_wake_targets(input: impl Into<String>) -> Result<Vec<WakeTa
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn explicit_wake_targets_for_ip(mac: MacAddr, ip: IpAddr) -> Vec<WakeTarget> {
|
||||
vec![WakeTarget {
|
||||
ip: Some(ip),
|
||||
mac: Some(mac),
|
||||
}]
|
||||
}
|
||||
|
||||
fn broadcast_wake_targets_from_interfaces(
|
||||
interfaces: &[InterfaceSummary],
|
||||
mac: MacAddr,
|
||||
) -> Result<Vec<WakeTarget>> {
|
||||
let targets: Vec<WakeTarget> = interfaces
|
||||
.iter()
|
||||
.flat_map(|iface| iface.addrs.iter())
|
||||
.filter_map(|addr| addr.broadcast)
|
||||
.map(|ip| WakeTarget {
|
||||
ip: Some(IpAddr::V4(ip)),
|
||||
mac: Some(mac),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if targets.is_empty() {
|
||||
anyhow::bail!("no broadcast-capable interfaces found");
|
||||
}
|
||||
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use super::{broadcast_wake_targets_from_interfaces, explicit_wake_targets_for_ip};
|
||||
use wakey_core::{InterfaceAddr, InterfaceSummary};
|
||||
|
||||
#[test]
|
||||
fn explicit_wake_target_for_ip_builds_one_complete_target() {
|
||||
let mac: macaddr::MacAddr = "aa:bb:cc:dd:ee:ff".parse().expect("mac");
|
||||
let ip = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255));
|
||||
|
||||
let targets = explicit_wake_targets_for_ip(mac, ip);
|
||||
|
||||
assert_eq!(targets.len(), 1);
|
||||
assert_eq!(targets[0].ip, Some(ip));
|
||||
assert_eq!(targets[0].mac, Some(mac));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broadcast_wake_targets_from_interfaces_errors_when_no_broadcast_exists() {
|
||||
let mac: macaddr::MacAddr = "aa:bb:cc:dd:ee:ff".parse().expect("mac");
|
||||
let interfaces = vec![InterfaceSummary {
|
||||
ifindex: 1,
|
||||
ifname: "eth0".into(),
|
||||
operstate: "up".into(),
|
||||
mac: None,
|
||||
addrs: vec![InterfaceAddr {
|
||||
family: Some("inet".into()),
|
||||
cidr: Some("192.168.1.10/24".into()),
|
||||
broadcast: None,
|
||||
scope: Some("global".into()),
|
||||
label: None,
|
||||
}],
|
||||
}];
|
||||
|
||||
let err = broadcast_wake_targets_from_interfaces(&interfaces, mac)
|
||||
.expect_err("should error without broadcast-capable interfaces");
|
||||
|
||||
assert!(err.to_string().contains("no broadcast-capable interfaces found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn broadcast_wake_targets_from_interfaces_builds_targets_from_broadcast_rows() {
|
||||
let mac: macaddr::MacAddr = "aa:bb:cc:dd:ee:ff".parse().expect("mac");
|
||||
let interfaces = vec![InterfaceSummary {
|
||||
ifindex: 2,
|
||||
ifname: "br-lan".into(),
|
||||
operstate: "up".into(),
|
||||
mac: None,
|
||||
addrs: vec![
|
||||
InterfaceAddr {
|
||||
family: Some("inet".into()),
|
||||
cidr: Some("192.168.1.1/24".into()),
|
||||
broadcast: Some(Ipv4Addr::new(192, 168, 1, 255)),
|
||||
scope: Some("global".into()),
|
||||
label: None,
|
||||
},
|
||||
InterfaceAddr {
|
||||
family: Some("inet6".into()),
|
||||
cidr: Some("fe80::1/64".into()),
|
||||
broadcast: None,
|
||||
scope: Some("link".into()),
|
||||
label: None,
|
||||
},
|
||||
],
|
||||
}];
|
||||
|
||||
let targets = broadcast_wake_targets_from_interfaces(&interfaces, mac)
|
||||
.expect("broadcast target resolution should succeed");
|
||||
|
||||
assert_eq!(targets.len(), 1);
|
||||
assert_eq!(
|
||||
targets[0].ip,
|
||||
Some(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 255)))
|
||||
);
|
||||
assert_eq!(targets[0].mac, Some(mac));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user