Add the appliance web UI look, interface names, MCP and REST API, local zone
Web UI: dark/light themes (appliance layout, sidebar, switch checkboxes), theme preference on the Advanced page, Changes page with the pending diff. Interface names: router.interfaces.<name>.mac, lans.<l>.deviceName and pppoe.vlanName; a rename is staged for the next reboot by router-apply. Remote access: an MCP server (POST /mcp) and an OpenAPI REST API (/api/v1, /api/openapi.json) with the same tokens, scopes, limits and audit log; tools to read status, settings, leases, logs (get_logs), and to edit and check the saved settings (never applied). Tokens on the API access page. Firewall: a reserved `local` zone (the router itself) as the destination of rules, so the web UI or SSH can be allowed from the WAN. PLAN.md lists what is still left. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -7,6 +7,29 @@ in
|
||||
options.router = {
|
||||
enable = mkEnableOption "the router role";
|
||||
|
||||
interfaces = mkOption {
|
||||
default = { };
|
||||
example = {
|
||||
wan.mac = "52:54:00:12:34:56";
|
||||
};
|
||||
description = ''
|
||||
Names for physical interfaces, keyed by the name to give: the network
|
||||
card with the given MAC address is called that from the next boot on
|
||||
(`wan.interface` and `lans.*.interface` use these names). Interfaces
|
||||
that are not listed keep the kernel's name. Names cannot change while
|
||||
the system runs, so a change only takes effect after a reboot.
|
||||
'';
|
||||
type = types.attrsOf (
|
||||
types.submodule {
|
||||
options.mac = mkOption {
|
||||
type = types.strMatching "([0-9a-f]{2}:){5}[0-9a-f]{2}";
|
||||
example = "52:54:00:12:34:56";
|
||||
description = "Permanent MAC address of the network card, in lower case.";
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
wan = {
|
||||
interface = mkOption {
|
||||
type = types.str;
|
||||
@@ -37,6 +60,12 @@ in
|
||||
default = null;
|
||||
description = "VLAN ID the ISP requires for PPPoE, or null for untagged.";
|
||||
};
|
||||
vlanName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "isp";
|
||||
description = "Name of the VLAN device of the PPPoE session; null: `<interface>.<vlan>`.";
|
||||
};
|
||||
username = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
@@ -117,6 +146,16 @@ in
|
||||
description = "802.1Q VLAN ID, or null for an untagged LAN on `interface`.";
|
||||
};
|
||||
|
||||
deviceName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "dmz";
|
||||
description = ''
|
||||
Name of the VLAN device (only with `vlan`); null: `<interface>.<vlan>`.
|
||||
Like `interfaces`, a changed name is safe only after a reboot.
|
||||
'';
|
||||
};
|
||||
|
||||
device = mkOption {
|
||||
type = types.str;
|
||||
readOnly = true;
|
||||
@@ -230,7 +269,12 @@ in
|
||||
};
|
||||
|
||||
config.device =
|
||||
if config.vlan == null then config.interface else "${config.interface}.${toString config.vlan}";
|
||||
if config.vlan == null then
|
||||
config.interface
|
||||
else if config.deviceName != null then
|
||||
config.deviceName
|
||||
else
|
||||
"${config.interface}.${toString config.vlan}";
|
||||
|
||||
# "invalid" is only ever seen when assertions.nix is already failing.
|
||||
config.ipv4.subnet =
|
||||
@@ -492,11 +536,13 @@ in
|
||||
description = ''
|
||||
Allow rules between zones, on top of the default (deny between zones).
|
||||
Rules are one-directional; return traffic is allowed by connection
|
||||
tracking. `to` must be the zone of a LAN or tunnel; `from` may also be
|
||||
the reserved zone `wan` (traffic arriving on the uplinks), which
|
||||
tracking. `to` must be the zone of a LAN or tunnel, or the reserved
|
||||
zone `local`, the router itself (SSH, the web UI, ...); `from` may also
|
||||
be the reserved zone `wan` (traffic arriving on the uplinks), which
|
||||
publishes services to the outside: such rules must name a
|
||||
`destination` and protocol (and ports for tcp/udp), so a typo cannot
|
||||
expose a whole zone. Address translation is separate (`firewall.nat`);
|
||||
`destination` (not for `local`) and protocol (and ports for tcp/udp),
|
||||
so a typo cannot expose a whole zone. A port forward on the same port
|
||||
takes the traffic first, so it never reaches `local`. Address translation is separate (`firewall.nat`);
|
||||
a port forward needs an allow rule as well, usually with `natOnly`.
|
||||
'';
|
||||
type = types.listOf (
|
||||
@@ -508,7 +554,7 @@ in
|
||||
};
|
||||
to = mkOption {
|
||||
type = types.str;
|
||||
description = "Destination zone.";
|
||||
description = "Destination zone, or `local` (the router itself).";
|
||||
};
|
||||
protocol = mkOption {
|
||||
type = types.enum [
|
||||
|
||||
Reference in New Issue
Block a user