Files
luci-app-zt-gateway/.omp/plans/add-setup-screen.md
Malar Invention 1e4a46c4bf setup wizard: fix routing, DHCP, WiFi AP, firewall, and deploy task
Setup script fixes:
- _cidr_to_mask: pad to 4 octets (/24 -> 255.255.255.0)
- UCI quoting: remove embedded shell quotes from uci set calls
- Bridge ports: auto-detect zt* interface instead of hardcoding ztabc0
- Bridge netmask: default to /23 (255.255.254.0) for ZT+WIBLAN
- DHCP/WiFi AP: reference interface name (zt_wiblan) not device name (br_zt)
- Firewall zone: add zt_wiblan to LAN zone for nftables fw4
- ZT IP persistence: ensure ZT-assigned IP stays on interface for ARP
- Exit gateway routing: table 100/101 route via exit gateway, not self
- New setup-wifi-ap subcommand for WIBLAN WiFi AP

UBUS handler:
- Add setup-wifi-ap to validation regex and error message

Deploy task:
- Auto-discover files from root/ and htdocs/ instead of hardcoded list
- Clear LuCI cache before restarting services

Documentation:
- New docs/SETUP-GATEWAY.md with architecture, config, pitfalls, checklist
- Updated docs/INSTALL.md with deploy task and setup wizard sections
- Updated docs/PROGRESS.md with session log and learnings
2026-07-13 11:11:19 +05:30

280 lines
13 KiB
Markdown

# Plan: One-Time Setup Screen for luci-app-zt-gateway
## Problem
When `luci-app-zt-gateway` is installed on a fresh OpenWrt router, the switch script fails with `Cannot find device "br-zt"` because the prerequisite networking (bridge, routing, hotplug, DHCP) doesn't exist yet. There's no guided setup — the user must manually configure everything via CLI.
## Goal
Add a **Setup** section to the overview UI that:
1. Detects what's already configured vs. what's missing
2. Lets the user run each setup step individually or all at once
3. Shows real-time status after each step
4. For WiFi AP, displays the UCI config to add (user confirms)
## Production Router State (reference)
The router at `root@10.11.12.254` shows the target state:
| Component | UCI/Config | Runtime |
|---|---|---|
| br-zt bridge | `config device type bridge name br-zt` | `ip link show br-zt` |
| brzt interface | `config interface brzt proto static device br-zt ipaddr 10.11.12.254` | `ip addr show br-zt` |
| ZT interface | `config interface zerotier proto none device ztk4jpk77j` | `ip link show ztk4jpk77j` |
| ZT enslavement | hotplug script | `brctl show br-zt` |
| Policy rule | `config rule src 10.11.13.0/24 lookup 100 priority 100` | `ip rule show` |
| Table 100 default | `config route interface brzt target 0.0.0.0 gateway 10.11.12.3 table 100` | `ip route show table 100` |
| Host route | `config route interface brzt target 10.11.12.3` | `ip route show` |
| WIBLAN return | `config route interface brzt target 10.11.13.0 table 100` | — |
| DHCP | `config dhcp brzt interface brzt start 257 limit 254` | — |
| WIBLAN AP | `config wifi-iface wifinet0 device radio0 ssid WIBLAN network brzt` | — |
| IP forwarding | sysctl | `cat /proc/sys/net/ipv4/ip_forward` → 1 |
| Hotplug | `/etc/hotplug.d/net/99-zerotier-bridge` | — |
| rc.local | routes in `/etc/rc.local` | — |
## Architecture
### 1. Setup Script: `root/usr/sbin/zt-gateway-setup`
A new POSIX shell script with subcommands. Idempotent — running any step twice is safe.
```
Usage: zt-gateway-setup <command>
Commands:
status Show setup status for all components
setup-bridge Create br-zt bridge and detect ZT interface
setup-routing Configure ip rules, table 100, ip forwarding
setup-hotplug Write /etc/hotplug.d/net/99-zerotier-bridge
setup-persistence Write rc.local + UCI network routes
setup-dhcp Configure DHCP for WIBLAN subnet
setup-all Run all setup steps in order
```
**Environment variables** (same pattern as `zt-gateway-switch`):
- `ZTG_BRIDGE` (default: `br-zt`)
- `ZTG_WIBLAN_CIDR` (default: `10.11.13.0/24`)
- `ZTG_TABLE_MAIN` (default: `100`)
- `ZTG_ZT_NETWORK_ID` (default: auto-detect from `zerotier-cli listnetworks`)
**`status` output** (JSON, for ubus consumption):
```json
{
"zt_client": true,
"zt_network_joined": true,
"zt_interface": "ztk4jpk77j",
"bridge_exists": true,
"bridge_has_zt": true,
"bridge_has_address": true,
"routing_ready": true,
"ip_forwarding": true,
"hotplug_script": true,
"persistence": true,
"dhcp_configured": true
}
```
**Key implementation details:**
#### `setup-bridge`
1. Find ZT interface: `zerotier-cli listnetworks` → parse `<dev>` column
2. If no ZT interface found → error "ZeroTier not joined to any network"
3. Create UCI device: `uci set network.zt_bridge=bridge; uci set network.zt_bridge.name=$BRIDGE; uci set network.zt_bridge.bridge_empty=1`
4. Create UCI interface: `uci set network.brzt=interface; uci set network.brzt.proto=static; uci set network.brzt.device=$BRIDGE`
5. Set IP from UCI config (read `global` section for WIBLAN CIDR, derive gateway IP)
6. Create UCI zerotier interface: `uci set network.zerotier=interface; uci set network.zerotier.proto=none; uci set network.zerotier.device=$ZT_IFACE`
7. `uci commit network`
8. Bring up: `ifup brzt; ifup zerotier`
9. Runtime fallback: if `ifup` doesn't enslave, do it manually:
```
ip link add name $BRIDGE type bridge 2>/dev/null || true
ip link set $BRIDGE up
ip link set $ZT_IFACE master $BRIDGE
```
#### `setup-routing`
1. `echo 1 > /proc/sys/net/ipv4/ip_forward`
2. Persist: add `net.ipv4.ip_forward=1` to `/etc/sysctl.d/99-zt-gateway.conf` + `sysctl -p`
3. `ip rule del from $WIBLAN_CIDR table $TABLE_MAIN 2>/dev/null || true`
4. `ip rule add from $WIBLAN_CIDR table $TABLE_MAIN priority 100`
5. Read `active_gateway` from UCI → get IP from gateway config
6. `ip route replace default via $GW_IP dev $BRIDGE table $TABLE_MAIN`
7. `ip route replace $GW_IP dev $BRIDGE` (host route)
8. `ip route replace $WIBLAN_CIDR dev $BRIDGE table 1` (MWAN return)
#### `setup-hotplug`
Write `/etc/hotplug.d/net/99-zerotier-bridge`:
```sh
#!/bin/sh
# Auto-generated by zt-gateway-setup — do not edit manually.
# Re-enslaves ZT interface to br-zt and restores routes on reconnect.
[ "$INTERFACE" = "$ZT_IFACE" ] && [ "$ACTION" = "add" ] && {
ip link set $ZT_IFACE master $BRIDGE 2>/dev/null || brctl addif $BRIDGE $ZT_IFACE 2>/dev/null
ip route del $ZT_SUBNET dev $ZT_IFACE 2>/dev/null
ip route replace $WIBLAN_CIDR dev $BRIDGE table 1 2>/dev/null
ip route replace $GW_IP dev $BRIDGE 2>/dev/null
ip route replace default via $GW_IP dev $BRIDGE table $TABLE_MAIN 2>/dev/null
}
```
The IPs are hardcoded (matching how `persist_all` uses `sed` to update them on switch).
#### `setup-persistence`
Write rc.local entries and UCI network routes (already handled by `persist_all` in the switch script, but `setup-persistence` creates the initial entries).
#### `setup-dhcp`
Write to `/etc/config/dhcp` (NOT `/etc/config/network`):
```sh
uci set dhcp.brzt=dhcp
uci set dhcp.brzt.interface='brzt'
uci set dhcp.brzt.start=257
uci set dhcp.brzt.limit=254
uci set dhcp.brzt.leasetime='12h'
uci add_list dhcp.brzt.dhcp_option='3,$WIBLAN_GW' # gateway
uci add_list dhcp.brzt.dhcp_option='6,8.8.8.8,1.1.1.1' # DNS
uci commit dhcp
```
The gateway IP is the router's own IP on br-zt (read from UCI `network.brzt.ipaddr` after bridge setup).
### 2. Backend: `root/usr/share/rpcd/ucode/zt-gateway.uc`
Add a new `setup` ubus method:
```js
setup: {
args: {
action: 'string' // 'status' | 'setup-bridge' | 'setup-routing' | ...
},
call: function(req) {
const action = req.args?.action || 'status';
const result = system_output(`/usr/sbin/zt-gateway-setup ${shell_quote(action)}`);
// Parse JSON output, return structured result
}
}
```
Also update the ACL in `root/usr/share/rpcd/acl.d/luci-app-zt-gateway.json` to allow the `setup` method.
### 3. UI: `htdocs/luci-static/resources/view/zt-gateway/overview.js`
Add a **Setup** section to the overview page. When setup is incomplete, show it prominently at the top. When complete, collapse it or hide it.
#### Setup Panel Layout
```
┌─────────────────────────────────────────────────────────┐
│ Setup [Status] │
├─────────────────────────────────────────────────────────┤
│ │
│ ☑ ZeroTier client installed │
│ ☑ Network "whiteblossom" joined (ztk4jpk77j) │
│ │
│ ☐ Bridge (br-zt) [Setup] │
│ Creates br-zt and enslaves ZT interface │
│ │
│ ☐ Routing (ip rules + table 100) [Setup] │
│ Configures policy routing for WIBLAN traffic │
│ │
│ ☐ Hotplug script [Setup] │
│ Restores routes when ZT reconnects │
│ │
│ ☐ Boot persistence [Setup] │
│ Routes survive reboot │
│ │
│ ☐ DHCP for WIBLAN [Setup] │
│ Assigns IPs to WiFi clients (10.11.13.0/24) │
│ │
│ ───────────────────────────────────────────────────── │
│ WiFi AP (WIBLAN) [Guide] │
│ Add to /etc/config/wireless: │
│ ┌─────────────────────────────────────────────┐ │
│ │ config wifi-iface 'wifinetN' │ │
│ │ option device 'radio0' │ │
│ │ option mode 'ap' │ │
│ │ option ssid 'WIBLAN' │ │
│ │ option encryption 'psk2' │ │
│ │ option key '<your-password>' │ │
│ │ option network 'brzt' │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ [Run All Setup Steps] │
│ │
└─────────────────────────────────────────────────────────┘
```
#### UI Behavior
1. **On page load**: Call `zt-gateway.setup { action: 'status' }` to get current state
2. **Render checklist**: Each step shows ✓ (green) or ✗ (gray) with a [Setup] button
3. **[Setup] button**: Calls `zt-gateway.setup { action: 'setup-bridge' }` (etc.), then refreshes status
4. **[Run All]**: Calls `zt-gateway.setup { action: 'setup-all' }`, then refreshes status
5. **WiFi Guide**: Shows the UCI config snippet to add (read-only, with copy button)
6. **After all steps complete**: Collapse the setup section, show normal overview
#### Status Detection Logic (in `zt-gateway-setup status`)
| Check | How |
|---|---|
| ZT client installed | `command -v zerotier-cli` |
| ZT network joined | `zerotier-cli listnetworks 2>/dev/null \| grep -q OK` |
| ZT interface name | Parse `zerotier-cli listnetworks` → `<dev>` column |
| Bridge exists | `ip link show $BRIDGE 2>/dev/null` |
| Bridge has ZT | `brctl show $BRIDGE 2>/dev/null \| grep -q $ZT_IFACE` |
| Bridge has address | `ip -o -4 addr show dev $BRIDGE 2>/dev/null` |
| IP forwarding | `cat /proc/sys/net/ipv4/ip_forward` |
| Policy rule | `ip rule show \| grep "from $WIBLAN_CIDR.*lookup $TABLE_MAIN"` |
| Table 100 default | `ip route show table $TABLE_MAIN \| grep -q "default via"` |
| Hotplug script | `[ -x /etc/hotplug.d/net/99-zerotier-bridge ]` |
| Persistence | Check rc.local has routes |
| DHCP | `uci get dhcp.brzt.interface 2>/dev/null` |
### 4. Files to Create/Modify
| File | Action | Purpose |
|---|---|---|
| `root/usr/sbin/zt-gateway-setup` | **Create** | Setup script with subcommands |
| `root/usr/share/rpcd/ucode/zt-gateway.uc` | **Modify** | Add `setup` ubus method |
| `root/usr/share/rpcd/acl.d/luci-app-zt-gateway.json` | **Modify** | Add `setup` to ubus read/write ACL |
| `htdocs/luci-static/resources/view/zt-gateway/overview.js` | **Modify** | Add setup panel section |
| `root/etc/config/zt-gateway` | **Modify** | Add `wiblan_subnet` and `zt_network_id` options to global |
| `docs/INSTALL.md` | **Modify** | Document setup screen in installation guide |
### 5. UCI Config Additions
Add to `config global` in `root/etc/config/zt-gateway`:
```
option wiblan_subnet '10.11.13.0/24'
option wiblan_gateway '10.11.12.254'
option wiblan_dhcp_start '257'
option wiblan_dhcp_limit '254'
option zt_network_id ''
```
These allow the setup script and UI to derive all IP addresses from config rather than hardcoding.
### 6. Verification
1. **Deploy to router**: `HOST=root@10.11.12.254 mise run deploy:install`
2. **Open UI**: Navigate to Services > ZeroTier Gateway
3. **Setup panel**: Should show checklist with current state (most items ✓ since router is pre-configured)
4. **Fresh test**: Deploy to a clean OpenWrt container (`docker compose up openwrt-luci`), verify setup panel shows all ✗
5. **Run setup**: Click [Run All], verify all items turn ✓
6. **Switch test**: After setup, select a gateway and switch — should succeed
7. **Reboot test**: After setup, reboot — verify routes survive
### 7. Implementation Order
1. Create `zt-gateway-setup` script with `status` command only
2. Add `setup` ubus method to backend
3. Add ACL for `setup` method
4. Add setup panel to UI (read-only status display)
5. Implement `setup-bridge` in script
6. Implement `setup-routing` in script
7. Implement `setup-hotplug` in script
8. Implement `setup-persistence` in script
9. Implement `setup-dhcp` in script
10. Implement `setup-all` in script
11. Wire [Setup] buttons in UI to call backend
12. Add WiFi AP guide section
13. Update UCI config with new options
14. Update deploy task and docs
15. Test end-to-end