# 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 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 `` 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 '' │ │ │ │ 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` → `` 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