- banner box width adapts to longest value (fixes overflow with long paths)
- hoist h() HTML escape function to script top, remove 3 local copies
- serve_ca: add Cache-Control: public, max-age=86400
- restore log path in dashboard footer alongside new config/data fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SRV record uses first service's port (was 0, confused dns-sd -L)
- Remove examples/mdns_coexist.rs (served its purpose as spike)
- Reject percent-encoding in route paths (defense-in-depth)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add lan_enabled to ServerCtx
- Add lan field to /stats API (enabled, peer count)
- Dashboard shows "LAN off" (dim) or "LAN on · N peers" (green)
- Tooltip shows enable command or mDNS service type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- numa lan on/off toggles LAN discovery in numa.toml
- Writes [lan] section if missing, updates enabled if present
- Colored output with restart hint
- README: add lan on/off to help text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add inline route management (+ route / x) per service in dashboard
- Expose service source (config vs api) in API response
- Only show service delete button for API-created services
- Pre-fill route port with service target_port
- Fix XSS in route path onclick handlers
- Skip renderServices refresh while route form is open (editingRoute guard)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- LAN discovery disabled by default (opt-in via [lan] enabled = true)
- Replace custom JSON multicast (239.255.70.78:5390) with standard mDNS
(_numa._tcp.local on 224.0.0.251:5353) using existing DNS parser
- Instance ID in TXT record for multi-instance self-filtering
- API and proxy bind to 127.0.0.1 by default (0.0.0.0 when LAN enabled)
- Path-based routing: longest prefix match with optional prefix stripping
via [[services]] routes = [{path, port, strip?}]
- REST API: GET/POST/DELETE /services/{name}/routes
- Dashboard shows route lines per service when configured
- Segment-boundary route matching (prevents /api matching /apiary)
- Route path validation (rejects path traversal)
Closes#11
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LAN IP checked every 5s (cheap UDP socket call). Full upstream
re-detection runs every 30s as safety net, or immediately when
LAN IP changes. Reduces worst-case network switch recovery from
30s to 5s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose current upstream address in /stats response. Dashboard footer
now shows "Upstream: x.x.x.x:53" — updates live when the network
watcher swaps the upstream.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When numa install is active, scutil --dns only returns 127.0.0.1.
Previously fell back to 9.9.9.9 (Quad9) which fails on networks
that block external DNS. Now reads DHCP-provided DNS from
ipconfig getpacket en0/en1 as intermediate fallback before Quad9.
Tested on a network that blocks 8.8.8.8, 9.9.9.9, 1.1.1.1 but
allows ISP DNS (213.154.124.25) — Numa now auto-detects and uses it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Always detect network changes (LAN IP, upstream, peers) regardless
of upstream config. LAN IP is now tracked in ServerCtx and updated
every 30s — multicast announcements use the current IP instead of
the startup IP. Upstream re-detection still only runs when
auto-detected. Peer flush triggers on any network change.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Upstream DNS was resolved once at startup and never updated. Switching
Wi-Fi networks made all queries fail until restart.
Now spawns a background task (every 30s) that re-runs system DNS
discovery and swaps the upstream atomically if it changed. Also flushes
stale LAN peers from the old network on change.
Only activates when upstream is auto-detected (not explicitly configured).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Windows, the not(macos/linux) cfg block is the only path, so
clippy flags the return as needless. Use expression form instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gate version detection behind cfg(unix), fix unreachable Ok(()) after
return in trust_ca, use next_back() and is_some_and() per clippy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cross-platform paths: config_dir() uses %APPDATA%, data_dir() uses
%PROGRAMDATA% on Windows. TLS cert directory uses data_dir() instead
of hardcoded /usr/local/var/numa. Windows DNS discovery via ipconfig.
Fixed cfg gates from not(macos) to explicit linux to prevent Linux
code compiling on Windows. Added Windows target to CI and release
workflows with zip packaging.
System integration (numa install/service) not yet supported on Windows
— users run numa.exe manually.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorder scenes to show services first (matching panel order),
scroll to blocking panel for domain check scene. LAN badge
now visible after adding a service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add LAN discovery section to README with mesh and hub mode docs.
Update comparison table and roadmap. Move Local Services panel
above Blocking in dashboard for developer-first layout.
Bump version from 0.1.0 to 0.3.0 to match release cadence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show whether each service is reachable from the network or bound to
localhost only. Dashboard displays green "LAN" or amber "local only"
badge next to each healthy service. Unified TCP check function,
concurrent health+LAN probes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace IP-based self-announcement filtering with a per-process instance
ID (pid ^ timestamp) so multiple instances on the same host can discover
each other. Enable SO_REUSEPORT for multicast socket binding on Unix.
Add multicast address validation on configured group.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Numa instances on the same network auto-discover each other's .numa
services. No config, no cloud — just multicast on 239.255.70.78:5390.
- PeerStore with lazy expiry (90s timeout, 30s broadcast interval)
- DNS resolves remote .numa services to peer's LAN IP (not localhost)
- Proxy forwards to peer IP for remote services
- Graceful degradation if multicast bind fails
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
musl.cc was unreachable from CI. cross handles the Docker-based
cross-compilation automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
glibc-linked binaries fail on older distros (GLIBC_2.38 not found).
musl produces fully static binaries that work on any Linux.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dead code — certs are generated at startup, not loaded from PEM files.
Removes RUSTSEC-2025-0134 warning. Audit now passes clean.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RUSTSEC-2026-0049 fixed by updating rustls-webpki 0.103.9 → 0.103.10.
RUSTSEC-2025-0134 (rustls-pemfile unmaintained) ignored — no replacement
available, warning only, not a vulnerability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Roman Stone themed 404 with Instrument Serif heading, JetBrains Mono
domain badge, brick pattern background, syntax-highlighted curl
example, and a delayed easter egg. Also updates dashboard link in
README to numa.numa.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Docs contain internal planning (token economics, growth plan) that
should not be public. Keep locally via .gitignore.
Added macOS ad-hoc codesign step to install.sh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Moved "from scratch in Rust" into hero, added AdGuard Home to
comparison, named Hagezi Pro blocklist, cut 40% (API table + config
to docs), install script first in Quick Start, added Linux mention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>