Automate TP-Link Archer A20 config via Playwright (static port forwards for Tailscale) #107

Open
opened 2026-05-24 20:08:45 +00:00 by coilysiren · 0 comments
Owner

Goal

Drive the TP-Link Archer A20 web admin UI (http://192.168.0.1) via Playwright so router config is declarative and re-applicable. The immediate use case is static UDP port forwards for Tailscale on each device, to survive the router's UPnP table being saturated by a LAN BitTorrent client (see #106 for the root cause).

Why this approach

  • TP-Link Archer A20 has no API, no Terraform provider, no native config-import path that's parseable. Playwright is the only realistic automation surface for this hardware.
  • This is a 'deployment of one' router (per AGENTS framing) - one Kai, one router, no external blast radius. Scraping the web UI is acceptable here in a way it wouldn't be for shared infra.
  • Alternative is replacing the router with OpenWRT/pfSense, which both have proper Terraform providers. That is the 'right' answer long-term but a heavier lift. Playwright unblocks today.

Scope - in

  1. Login to router admin (password via SSM, param to be created).
  2. Manage port forwards (Advanced -> NAT Forwarding -> Virtual Servers): declarative YAML of {device_name, internal_ip, external_port, internal_port, protocol}, idempotent apply.
  3. Manage DHCP reservations (Advanced -> Network -> DHCP Server -> Address Reservation): declarative {hostname, mac, ip}. Needed because static port forwards bind to internal IP, and devices need pinned IPs to be reliable targets.
  4. Verify-only / dry-run mode that reads current state and diffs against desired without applying.

Scope - out (for now)

  • Driving tailscale set --port / tailscaled --port on each device end-to-end. That belongs in a separate Ansible-style role per AGENTS. This ticket is router-config only.
  • Wireless settings, parental controls, QoS, anything not directly serving the port-forward goal.
  • Firmware updates (still manual; risk profile too different).

SSM dependencies

Needs a new SecureString param (Kai will add after this issue is firmed up):

  • /coilysiren/home/tplink-admin-password - admin password for the router web UI.

The LAN IP 192.168.0.1 is fine to hardcode (RFC1918, not opaque, no leak risk).

Implementation notes

  • Python + playwright (matches existing coilysiren/* tooling stack).
  • Headless by default; --headed flag for debugging the brittle parts.
  • TP-Link's UI has historically used a JS framework that injects CSRF tokens and rewrites form fields, so expect this to be fragile. Lean on stable accessibility selectors over CSS where possible.
  • The TP-Link Archer A20 login may have a max-attempts lockout - implement single-attempt-then-fail with clear errors rather than retry loops that could lock out Kai's only management path.
  • Store state under infrastructure/router-state.yaml or similar. Diff-first, apply-last. Audit log via coily.

Where it lives

Probably infrastructure/scripts/playwright-tplink/ since this repo is canonical homelab knowledge per AGENTS, and the router is homelab infra. Could also be a standalone repo if the surface grows beyond one router.

Origin

Spawned from today's mobile-SSH debug session (#106). Kai wants the router automation tracked separately so the SSH-debug issue can stay focused.

Filed by Claude.

**Goal** Drive the TP-Link Archer A20 web admin UI (http://192.168.0.1) via Playwright so router config is declarative and re-applicable. The immediate use case is **static UDP port forwards for Tailscale on each device**, to survive the router's UPnP table being saturated by a LAN BitTorrent client (see [#106](https://forgejo.coilysiren.me/coilysiren/infrastructure/issues/106) for the root cause). **Why this approach** - TP-Link Archer A20 has no API, no Terraform provider, no native config-import path that's parseable. Playwright is the only realistic automation surface for this hardware. - This is a 'deployment of one' router (per AGENTS framing) - one Kai, one router, no external blast radius. Scraping the web UI is acceptable here in a way it wouldn't be for shared infra. - Alternative is replacing the router with OpenWRT/pfSense, which both have proper Terraform providers. That is the 'right' answer long-term but a heavier lift. Playwright unblocks today. **Scope - in** 1. Login to router admin (password via SSM, param to be created). 2. Manage **port forwards** (Advanced -> NAT Forwarding -> Virtual Servers): declarative YAML of `{device_name, internal_ip, external_port, internal_port, protocol}`, idempotent apply. 3. Manage **DHCP reservations** (Advanced -> Network -> DHCP Server -> Address Reservation): declarative `{hostname, mac, ip}`. Needed because static port forwards bind to internal IP, and devices need pinned IPs to be reliable targets. 4. Verify-only / dry-run mode that reads current state and diffs against desired without applying. **Scope - out (for now)** - Driving `tailscale set --port` / `tailscaled --port` on each device end-to-end. That belongs in a separate Ansible-style role per AGENTS. This ticket is router-config only. - Wireless settings, parental controls, QoS, anything not directly serving the port-forward goal. - Firmware updates (still manual; risk profile too different). **SSM dependencies** Needs a new SecureString param (Kai will add after this issue is firmed up): - `/coilysiren/home/tplink-admin-password` - admin password for the router web UI. The LAN IP `192.168.0.1` is fine to hardcode (RFC1918, not opaque, no leak risk). **Implementation notes** - Python + `playwright` (matches existing `coilysiren/*` tooling stack). - Headless by default; `--headed` flag for debugging the brittle parts. - TP-Link's UI has historically used a JS framework that injects CSRF tokens and rewrites form fields, so expect this to be fragile. Lean on stable accessibility selectors over CSS where possible. - The TP-Link Archer A20 login may have a max-attempts lockout - implement single-attempt-then-fail with clear errors rather than retry loops that could lock out Kai's only management path. - Store state under `infrastructure/router-state.yaml` or similar. Diff-first, apply-last. Audit log via coily. **Where it lives** Probably `infrastructure/scripts/playwright-tplink/` since this repo is canonical homelab knowledge per AGENTS, and the router is homelab infra. Could also be a standalone repo if the surface grows beyond one router. **Origin** Spawned from today's mobile-SSH debug session ([#106](https://forgejo.coilysiren.me/coilysiren/infrastructure/issues/106)). Kai wants the router automation tracked separately so the SSH-debug issue can stay focused. **Filed by Claude.**
coilysiren added
P4
and removed
P3
labels 2026-05-31 07:00:41 +00:00
Sign in to join this conversation.
No labels
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/infrastructure#107
No description provided.