use hotplug hooks, cant wait to use ts.

This commit is contained in:
lda
2026-04-26 22:17:28 +07:00 Verified
parent dc35e6e5d1
commit 35bed390f7
9 changed files with 258 additions and 1 deletions
+20
View File
@@ -68,8 +68,12 @@ try {
$initDestTmp = "$User@${HostName}:$initTmp" $initDestTmp = "$User@${HostName}:$initTmp"
$localDeploy = Join-Path $repoRoot 'scripts/remote_deploy_wakey.sh' $localDeploy = Join-Path $repoRoot 'scripts/remote_deploy_wakey.sh'
$localInit = Join-Path $repoRoot 'scripts/init/openwrt/wakey' $localInit = Join-Path $repoRoot 'scripts/init/openwrt/wakey'
$localDhcpHotplug = Join-Path $repoRoot 'scripts/hotplug/openwrt/dhcp/95-wakey'
$localNeighHotplug = Join-Path $repoRoot 'scripts/hotplug/openwrt/neigh/95-wakey'
$deployTmp = '/var/tmp/remote_deploy_wakey.sh' $deployTmp = '/var/tmp/remote_deploy_wakey.sh'
$deployPreferred = '/root/.bin/remote_deploy_wakey.sh' $deployPreferred = '/root/.bin/remote_deploy_wakey.sh'
$dhcpHotplugTmp = '/var/tmp/wakey.hotplug.dhcp.tmp'
$neighHotplugTmp = '/var/tmp/wakey.hotplug.neigh.tmp'
# Push binaries # Push binaries
Invoke-Scp -Local $localBin -Dest $destTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword Invoke-Scp -Local $localBin -Dest $destTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
@@ -79,6 +83,12 @@ try {
if (-not $SkipInitScript -and (Test-Path $localInit)) { if (-not $SkipInitScript -and (Test-Path $localInit)) {
Invoke-Scp -Local $localInit -Dest $initDestTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword Invoke-Scp -Local $localInit -Dest $initDestTmp -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
} }
if (Test-Path $localDhcpHotplug) {
Invoke-Scp -Local $localDhcpHotplug -Dest "$User@${HostName}:$dhcpHotplugTmp" -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
if (Test-Path $localNeighHotplug) {
Invoke-Scp -Local $localNeighHotplug -Dest "$User@${HostName}:$neighHotplugTmp" -Pass $Pass -HostKey $HostKey -Port $Port -Quiet:$Quiet -ForcePassword:$ForcePassword
}
# Push deploy helper if exists # Push deploy helper if exists
if (Test-Path $localDeploy) { if (Test-Path $localDeploy) {
@@ -103,6 +113,16 @@ if [ -f $initTmp ]; then
fi fi
"@ "@
}) })
if [ -f $dhcpHotplugTmp ]; then
sed -i "s/\r$//" $dhcpHotplugTmp 2>/dev/null || true
mkdir -p /etc/hotplug.d/dhcp
cp -f $dhcpHotplugTmp /etc/hotplug.d/dhcp/95-wakey
fi
if [ -f $neighHotplugTmp ]; then
sed -i "s/\r$//" $neighHotplugTmp 2>/dev/null || true
mkdir -p /etc/hotplug.d/neigh
cp -f $neighHotplugTmp /etc/hotplug.d/neigh/95-wakey
fi
"@ "@
$remoteCmd = "sh -lc '$($script -replace "`r",'')'" $remoteCmd = "sh -lc '$($script -replace "`r",'')'"
if ($Quiet) { $remoteCmd += " >/dev/null 2>&1" } if ($Quiet) { $remoteCmd += " >/dev/null 2>&1" }
+17
View File
@@ -0,0 +1,17 @@
#!/bin/sh
BIN="/root/.bin/wakey-agent"
[ -x "$BIN" ] || BIN="$(command -v wakey-agent 2>/dev/null)"
[ -n "$BIN" ] || exit 0
[ -n "$ACTION" ] || exit 0
[ -n "$MACADDR" ] || exit 0
if [ -n "$IPADDR" ] && [ -n "$HOSTNAME" ]; then
"$BIN" observe dhcp --action "$ACTION" --mac "$MACADDR" --ip "$IPADDR" --hostname "$HOSTNAME" >/dev/null 2>&1 || true
elif [ -n "$IPADDR" ]; then
"$BIN" observe dhcp --action "$ACTION" --mac "$MACADDR" --ip "$IPADDR" >/dev/null 2>&1 || true
elif [ -n "$HOSTNAME" ]; then
"$BIN" observe dhcp --action "$ACTION" --mac "$MACADDR" --hostname "$HOSTNAME" >/dev/null 2>&1 || true
else
"$BIN" observe dhcp --action "$ACTION" --mac "$MACADDR" >/dev/null 2>&1 || true
fi
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
BIN="/root/.bin/wakey-agent"
[ -x "$BIN" ] || BIN="$(command -v wakey-agent 2>/dev/null)"
[ -n "$BIN" ] || exit 0
[ -n "$ACTION" ] || exit 0
if [ -n "$MACADDR" ] && [ -n "$IPADDR" ]; then
"$BIN" observe neigh --action "$ACTION" --mac "$MACADDR" --ip "$IPADDR" >/dev/null 2>&1 || true
elif [ -n "$MACADDR" ]; then
"$BIN" observe neigh --action "$ACTION" --mac "$MACADDR" >/dev/null 2>&1 || true
elif [ -n "$IPADDR" ]; then
"$BIN" observe neigh --action "$ACTION" --ip "$IPADDR" >/dev/null 2>&1 || true
else
"$BIN" observe neigh --action "$ACTION" >/dev/null 2>&1 || true
fi
+15
View File
@@ -71,6 +71,21 @@ Get-ChildItem (Join-Path $root 'scripts/init/openwrt') -File | ForEach-Object {
Set-Content -NoNewline -LiteralPath $dest -Value $content -Encoding UTF8 Set-Content -NoNewline -LiteralPath $dest -Value $content -Encoding UTF8
} }
# Copy OpenWrt hotplug hooks.
$hotplugSrc = Join-Path $root 'scripts/hotplug/openwrt'
if (Test-Path $hotplugSrc) {
Get-ChildItem $hotplugSrc -Directory | ForEach-Object {
$hotplugTypeDir = Join-Path $staging ("etc/hotplug.d/" + $_.Name)
New-Item -ItemType Directory -Force -Path $hotplugTypeDir | Out-Null
Get-ChildItem $_.FullName -File | ForEach-Object {
$dest = Join-Path $hotplugTypeDir $_.Name
$content = Get-Content -Raw -LiteralPath $_.FullName
$content = $content -replace "`r`n", "`n"
Set-Content -NoNewline -LiteralPath $dest -Value $content -Encoding UTF8
}
}
}
# Copy helper scripts intended for /etc/ldlda_help # Copy helper scripts intended for /etc/ldlda_help
$helpSrc = Join-Path $root 'scripts/ldlda_help' $helpSrc = Join-Path $root 'scripts/ldlda_help'
if (Test-Path $helpSrc) { if (Test-Path $helpSrc) {
+55
View File
@@ -33,6 +33,8 @@ pub enum Command {
Wake(WakeArgs), Wake(WakeArgs),
/// Show condensed network interface summaries. /// Show condensed network interface summaries.
Devs(DevsArgs), Devs(DevsArgs),
/// Record local hotplug observations.
Observe(ObserveArgs),
} }
#[derive(Args)] #[derive(Args)]
@@ -116,6 +118,42 @@ pub struct DevsArgs {
pub json: bool, pub json: bool,
} }
#[derive(Args)]
pub struct ObserveArgs {
#[command(subcommand)]
pub command: ObserveCommand,
}
#[derive(Subcommand)]
pub enum ObserveCommand {
/// Observe a dnsmasq DHCP lease event.
Dhcp(ObserveDhcpArgs),
/// Observe a neighbor-table event.
Neigh(ObserveNeighArgs),
}
#[derive(Args)]
pub struct ObserveDhcpArgs {
#[arg(long)]
pub action: String,
#[arg(long)]
pub mac: macaddr::MacAddr,
#[arg(long)]
pub ip: Option<IpAddr>,
#[arg(long)]
pub hostname: Option<String>,
}
#[derive(Args)]
pub struct ObserveNeighArgs {
#[arg(long)]
pub action: String,
#[arg(long)]
pub mac: Option<macaddr::MacAddr>,
#[arg(long)]
pub ip: Option<IpAddr>,
}
pub fn init_tracing(verbose: u8) { pub fn init_tracing(verbose: u8) {
let filter = EnvFilter::try_from_default_env() let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new(default_filter_for_verbosity(verbose))) .or_else(|_| EnvFilter::try_new(default_filter_for_verbosity(verbose)))
@@ -206,6 +244,23 @@ pub async fn run(cli: Cli) -> Result<()> {
println!("{}", table::render_devs_table(&devs)); println!("{}", table::render_devs_table(&devs));
} }
} }
Command::Observe(args) => match args.command {
ObserveCommand::Dhcp(args) => {
let changed = wakey::observe_dhcp_event(
&args.action,
args.mac,
args.ip,
args.hostname.as_deref(),
)
.await?;
println!("observed=dhcp changed={changed}");
}
ObserveCommand::Neigh(args) => {
let changed =
wakey::observe_neighbor_event(&args.action, args.mac, args.ip).await?;
println!("observed=neigh changed={changed}");
}
},
} }
Ok(()) Ok(())
+1
View File
@@ -9,6 +9,7 @@ pub use service::{
inventory, leases_without_state, merge_devices, resolve_devices, resolve_query, inventory, leases_without_state, merge_devices, resolve_devices, resolve_query,
resolve_selector, resolve_wake_targets, wake_explicit, wake_from_query, wake_targets, resolve_selector, resolve_wake_targets, wake_explicit, wake_from_query, wake_targets,
}; };
pub use wakey_linux::dhcp::{observe_dhcp_event, observe_neighbor_event};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
+38
View File
@@ -28,6 +28,8 @@ pub enum Command {
InitConfig(InitConfigArgs), InitConfig(InitConfigArgs),
/// Reload a running agent daemon by sending SIGHUP. /// Reload a running agent daemon by sending SIGHUP.
Reload(ReloadArgs), Reload(ReloadArgs),
/// Pass local hotplug observations through to the wakey CLI.
Observe(ObserveArgs),
} }
#[derive(Args)] #[derive(Args)]
@@ -101,3 +103,39 @@ pub struct ReloadArgs {
#[arg(long, default_value = DEFAULT_PID_FILE)] #[arg(long, default_value = DEFAULT_PID_FILE)]
pub pid_file: PathBuf, pub pid_file: PathBuf,
} }
#[derive(Args)]
pub struct ObserveArgs {
#[command(subcommand)]
pub command: ObserveCommand,
}
#[derive(Subcommand)]
pub enum ObserveCommand {
/// Observe a dnsmasq DHCP lease event.
Dhcp(ObserveDhcpArgs),
/// Observe a neighbor-table event.
Neigh(ObserveNeighArgs),
}
#[derive(Args)]
pub struct ObserveDhcpArgs {
#[arg(long)]
pub action: String,
#[arg(long)]
pub mac: String,
#[arg(long)]
pub ip: Option<String>,
#[arg(long)]
pub hostname: Option<String>,
}
#[derive(Args)]
pub struct ObserveNeighArgs {
#[arg(long)]
pub action: String,
#[arg(long)]
pub mac: Option<String>,
#[arg(long)]
pub ip: Option<String>,
}
+56 -1
View File
@@ -9,7 +9,7 @@ mod tracing;
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::Parser;
use cli::{Cli, Command, InitConfigArgs}; use cli::{Cli, Command, InitConfigArgs, ObserveCommand};
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@@ -86,11 +86,66 @@ async fn main() -> Result<()> {
::tracing::info!(pid_file = %args.pid_file.display(), "wakey-agent command: reload"); ::tracing::info!(pid_file = %args.pid_file.display(), "wakey-agent command: reload");
serve::reload_daemon(&args.pid_file)? serve::reload_daemon(&args.pid_file)?
} }
Command::Observe(args) => observe(args)?,
} }
Ok(()) Ok(())
} }
fn observe(args: cli::ObserveArgs) -> Result<()> {
let mut cmd = std::process::Command::new(resolve_wakey_binary());
cmd.arg("observe");
match args.command {
ObserveCommand::Dhcp(args) => {
cmd.arg("dhcp")
.arg("--action")
.arg(args.action)
.arg("--mac")
.arg(args.mac);
if let Some(ip) = args.ip {
cmd.arg("--ip").arg(ip);
}
if let Some(hostname) = args.hostname {
cmd.arg("--hostname").arg(hostname);
}
}
ObserveCommand::Neigh(args) => {
cmd.arg("neigh").arg("--action").arg(args.action);
if let Some(mac) = args.mac {
cmd.arg("--mac").arg(mac);
}
if let Some(ip) = args.ip {
cmd.arg("--ip").arg(ip);
}
}
}
let status = cmd.status()?;
if !status.success() {
anyhow::bail!("wakey observe exited with {status}");
}
Ok(())
}
fn resolve_wakey_binary() -> std::path::PathBuf {
if let Ok(current) = std::env::current_exe()
&& let Some(dir) = current.parent()
{
let sibling = dir.join("wakey");
if sibling.exists() {
return sibling;
}
}
let root_bin = std::path::PathBuf::from("/root/.bin/wakey");
if root_bin.exists() {
return root_bin;
}
"wakey".into()
}
fn init_config(args: InitConfigArgs) -> Result<()> { fn init_config(args: InitConfigArgs) -> Result<()> {
if args.stdout && args.config.is_some() { if args.stdout && args.config.is_some() {
anyhow::bail!("--stdout cannot be used with --config"); anyhow::bail!("--stdout cannot be used with --config");
+40
View File
@@ -1,6 +1,7 @@
use std::io::{self, ErrorKind}; use std::io::{self, ErrorKind};
use std::net::IpAddr; use std::net::IpAddr;
use macaddr::MacAddr;
use wakey_core::{DhcpLease, DhcpLeaseWithState}; use wakey_core::{DhcpLease, DhcpLeaseWithState};
const MAC_NAME_CACHE: &str = "/tmp/wakey_mac_names.json"; const MAC_NAME_CACHE: &str = "/tmp/wakey_mac_names.json";
@@ -21,6 +22,45 @@ async fn save_mac_name_cache(map: &std::collections::BTreeMap<String, String>) -
Ok(()) Ok(())
} }
/// Observe a DHCP hotplug event and update the local MAC-to-name cache.
pub async fn observe_dhcp_event(
action: &str,
mac: MacAddr,
_ip: Option<IpAddr>,
hostname: Option<&str>,
) -> io::Result<bool> {
if !matches!(action, "add" | "update" | "old") {
return Ok(false);
}
let Some(hostname) = hostname
.map(str::trim)
.filter(|v| !v.is_empty() && *v != "*")
else {
return Ok(false);
};
let mut cache = load_mac_name_cache().await.unwrap_or_default();
let mac_s = mac.to_string();
if cache.get(&mac_s).map(|v| v == hostname).unwrap_or(false) {
return Ok(false);
}
cache.insert(mac_s, hostname.to_string());
save_mac_name_cache(&cache).await?;
Ok(true)
}
/// Observe a neighbor hotplug event. This is currently a no-op placeholder for
/// keeping DHCP and neighbor hook commands symmetrical.
pub async fn observe_neighbor_event(
_action: &str,
_mac: Option<MacAddr>,
_ip: Option<IpAddr>,
) -> io::Result<bool> {
Ok(false)
}
/// Parse one `dnsmasq`-style DHCP lease line. /// Parse one `dnsmasq`-style DHCP lease line.
pub fn parse_dhcp_lease_line(line: &str) -> Option<DhcpLease> { pub fn parse_dhcp_lease_line(line: &str) -> Option<DhcpLease> {
let mut c = line.split_whitespace(); let mut c = line.split_whitespace();