ts some minor update HOPEFULLY wont make it run 16k times worse HOPEFULLY

This commit is contained in:
lda
2026-04-15 03:26:26 +07:00 Unverified
parent 5fbe4b9d61
commit f59fd7a846
13 changed files with 96 additions and 77 deletions
+31
View File
@@ -15,6 +15,7 @@
# WAKEY_CC_VERSION Bundle version label (default: v0.0.0-local-<short_sha>) # WAKEY_CC_VERSION Bundle version label (default: v0.0.0-local-<short_sha>)
# WAKEY_CC_NO_RESTART If set, skip systemd restart # WAKEY_CC_NO_RESTART If set, skip systemd restart
# WAKEY_CC_KEEP_TMP If set, keep temp checkout/build dir for debugging # WAKEY_CC_KEEP_TMP If set, keep temp checkout/build dir for debugging
# SUDO Privilege escalation command/path (default: sudo when needed)
# #
# Requires: git, cargo, tar, pnpm, systemctl (optional for restart) # Requires: git, cargo, tar, pnpm, systemctl (optional for restart)
@@ -45,12 +46,31 @@ require_cmd() {
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1" command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
} }
sudo_cmd() {
if [ "$(id -u)" -eq 0 ]; then
return 0
fi
if [ -n "${SUDO:-}" ]; then
printf '%s' "$SUDO"
return 0
fi
if command -v sudo >/dev/null 2>&1; then
printf '%s' sudo
return 0
fi
fail 'root privileges are required for install/restart steps; set SUDO or run as root'
}
main() { main() {
REPO_URL="${WAKEY_CC_REPO_URL:-https://git.ldlda.com/lda/wakey.git}" REPO_URL="${WAKEY_CC_REPO_URL:-https://git.ldlda.com/lda/wakey.git}"
REF="${WAKEY_CC_REF:-main}" REF="${WAKEY_CC_REF:-main}"
TARGET="${WAKEY_CC_TARGET:-$(default_target)}" TARGET="${WAKEY_CC_TARGET:-$(default_target)}"
ROOT="${WAKEY_CC_ROOT:-$PWD}" ROOT="${WAKEY_CC_ROOT:-$PWD}"
SERVICE="${WAKEY_CC_SERVICE:-wakey-cc.service}" SERVICE="${WAKEY_CC_SERVICE:-wakey-cc.service}"
SUDO_BIN=$(sudo_cmd)
require_cmd git require_cmd git
require_cmd cargo require_cmd cargo
@@ -104,15 +124,26 @@ main() {
[ -f "$STAGING/bin/wakey-control-plane" ] || fail 'bundle missing bin/wakey-control-plane' [ -f "$STAGING/bin/wakey-control-plane" ] || fail 'bundle missing bin/wakey-control-plane'
[ -f "$STAGING/ui/dist/index.html" ] || fail 'bundle missing ui/dist/index.html' [ -f "$STAGING/ui/dist/index.html" ] || fail 'bundle missing ui/dist/index.html'
if [ "$(id -u)" -ne 0 ] && [ -n "$SUDO_BIN" ]; then
$SUDO_BIN mkdir -p "$ROOT"
$SUDO_BIN cp -a "$STAGING/." "$ROOT/"
else
mkdir -p "$ROOT" mkdir -p "$ROOT"
cp -a "$STAGING/." "$ROOT/" cp -a "$STAGING/." "$ROOT/"
fi
if [ -z "${WAKEY_CC_NO_RESTART:-}" ] && command -v systemctl >/dev/null 2>&1; then if [ -z "${WAKEY_CC_NO_RESTART:-}" ] && command -v systemctl >/dev/null 2>&1; then
if systemctl list-unit-files "$SERVICE" >/dev/null 2>&1; then if systemctl list-unit-files "$SERVICE" >/dev/null 2>&1; then
log "restarting $SERVICE" log "restarting $SERVICE"
if [ "$(id -u)" -ne 0 ] && [ -n "$SUDO_BIN" ]; then
$SUDO_BIN systemctl daemon-reload
$SUDO_BIN systemctl restart "$SERVICE"
$SUDO_BIN systemctl --no-pager --full status "$SERVICE" | sed -n '1,16p'
else
systemctl daemon-reload systemctl daemon-reload
systemctl restart "$SERVICE" systemctl restart "$SERVICE"
systemctl --no-pager --full status "$SERVICE" | sed -n '1,16p' systemctl --no-pager --full status "$SERVICE" | sed -n '1,16p'
fi
else else
log "service $SERVICE not installed; skipped restart" log "service $SERVICE not installed; skipped restart"
fi fi
+2 -3
View File
@@ -3,9 +3,8 @@ pub mod utils;
pub use service::{ pub use service::{
broadcast_wake_targets, get_interface_summaries, get_interface_summary, get_ips, get_leases, broadcast_wake_targets, get_interface_summaries, get_interface_summary, get_ips, get_leases,
inventory, leases_without_state, merge_devices, query_to_inventory_query, resolve_devices, inventory, leases_without_state, merge_devices, resolve_devices, resolve_query,
resolve_query, resolve_selector, resolve_wake_targets, wake_explicit, wake_from_query, resolve_selector, resolve_wake_targets, wake_explicit, wake_from_query, wake_targets,
wake_targets,
}; };
#[cfg(test)] #[cfg(test)]
+18 -35
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use anyhow::Result; use anyhow::Result;
use wakey_core::{ use wakey_core::{
Device, DeviceInventory, DhcpLease, DhcpLeaseWithState, InventoryQuery, NeighborEntry, Device, DeviceInventory, DhcpLease, DhcpLeaseWithState, InventoryQuery, NeighborEntry,
@@ -59,48 +61,29 @@ pub fn merge_devices(
.map(|(neighbors, leases)| Device::from_parts(neighbors, leases)) .map(|(neighbors, leases)| Device::from_parts(neighbors, leases))
.collect(); .collect();
let mut texts: Vec<&str> = Vec::new(); let mut texts = HashSet::new();
let mut devs: Vec<&str> = Vec::new(); let mut devs = HashSet::new();
let mut ips = Vec::new(); let mut ips = HashSet::new();
let mut macs = Vec::new(); let mut macs = HashSet::new();
let mut nuds = Vec::new(); let mut nuds = HashSet::new();
for term in query { for term in query {
match term { match term {
Query::Text(v) => texts.push(v.as_str()), Query::Text(v) => texts.insert(v.as_str()),
Query::Interface(v) => devs.push(v.as_str()), Query::Interface(v) => devs.insert(v.as_str()),
Query::Ip(v) => ips.push(*v), Query::Ip(v) => ips.insert(*v),
Query::Mac(v) => macs.push(*v), Query::Mac(v) => macs.insert(*v),
Query::NeighborState(v) => nuds.push(*v), Query::NeighborState(v) => nuds.insert(*v),
} };
} }
if !texts.is_empty() {
devices.retain(|device| device.names.iter().any(|n| texts.iter().any(|t| n == t)));
}
if !devs.is_empty() {
devices.retain(|device| { devices.retain(|device| {
device (texts.is_empty() || device.names.iter().any(|n| texts.contains(n.as_str())))
.interfaces && (devs.is_empty() || device.interfaces.iter().any(|i| devs.contains(i.as_str())))
.iter() && (ips.is_empty() || device.ips.iter().any(|ip| ips.contains(ip)))
.any(|iface| devs.iter().any(|d| iface == d)) && (macs.is_empty() || device.macs.iter().any(|mac| macs.contains(mac)))
&& (nuds.is_empty() || device.neighbors.iter().any(|n| nuds.contains(&n.state)))
}); });
}
if !ips.is_empty() {
devices.retain(|device| device.ips.iter().any(|ip| ips.contains(ip)));
}
if !macs.is_empty() {
devices.retain(|device| device.macs.iter().any(|mac| macs.contains(mac)));
}
if !nuds.is_empty() {
devices.retain(|device| {
device
.neighbors
.iter()
.any(|neighbor| nuds.contains(&neighbor.state))
});
}
devices.sort_by(|a, b| { devices.sort_by(|a, b| {
presence_rank(b.presence) presence_rank(b.presence)
.cmp(&presence_rank(a.presence)) .cmp(&presence_rank(a.presence))
+1 -1
View File
@@ -7,7 +7,7 @@ pub mod wake;
pub use interfaces::{get_interface_summaries, get_interface_summary, get_ips}; pub use interfaces::{get_interface_summaries, get_interface_summary, get_ips};
pub use inventory::{inventory, merge_devices, resolve_devices}; pub use inventory::{inventory, merge_devices, resolve_devices};
pub use leases::{get_leases, leases_without_state}; pub use leases::{get_leases, leases_without_state};
pub use query::{query_to_inventory_query, resolve_query, resolve_selector}; pub use query::{resolve_query, resolve_selector};
pub use wake::{ pub use wake::{
broadcast_wake_targets, resolve_wake_targets, wake_explicit, wake_from_query, wake_targets, broadcast_wake_targets, resolve_wake_targets, wake_explicit, wake_from_query, wake_targets,
}; };
+1 -12
View File
@@ -2,11 +2,8 @@ use anyhow::Result;
use wakey_core::{InventoryQuery, Query, QueryInput}; use wakey_core::{InventoryQuery, Query, QueryInput};
/// Resolve free-form user input into an `InventoryQuery` filter shape. /// Resolve free-form user input into an `InventoryQuery` filter shape.
///
/// This is the compatibility entrypoint used by CLI and HTTP paths that still
/// speak in terms of query/filter payloads.
pub async fn resolve_query(input: impl Into<String>) -> Result<InventoryQuery> { pub async fn resolve_query(input: impl Into<String>) -> Result<InventoryQuery> {
query_to_inventory_query(resolve_selector(input).await?) Ok(vec![resolve_selector(input).await?])
} }
/// Classify one piece of free-form user input into a typed selector. /// Classify one piece of free-form user input into a typed selector.
@@ -24,11 +21,3 @@ pub async fn resolve_selector(input: impl Into<String>) -> Result<Query> {
}, },
) )
} }
/// Convert the newer selector-oriented `Query` model into an `InventoryQuery`.
///
/// This keeps the old filter-based service and HTTP surfaces working while the
/// internals migrate toward selector- and device-oriented APIs.
pub fn query_to_inventory_query(query: Query) -> Result<InventoryQuery> {
Ok(vec![query])
}
+9 -7
View File
@@ -6,6 +6,7 @@ use wakey_core::{InterfaceSummary, WakeResult, WakeTarget};
use crate::service::interfaces::get_interface_summaries; use crate::service::interfaces::get_interface_summaries;
use crate::service::inventory::resolve_devices; use crate::service::inventory::resolve_devices;
use crate::utils::product;
/// Send Wake-on-LAN packets for already-concrete wake targets. /// Send Wake-on-LAN packets for already-concrete wake targets.
#[instrument(skip_all, fields(targets = targets.len()))] #[instrument(skip_all, fields(targets = targets.len()))]
@@ -50,14 +51,15 @@ pub async fn wake_explicit(mac: MacAddr, ip: Option<IpAddr>) -> Result<WakeResul
#[instrument(skip_all)] #[instrument(skip_all)]
pub async fn resolve_wake_targets(input: impl Into<String>) -> Result<Vec<WakeTarget>> { pub async fn resolve_wake_targets(input: impl Into<String>) -> Result<Vec<WakeTarget>> {
let devices = resolve_devices(input).await?; let devices = resolve_devices(input).await?;
let targets: Vec<WakeTarget> = devices let targets: Vec<WakeTarget> = devices
.into_iter() .into_iter()
.flat_map(|device| { .flat_map(|device| {
let mac = device.macs.first().copied(); // TRY EVERYTHING some has to work
device product(device.ips, device.macs).map(|(i, m)| WakeTarget {
.ips ip: Some(i),
.into_iter() mac: Some(m),
.map(move |ip| WakeTarget { ip: Some(ip), mac }) })
}) })
.collect(); .collect();
debug!(targets = targets.len(), "resolved wake targets"); debug!(targets = targets.len(), "resolved wake targets");
@@ -79,8 +81,8 @@ fn broadcast_wake_targets_from_interfaces(
.iter() .iter()
.flat_map(|iface| iface.addrs.iter()) .flat_map(|iface| iface.addrs.iter())
.filter_map(|addr| addr.broadcast) .filter_map(|addr| addr.broadcast)
.map(|ip| WakeTarget { .map(|br| WakeTarget {
ip: Some(IpAddr::V4(ip)), ip: Some(IpAddr::V4(br)), // some will hit eventually AHHH logic
mac: Some(mac), mac: Some(mac),
}) })
.collect(); .collect();
+15 -1
View File
@@ -1 +1,15 @@
// Intentionally left empty. //! rare fns that belong nowhere
/// absolute dih cheese
/// took some inspo from itertools
pub fn product<A, B, T1, T2>(i1: T1, i2: T2) -> impl Iterator<Item = (A, B)>
where
A: Clone,
T1: IntoIterator<Item = A>,
T2: IntoIterator<Item = B>,
T2::IntoIter: Clone,
{
let i2 = i2.into_iter();
i1.into_iter()
.flat_map(move |i1i| i2.clone().map(move |i2i| (i1i.clone(), i2i)))
}
+1 -1
View File
@@ -109,7 +109,7 @@ fn init_config(args: InitConfigArgs) -> Result<()> {
let cfg = config::AgentConfig { let cfg = config::AgentConfig {
server_url: args server_url: args
.server_url .server_url
.unwrap_or_else(|| "https://control-plane.example.com".to_string()), .unwrap_or_else(|| "https://wakey.ldlda.com".to_string()),
agent_id: args agent_id: args
.agent_id .agent_id
.unwrap_or_else(|| "REPLACE_ME_AGENT_ID".to_string()), .unwrap_or_else(|| "REPLACE_ME_AGENT_ID".to_string()),
+1 -1
View File
@@ -36,7 +36,7 @@ async fn run_once(config: &AgentConfig) -> Result<()> {
.unwrap_or_default() .unwrap_or_default()
); );
let span = info_span!("agent_session", session_id = %session_id, agent_id = %config.agent_id); let span = info_span!("agent_session", session_id = %session_id, agent_id = %config.agent_id);
let _span_guard = span.enter(); let _span_guard = span.enter(); // the ENTIRETY of below is agent_session.
let ws_url = websocket_url(&config.server_url)?; let ws_url = websocket_url(&config.server_url)?;
info!(%ws_url, agent_id = %config.agent_id, "connecting agent websocket"); info!(%ws_url, agent_id = %config.agent_id, "connecting agent websocket");
+1 -1
View File
@@ -18,7 +18,7 @@ pub struct Store {
agent_meta: sled::Tree, agent_meta: sled::Tree,
audit_events: sled::Tree, audit_events: sled::Tree,
active_alerts: sled::Tree, active_alerts: sled::Tree,
alert_transitions: sled::Tree, alert_transitions: sled::Tree, // Where are them Types!
} }
const SCHEMA_VERSION_KEY: &[u8] = b"schema_version"; const SCHEMA_VERSION_KEY: &[u8] = b"schema_version";
+6 -6
View File
@@ -71,8 +71,8 @@ impl Device {
for lease in &leases { for lease in &leases {
ips.insert(lease.ip); ips.insert(lease.ip);
macs.insert(lease.mac); macs.insert(lease.mac);
if let Some(name) = &lease.name { if let Some(name) = lease.name.as_deref() {
names.insert(name.clone()); names.insert(name);
} }
} }
for neighbor in &neighbors { for neighbor in &neighbors {
@@ -80,8 +80,8 @@ impl Device {
if let Some(mac) = neighbor.mac { if let Some(mac) = neighbor.mac {
macs.insert(mac); macs.insert(mac);
} }
if let Some(dev) = &neighbor.dev { if let Some(dev) = neighbor.dev.as_deref() {
interfaces.insert(dev.clone()); interfaces.insert(dev);
} }
presence = std::cmp::max( presence = std::cmp::max(
presence_rank(presence), presence_rank(presence),
@@ -93,10 +93,10 @@ impl Device {
let macs: Vec<MacAddr> = macs.into_iter().collect(); let macs: Vec<MacAddr> = macs.into_iter().collect();
Self { Self {
id: macs.first().copied().map(|mac| DeviceId { mac }), id: macs.first().copied().map(|mac| DeviceId { mac }),
names: names.into_iter().collect(), names: names.into_iter().map(|n| n.to_owned()).collect(),
ips: ips.into_iter().collect(), ips: ips.into_iter().collect(),
macs, macs,
interfaces: interfaces.into_iter().collect(), interfaces: interfaces.into_iter().map(|d| d.to_owned()).collect(),
neighbors, neighbors,
leases, leases,
presence, presence,
+1 -1
View File
@@ -50,7 +50,7 @@ pub struct WakeTargetResult {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum WakeStatus { pub enum WakeStatus {
Succeed, Succeed,
NonexistentAddress, NonexistentAddress, // how do i know this isnt some other errors? what!
WrongSize, WrongSize,
Incomplete, Incomplete,
} }
+2 -1
View File
@@ -13,7 +13,7 @@ use wakey_core::{InterfaceAddr, InterfaceSummary};
/// Loopback is intentionally excluded. /// Loopback is intentionally excluded.
pub async fn list_interface_summaries() -> Result<Vec<InterfaceSummary>> { pub async fn list_interface_summaries() -> Result<Vec<InterfaceSummary>> {
#[cfg(unix)] #[cfg(unix)]
let rows = address::nl::get(None) let rows = address::nl::get(None) // <- why not do filtering here
.await .await
.context("rtnetlink address query failed")?; .context("rtnetlink address query failed")?;
@@ -22,6 +22,7 @@ pub async fn list_interface_summaries() -> Result<Vec<InterfaceSummary>> {
.await .await
.context("ip -j address show failed")?; .context("ip -j address show failed")?;
// convert semi raw [AddrOutput] into Almost the same [InterfaceSummary]
let mut out: Vec<InterfaceSummary> = rows let mut out: Vec<InterfaceSummary> = rows
.into_iter() .into_iter()
.filter(|row| row.ifname != "lo") .filter(|row| row.ifname != "lo")