452 lines
13 KiB
Markdown
452 lines
13 KiB
Markdown
# wakey
|
|
|
|
`wakey` is a Wake-on-LAN and LAN-observability tool for a Linux router.
|
|
|
|
It started as a small web UI running on-device. It is now being reshaped into a
|
|
service-first project with:
|
|
|
|
- a reusable core model
|
|
- a Linux/OpenWrt adapter layer
|
|
- a CLI for operators
|
|
- an outbound agent plus control-plane model
|
|
|
|
## What it does
|
|
|
|
`wakey` currently focuses on a small set of router-side jobs:
|
|
|
|
- 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
|
|
|
|
In practice that means commands like:
|
|
|
|
```sh
|
|
wakey inventory bedroom-pc
|
|
wakey leases --include-state
|
|
wakey devs
|
|
wakey wake bedroom-pc
|
|
wakey wake --mac aa:bb:cc:dd:ee:ff
|
|
```
|
|
|
|
(`inventory` is the canonical subcommand; `status` is a visible alias for the same behavior.)
|
|
|
|
## Architecture at a glance
|
|
|
|
- **On-router (local):** run `wakey` for `inventory`, `leases`, `devs`, and `wake` against live Linux neighbor/DHCP/interface data.
|
|
- **Fleet (remote):** `wakey-agent` on each router enrolls with `wakey-control-plane`, keeps an authenticated WebSocket, and executes relayed commands using the same service logic as the local CLI.
|
|
|
|
## 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 and operator CLI
|
|
- `wakey-agent`
|
|
- outbound router daemon, enrollment, websocket command execution
|
|
- `wakey-control-plane`
|
|
- enrollment endpoint, connected-agent registry, command relay API
|
|
- `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
|
|
- inventory, leases, interfaces, wake, and query resolution
|
|
|
|
The long-term direction is:
|
|
|
|
- keep the service layer stable
|
|
- keep the local CLI stable for operators
|
|
- run remote control via outbound agent + control-plane relay
|
|
|
|
## Future direction
|
|
|
|
The project now uses:
|
|
|
|
- `wakey` for local operator workflows and shared service behavior
|
|
- `wakey-agent` for outbound enrollment and websocket execution
|
|
- `wakey-control-plane` for enrollment, registry, and command relay
|
|
|
|
## Logging and troubleshooting
|
|
|
|
Both daemons emit structured tracing logs to stderr. If you are not seeing
|
|
enrollment/command activity, run with increased verbosity.
|
|
|
|
Control-plane also supports a config file at
|
|
`/etc/wakey-control-plane/config.toml` (override with `--config-file`) so you
|
|
can persist telemetry settings instead of passing flags.
|
|
|
|
You can scaffold this file with:
|
|
|
|
```sh
|
|
wakey-control-plane init-config
|
|
```
|
|
|
|
Or bootstrap once during serve (writes only if config is missing):
|
|
|
|
```sh
|
|
wakey-control-plane serve --bootstrap-config
|
|
```
|
|
|
|
Inspect current persisted state:
|
|
|
|
```sh
|
|
wakey-control-plane state-stats
|
|
```
|
|
|
|
List/revoke enroll tokens from CLI:
|
|
|
|
```sh
|
|
wakey-control-plane list-enroll-tokens
|
|
wakey-control-plane revoke-enroll-token --token enr-...
|
|
wakey-control-plane revoke-agent --agent-id agent-...
|
|
```
|
|
|
|
Machine-readable output is available:
|
|
|
|
```sh
|
|
wakey-control-plane list-enroll-tokens --json
|
|
wakey-control-plane state-stats --json
|
|
```
|
|
|
|
Example:
|
|
|
|
```toml
|
|
data_dir = "/var/lib/wakey-control-plane"
|
|
bind = "0.0.0.0:8080"
|
|
public_url = "https://cp.example.com"
|
|
state_file = "state.sqlite3"
|
|
pid_file = "wakey-control-plane.pid"
|
|
ui_dist_dir = "/opt/wakey/ui/dist"
|
|
command_timeout_ms = 30000
|
|
enroll_token_ttl_seconds = 86400
|
|
|
|
[telemetry]
|
|
otlp_endpoint = "http://127.0.0.1:4317"
|
|
service_name = "wakey-control-plane"
|
|
json_logs = false
|
|
```
|
|
|
|
If `telemetry.otlp_endpoint` is omitted, logs still work normally and only local
|
|
structured logs are emitted.
|
|
|
|
State is persisted in an embedded SQLite database (default
|
|
`/var/lib/wakey-control-plane/state.sqlite3`).
|
|
Relative paths in config are resolved under `data_dir`.
|
|
Legacy sled state can be imported explicitly:
|
|
|
|
```sh
|
|
wakey-control-plane migrate-sqlite-state --from /var/lib/wakey-control-plane/legacy.sqlite3 --to /var/lib/wakey-control-plane/state.sqlite3
|
|
```
|
|
|
|
Enroll tokens are now expiring and revocable. Issuance returns `expires_at_unix`.
|
|
Expired tokens are rejected on enroll and can be garbage-collected periodically
|
|
or on demand.
|
|
|
|
### Quick start
|
|
|
|
Control-plane:
|
|
|
|
```sh
|
|
wakey-control-plane -v serve --bind 0.0.0.0:8787 --public-url https://cp.example.com
|
|
```
|
|
|
|
Agent:
|
|
|
|
```sh
|
|
wakey-agent -v serve --config /etc/wakey-agent/config.toml
|
|
```
|
|
|
|
### Fine-grained log filters
|
|
|
|
Use `RUST_LOG` when you want to focus on websocket/API internals.
|
|
|
|
```sh
|
|
RUST_LOG=wakey_control_plane=debug,wakey_agent=debug wakey-control-plane serve
|
|
RUST_LOG=wakey_agent=debug wakey-agent serve
|
|
```
|
|
|
|
### What you should see
|
|
|
|
During registration/enroll:
|
|
|
|
- control-plane: `issued enroll token`, then `agent enrollment accepted`
|
|
- agent: `starting agent enrollment`, then `agent enrollment succeeded and config was written`
|
|
|
|
During live connectivity:
|
|
|
|
- control-plane: `agent websocket upgraded`, `agent authenticated`, `agent disconnected`
|
|
- agent: `connecting agent websocket`, `agent websocket dns resolved`, `agent websocket connected`, `agent websocket session authenticated`, `heartbeat sent` (debug)
|
|
|
|
During command relay:
|
|
|
|
- control-plane: `dispatching command to agent`
|
|
- agent: `received command from control-plane`, then `command execution completed` (or `command dispatch failed`)
|
|
- control-plane: `agent command completed` (or timeout/error warnings)
|
|
|
|
During daemon control/state operations:
|
|
|
|
- control-plane: `wrote control-plane pid file`, `saved control-plane store`, `reloaded control-plane store from disk`
|
|
- agent: `wrote wakey-agent pid file`, `sending wakey-agent reload signal`
|
|
|
|
Control-plane admin API includes token management endpoints:
|
|
|
|
- `POST /api/v1/control/enroll-token?ttl_seconds=<n>`
|
|
- `GET /api/v1/control/enroll-tokens`
|
|
- `DELETE /api/v1/control/enroll-tokens/{token}`
|
|
- `GET /api/v1/control/audit/events?agent_id=<id>&event_type=<type>&limit=<n>`
|
|
- `GET /api/v1/control/alerts?lookback_seconds=900`
|
|
- `GET /api/v1/control/alerts/history?since_unix=<ts>&limit=<n>`
|
|
- `GET /api/v1/control/alerts/ws` (websocket snapshots, recent transitions, and evaluator health)
|
|
- `GET /api/v1/control/terminals/events/ws` (websocket session-list invalidations)
|
|
- `DELETE /api/v1/control/agents/{agent_id}`
|
|
- `PATCH /api/v1/control/agents/{agent_id}/nickname`
|
|
|
|
Alert evaluation runs once per control plane every five seconds. The alerts GET
|
|
returns `503 alerts_unavailable` until a successful evaluation, after evaluation
|
|
failure, or when its snapshot is stale. The existing alerts stream also sends
|
|
`alerts_health` messages with `available` and an optional `reason`; the UI keeps
|
|
the last results visible with a stale warning until recovery. Custom alert-rule
|
|
query overrides are rejected with `400 unsupported_alert_override`.
|
|
|
|
Terminal session events carry only `sessions_changed` invalidations. Clients
|
|
refetch the session list after changes or reconnection and retain a 15-second
|
|
fallback refresh. Events do not grant session ownership; attachment still uses
|
|
the existing server-side locking and scoped credentials.
|
|
|
|
If commands still appear silent, verify both processes are running with `-v`
|
|
and that `RUST_LOG` is not overriding to a stricter level.
|
|
|
|
If websocket connect feels delayed, compare `dns_resolve_ms` and `ws_connect_ms`
|
|
from agent logs. Slow DNS is a common source of multi-second connection stalls
|
|
when using hostnames; using a stable IP or local host mapping can avoid this.
|
|
|
|
## Edge Exposure (Caddy + Cloudflare Access)
|
|
|
|
Control-plane is intended to run behind a reverse proxy with TLS termination.
|
|
Use Cloudflare Access to protect `/ui/*` and `/api/v1/control/*`, while keeping
|
|
agent enrollment and websocket endpoints reachable.
|
|
|
|
An example Caddy config is provided at:
|
|
|
|
- `deploy/control-plane.Caddyfile`
|
|
|
|
Expected exposure model:
|
|
|
|
- Public: `/healthz`, `/api/v1/agents/enroll`, `/api/v1/agent/ws`
|
|
- Private (Cloudflare Access): `/ui/*`, `/api/v1/control/*`
|
|
|
|
Control-plane routing is organized with the same boundary in code:
|
|
|
|
- public router: health, enroll, agent websocket
|
|
- control router: all `/api/v1/control/*` admin endpoints
|
|
|
|
This keeps edge policy and app routing aligned as features grow.
|
|
|
|
## UI (Initial Shell)
|
|
|
|
Control-plane serves the built Operator UI at `/ui/` from `ui_dist_dir`
|
|
(defaults to `ui/dist`). Configure this in
|
|
`/etc/wakey-control-plane/config.toml` or pass `--ui-dist-dir` to `serve`.
|
|
|
|
Build UI assets before starting control-plane:
|
|
|
|
```sh
|
|
cd ui
|
|
pnpm install
|
|
pnpm build
|
|
```
|
|
|
|
Then start control-plane and open `/ui/` on the same host/port.
|
|
|
|
For UI development with hot reload, run `pnpm --dir ui dev` and open
|
|
`http://127.0.0.1:5173/ui/` (Vite chooses another port if occupied). API requests
|
|
and terminal/event WebSockets proxy to `http://127.0.0.1:8080` by default.
|
|
Override the backend with `WAKEY_DEV_API_TARGET=http://127.0.0.1:18080 pnpm --dir ui dev`,
|
|
or set it in `ui/.env` using `ui/.env.example`. Restart Vite after changing it.
|
|
This setting is dev-only; built assets still use the serving CC's origin.
|
|
|
|
For the remote CC, first open an SSH tunnel:
|
|
|
|
```sh
|
|
ssh -N -L 127.0.0.1:18080:127.0.0.1:23501 cloud
|
|
```
|
|
|
|
Use `http://127.0.0.1:18080` as the dev target. This connects directly to CC
|
|
through SSH, avoiding Cloudflare Access browser redirects/cookie handling in the
|
|
proxy. The dev server binds to loopback; do not expose this authenticated tunnel
|
|
through a public Vite server. Changes made in the dev UI affect the selected
|
|
backend, including production if that is what you tunnel to. HTTPS targets retain
|
|
certificate verification; the proxy does not add Cloudflare credentials.
|
|
|
|
## VPS Deploy (Manual Updates)
|
|
|
|
Suggested layout on VPS:
|
|
|
|
- `/opt/wakey/bin/wakey-control-plane`
|
|
- `/opt/wakey/ui/dist/*`
|
|
- `/etc/wakey-control-plane/config.toml` with `ui_dist_dir = "/opt/wakey/ui/dist"`
|
|
|
|
Use the provided unit template:
|
|
|
|
- `deploy/systemd/wakey-cc.service`
|
|
|
|
Install on VPS:
|
|
|
|
```sh
|
|
sudo install -m 0644 deploy/systemd/wakey-cc.service /etc/systemd/system/wakey-cc.service
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now wakey-cc.service
|
|
```
|
|
|
|
Manual update helper for VPS:
|
|
|
|
```sh
|
|
chmod +x scripts/update_wakey_cc.sh
|
|
cd /opt/wakey
|
|
sudo -E ./scripts/update_wakey_cc.sh
|
|
|
|
# optional: pin target and/or version
|
|
WAKEY_CC_TARGET=x86_64-unknown-linux-gnu sudo -E ./scripts/update_wakey_cc.sh
|
|
WAKEY_CC_VERSION=v0.2.0 WAKEY_CC_TARGET=x86_64-unknown-linux-gnu sudo -E ./scripts/update_wakey_cc.sh
|
|
```
|
|
|
|
The update tarball is expected to contain:
|
|
|
|
- `bin/wakey-control-plane`
|
|
- `ui/dist/index.html` (plus `ui/dist/assets/*`)
|
|
|
|
## CLI
|
|
|
|
`wakey` is usable as a local/operator CLI.
|
|
|
|
### Inventory
|
|
|
|
Show merged device inventory rows using a free-form selector:
|
|
|
|
```sh
|
|
wakey inventory bedroom-pc
|
|
```
|
|
|
|
Or use explicit filters:
|
|
|
|
```sh
|
|
wakey inventory --dev br-lan --nud reachable
|
|
wakey inventory --mac aa:bb:cc:dd:ee:ff
|
|
wakey inventory --json
|
|
```
|
|
|
|
The `status` subcommand is an alias for `inventory` (same flags and output).
|
|
|
|
### 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
|
|
```
|
|
|
|
## 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 GitHub Actions (`.github/workflows/ci.yml`), the same Rust checks run on
|
|
`ubuntu-latest`, plus a separate **`ui`** job: `pnpm install --frozen-lockfile`
|
|
in `ui/`, then `typecheck`, `format:check`, and `vite build`.
|
|
|
|
Focused control-plane state tests:
|
|
|
|
```sh
|
|
cargo test -p wakey-control-plane state::store::tests::gc_removes_expired_tokens
|
|
cargo test -p wakey-control-plane state::store::tests::enroll_rejects_expired_token
|
|
cargo test -p wakey-control-plane state::store::tests::stats_counts_agents_and_expired_tokens
|
|
```
|
|
|
|
### 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
|