This commit is contained in:
Graham McIntire 2026-07-19 14:42:02 -05:00
parent ac6c03c715
commit d0d1667991
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
144 changed files with 25370 additions and 390 deletions

View file

@ -1,22 +0,0 @@
# Configuration for router (10.254.254.110)
# Exported on 2025-10-04 11:22:56
# IP Addresses
/ip address add address=10.254.254.110/32 interface=loopback
/ip address add address=10.250.1.33/29 interface=ether7-380
/ip address add address=10.10.63.254/20 interface=mgmt
/ip address add address=100.64.35.254/22 interface=982
/ip address add address=204.110.188.126/27 interface=982
# IP Pools
/ip pool add name=cgnat ranges=100.64.32.1-100.64.35.199
/ip pool add name=mgmt ranges=10.10.48.1-10.10.62.254
/ip pool add name=public ranges=204.110.189.1-204.110.189.29
# PPPoE Servers
/interface pppoe-server server add name=982 interface=982 default-profile=982
# PPP Profiles
/ppp profile add name=982 local-address=100.64.35.253 remote-address=cgnat
# NAT Rules

View file

@ -138,7 +138,7 @@ above the `fasttrack-connection` rule on both endpoints. Documented in
```
verona ──AF11── climax ──AF24── core ──AF11── culleoka
│ │ │
AF11 │ AF11 │ AF11 (DOWN: power injector unplugged)
5GHz │ AF11 │ AF11 (DOWN: power injector unplugged)
│ │ │
494 newhope ──AF24── lowry
@ -146,7 +146,7 @@ verona ──AF11── climax ──AF24── core ──AF11── culleoka
982
```
Wait — that diagram's links are: climax↔494 (AF11), core↔newhope (AF11),
Wait — that diagram's links are: climax↔494 (5 GHz airMAX, **not** AF11 — see below), core↔newhope (AF11),
core↔982 (60 GHz), newhope↔lowry (AF24). The climax↔culleoka direct AF11
is currently down at the radio (physical issue), so culleoka traffic
transits via core.
@ -179,7 +179,7 @@ fasttrack-bypass rules in both directions on both routers.
|--|--|--|--|--|--|
| verona↔climax | AF11 | verona `ether3-climax-11ghz` | climax `ether6-verona-11ghz` | 10.250.1.24/29 | 2024 |
| climax↔core | AF24 | climax `ether4-380-airfiber24` | core `ether5-climax` | 10.250.1.88/29 | 2024 |
| climax↔494 | AF11 | climax `ether5-494` | 494 `ether2-climax` | 10.250.1.64/29 | 1580 |
| climax↔494 | 5 GHz airMAX | climax `ether5-494` | 494 `ether2-climax` | 10.250.1.64/29 | 1580 |
| climax↔culleoka | AF11 | climax `ether3-culleoka-11ghz` | culleoka `ether1-climax-11ghz` | 10.250.1.8/29 | 2024 (link DOWN) |
| core↔culleoka | AF11 | core `ether6-culleoka-11ghz` | culleoka `ether6-380-11ghz` | 10.250.1.48/29 | 2024 |
| core↔newhope | AF11 | core `ether4-newhope` | newhope `ether2-380` | 10.250.1.56/29 | 9000 |
@ -194,12 +194,32 @@ sides symmetrically when changing l2mtu** — single-side raises usually work
because Ethernet receivers accept anything ≤ their cap, but symmetric is
the rule.
### climax↔494 backhaul is 5 GHz airMAX, not AF11
Old docs called this hop AF11; it is actually a Ubiquiti airMAX AC PtP
pair (SSID `vntx_pr_494`, WPA2, 40 MHz wide): AP **"Climax to 494"**
(PBE-5AC-500) at `10.250.1.69` on the climax side, station **"494 to
Climax"** (PBE-5AC-400) at `10.250.1.66` on the 494 side. Both answer
SNMP v1 (`kdyyJrT0Mm`, airMAX MIB `1.3.6.1.4.1.41112.1.4`); added to
`radios.yaml` 2026-07-17. The station's mgmt IP (and the whole 494
tower) is only reachable **across this RF link** — when changing the
channel, first make sure the station's frequency scan list covers the
target, then change the AP (climax side), which stays reachable for
rollback either way.
2026-07-17 flap incident: link ran on **5260 MHz (DFS)**; associations
dropped for 4090 s every 45130 min (OSPF 40 s dead-interval timeouts
on both routers; ethernet ports never dropped, radios never rebooted —
association-uptime SNMP counters matched each OSPF flap). Local 5 GHz
survey: climax APs on 5335/5545/5575 (airMAX) + 5750/5775 (ePMP), 494
ePMP omni on 5800 → U-NII-1 (51705250) was empty at both towers and
is non-DFS, so the fix was moving the PtP there.
### IGP / routing
- OSPFv2 area `backbone-v2` (id 0.0.0.0) on all spine links, SHA-512 auth
with `auth-id=1` and a shared key. PTP type. **BFD is off on every
wireless backbone interface (AF11/AF24/60GHz)** — the global timers
(`/routing/bfd/configuration` = 200ms×5 = 1s detection) are too
aggressive for AF RF: a single >1s burst tore down OSPF on
aggressive for backhaul RF: a single >1s burst tore down OSPF on
climax↔494 every ~1540 min until `use-bfd=false` was applied to both
ends 2026-05-09. Wired fiber links may keep BFD if desired. If
sub-second failover on a wireless link is genuinely needed, also
@ -241,6 +261,33 @@ without it, hotspot drops all `204.110.191.x` traffic in `hs-unauth-to`
chain with `icmp-host-prohibited`. Symptom is "I can reach verona but
nothing past it" from the home network.
### graham's home network — multi-WAN failover (recursive check-gateway)
Home (RB5009, `10.0.19.254`, **not** in `routers.yaml` — use a temp
config with the same default creds to reach it via mikrotik-tool) fails
over between TMO (distance 1), VNTX/verona (distance 2), and Starlink
(distance 3, disabled) using the standard ROS recursive pattern, applied
2026-07-17 via `mikrotik-tool/home-recursive-failover.rsc`:
- Probe pins: `4.2.2.1/32→192.168.12.1%ether6-tmobile`,
`4.2.2.2/32→204.110.191.30%ether5-vntx-static`,
`4.2.2.3/32→192.168.1.1%ether7-starlink`, all `scope=10`, **no
check-gateway on the pins** — they stay active whenever the interface
has link, gluing probes to their WAN regardless of route state.
- Defaults: `gateway=4.2.2.x target-scope=11 check-gateway=ping` — the
check pings the probe IP through the pin; ~20s to go inactive, first
success to return. `tmo table default` (eweka policy routing) has the
same check so it falls back to main when TMO upstream dies.
**Do not reintroduce netwatch enable/disable failover scripts.** The old
design (netwatch 2s/1s single-packet → `/ip route disable`) deadlocked:
`disabled=yes` persists across reboots, and the probe was only tied to
the VNTX path while its pin was active — so the verona hotspot-binding
loss (verona pingable, transit dead) left the route disabled forever,
and probes leaked out other WANs giving false "up" (TMO netwatch read up
with the TMO interface physically down). Same lesson as the fleet BFD
incident: 1-2s detection on AF RF with >1s bursts causes false failover;
1020s check-gateway damping is intentional.
### IPv6 plan
Per-tower /44s + central server LAN at `2606:1c80::/64` on edge. Full
allocation plan in `mikrotik-tool/ipv6.md`. NetBox has these as IPAM

View file

@ -0,0 +1,198 @@
spawn ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=8 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o HostKeyAlgorithms=+ssh-rsa -o KexAlgorithms=+diffie-hellman-group14-sha1 ubnt@10.250.1.66 cat /tmp/system.cfg
aaa.1.radius.acct.1.port=1813
aaa.1.radius.acct.1.status=disabled
aaa.1.radius.auth.1.port=1812
aaa.1.status=disabled
aaa.1.wpa.psk=0000:0000
aaa.status=disabled
airview.status=enabled
bridge.1.devname=br0
bridge.1.port.1.devname=eth0
bridge.1.port.1.status=enabled
bridge.1.port.2.devname=ath0
bridge.1.port.2.status=enabled
bridge.1.status=enabled
bridge.1.stp.status=disabled
bridge.status=enabled
discovery.beacon.status=enabled
discovery.cdp.status=disabled
discovery.lldp.status=enabled
discovery.status=enabled
ebtables.status=enabled
ebtables.sys.eap.1.devname=ath0
ebtables.sys.eap.1.status=enabled
ebtables.sys.eap.status=enabled
ebtables.sys.fw.status=disabled
ebtables.sys.fw6.status=disabled
ebtables.sys.status=enabled
gui.language=en_US
gui.network.advanced.status=disabled
httpd.https.port=443
httpd.https.status=enabled
httpd.port=80
httpd.session.timeout=900
httpd.status=enabled
igmpproxy.status=disabled
iptables.status=disabled
iptables.sys.portfw.status=disabled
iptables.sys.status=enabled
netconf.1.autoip.status=disabled
netconf.1.devname=ath0
netconf.1.mtu=1500
netconf.1.status=enabled
netconf.1.up=enabled
netconf.2.autoip.status=disabled
netconf.2.autoneg=enabled
netconf.2.devname=eth0
netconf.2.flowcontrol.rx.status=enabled
netconf.2.flowcontrol.tx.status=enabled
netconf.2.mtu=1500
netconf.2.status=enabled
netconf.2.up=enabled
netconf.3.autoip.status=disabled
netconf.3.devname=br0
netconf.3.ip6.status=enabled
netconf.3.ip=10.250.1.66
netconf.3.mtu=1500
netconf.3.netmask=255.255.255.248
netconf.3.role=mlan
netconf.3.status=enabled
netconf.status=enabled
netmode=bridge
ntpclient.1.server=0.us.pool.ntp.org
ntpclient.1.status=enabled
ntpclient.status=enabled
pppoe-relay.status=disabled
pwdog.delay=300
pwdog.period=300
pwdog.retry=3
pwdog.status=disabled
radio.1.ack.auto=enabled
radio.1.ackdistance=600
radio.1.acktimeout=35
radio.1.antenna.gain=25
radio.1.antenna.id=1
radio.1.atpc.sta.status=enabled
radio.1.atpc.status=disabled
radio.1.atpc.threshold=36
radio.1.cable.loss=0
radio.1.center.1.freq=5165
radio.1.chanbw=0
radio.1.cmsbias=0
radio.1.countrycode=840
radio.1.cwm.enable=0
radio.1.cwm.mode=1
radio.1.devname=ath0
radio.1.dfs.slave=1
radio.1.dfs.status=enabled
radio.1.ff_cap_rep=0
radio.1.freq=5165
radio.1.ieee_mode=auto
radio.1.low_txpower_mode=disabled
radio.1.mode=managed
radio.1.obey=enabled
radio.1.polling=enabled
radio.1.polling_ff_dl_ratio=50
radio.1.polling_ff_dur=0
radio.1.polling_ff_flex=0
radio.1.polling_ff_sta_rx_rssi_th=0
radio.1.pollingnoack=0
radio.1.pollingpri=2
radio.1.ptpmode=1
radio.1.rc_mode=0
radio.1.reg_obey=enabled
radio.1.rx_sensitivity=-96
radio.1.scan_list.status=disabled
radio.1.scanbw.status=disabled
radio.1.status=enabled
radio.1.txpower=20
radio.countrycode=840
radio.status=enabled
resolv.host.1.name=494 to Climax
resolv.host.1.status=enabled
resolv.nameserver.1.ip=204.110.191.240
resolv.nameserver.1.status=enabled
resolv.nameserver.2.ip=204.110.191.250
resolv.nameserver.2.status=enabled
resolv.nameserver.status=enabled
route.1.devname=br0
route.1.gateway=10.250.1.70
route.1.ip=0.0.0.0
route.1.netmask=0
route.1.status=enabled
route.status=enabled
snmp.community=kdyyJrT0Mm
snmp.contact=vntx
snmp.location=vntx
snmp.status=enabled
sshd.auth.passwd=enabled
sshd.port=22
sshd.status=enabled
syslog.remote.port=514
syslog.remote.status=disabled
syslog.status=enabled
system.airosx.prov.status=enabled
system.autosend_crash_reports=disabled
system.cfg.editor.webui=2026-06-27T20:28:48.251Z
system.cfg.version=65547
system.date.status=disabled
system.external.reset=enabled
system.height=0
system.latitude=33.1801
system.leds.nightmode=disabled
system.longitude=-96.498
system.poe_passthrough=disabled
system.timezone.name=America/Chicago
system.timezone=CST6CDT,M3.2.0,M11.1.0
telnetd.port=23
telnetd.status=disabled
tshaper.status=disabled
unms.status=enabled
unms.ui_url=https://uisp.vntx.net
unms.uri=wss://uisp.vntx.net:443+NHjWcWdwuHlhAfrBqATpt6_Tz3-Sv-xYka64WI4hBGcAAAAA+allowUntrustedCertificate
update.check.status=enabled
users.1.name=ubnt
users.1.password=$1$.vDPgZgX$bg0TprBhNiUYo4uDoMwvi1
users.1.status=enabled
users.2.gid=100
users.2.shell=/bin/false
users.2.status=disabled
users.2.uid=100
users.status=enabled
wireless.1.ampdu.frames=32
wireless.1.ampdu.status=enabled
wireless.1.amsdu=3
wireless.1.devname=ath0
wireless.1.hide_ssid=disabled
wireless.1.l2_isolation=disabled
wireless.1.mac_acl.policy=allow
wireless.1.mac_acl.status=disabled
wireless.1.mcast.enhance=0
wireless.1.rate.auto=enabled
wireless.1.rate.mcs=-1
wireless.1.security.type=none
wireless.1.signal_led1=94
wireless.1.signal_led2=80
wireless.1.signal_led3=73
wireless.1.signal_led4=65
wireless.1.signal_led_status=enabled
wireless.1.ssid=vntx_pr_494
wireless.1.status=enabled
wireless.1.wds.status=enabled
wireless.status=enabled
wpasupplicant.device.1.devname=ath0
wpasupplicant.device.1.profile=AUTO
wpasupplicant.device.1.status=enabled
wpasupplicant.profile.1.name=AUTO
wpasupplicant.profile.1.network.1.eap.1.status=disabled
wpasupplicant.profile.1.network.1.key_mgmt.1.name=WPA-PSK
wpasupplicant.profile.1.network.1.pairwise.1.name=CCMP
wpasupplicant.profile.1.network.1.priority=100
wpasupplicant.profile.1.network.1.proto.1.name=RSN
wpasupplicant.profile.1.network.1.psk=0000:0000
wpasupplicant.profile.1.network.1.ssid=vntx_pr_494
wpasupplicant.profile.1.network.1.status=enabled
wpasupplicant.profile.1.network.2.key_mgmt.1.name=NONE
wpasupplicant.profile.1.network.2.priority=2
wpasupplicant.profile.1.network.2.status=disabled
wpasupplicant.status=enabled

View file

@ -0,0 +1,204 @@
spawn ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=8 -o NumberOfPasswordPrompts=1 -o PreferredAuthentications=password -o HostKeyAlgorithms=+ssh-rsa -o KexAlgorithms=+diffie-hellman-group14-sha1 ubnt@10.250.1.69 cat /tmp/system.cfg
aaa.status=enabled
aaa.1.devname=ath0
aaa.1.radius.acct.1.port=1813
aaa.1.radius.acct.1.status=disabled
aaa.1.radius.auth.1.port=1812
aaa.1.radius.auth.1.status=disabled
aaa.1.ssid=vntx_pr_494
aaa.1.status=enabled
aaa.1.wpa.key.1.mgmt=WPA-PSK
aaa.1.wpa.mode=2
aaa.1.wpa.psk=0000:0000
aaa.1.wpa.1.pairwise=CCMP
airview.status=enabled
bridge.status=enabled
bridge.1.devname=br0
bridge.1.port.1.devname=eth0
bridge.1.port.1.status=enabled
bridge.1.port.2.devname=ath0
bridge.1.port.2.status=enabled
bridge.1.status=enabled
bridge.1.stp.status=disabled
discovery.beacon.status=enabled
discovery.cdp.status=enabled
discovery.lldp.status=enabled
discovery.status=enabled
ebtables.status=enabled
ebtables.sys.eap.status=enabled
ebtables.sys.eap.1.devname=ath0
ebtables.sys.eap.1.status=enabled
ebtables.sys.fw.status=disabled
ebtables.sys.fw6.status=disabled
ebtables.sys.status=enabled
gui.language=en_US
gui.network.advanced.status=disabled
httpd.https.port=443
httpd.https.status=enabled
httpd.https.tlsv1=enabled
httpd.port=80
httpd.session.timeout=900
httpd.status=enabled
igmpproxy.status=disabled
iptables.status=disabled
iptables.sys.portfw.status=disabled
iptables.sys.status=enabled
netconf.status=enabled
netconf.1.autoip.status=disabled
netconf.1.devname=ath0
netconf.1.mtu=1500
netconf.1.status=enabled
netconf.1.up=enabled
netconf.2.autoip.status=disabled
netconf.2.autoneg=enabled
netconf.2.devname=eth0
netconf.2.flowcontrol.rx.status=enabled
netconf.2.flowcontrol.tx.status=enabled
netconf.2.mtu=1500
netconf.2.status=enabled
netconf.2.up=enabled
netconf.3.autoip.status=disabled
netconf.3.devname=br0
netconf.3.ip=10.250.1.69
netconf.3.ip6.status=enabled
netconf.3.mtu=1500
netconf.3.netmask=255.255.255.248
netconf.3.role=mlan
netconf.3.status=enabled
netmode=bridge
ntpclient.status=enabled
ntpclient.1.server=0.us.pool.ntp.org
ntpclient.1.status=enabled
pppoe-relay.status=disabled
pwdog.delay=300
pwdog.period=300
pwdog.retry=3
pwdog.status=disabled
radio.countrycode=840
radio.status=enabled
radio.1.ack.auto=enabled
radio.1.ackdistance=600
radio.1.acktimeout=35
radio.1.antenna.gain=27
radio.1.antenna.id=1
radio.1.atpc.sta.status=enabled
radio.1.atpc.status=disabled
radio.1.atpc.threshold=36
radio.1.cable.loss=0
radio.1.center.1.freq=5270
radio.1.chanbw=40
radio.1.cmsbias=0
radio.1.countrycode=840
radio.1.cwm.enable=0
radio.1.cwm.mode=1
radio.1.devname=ath0
radio.1.dfs.status=enabled
radio.1.ff_cap_rep=0
radio.1.freq=5260
radio.1.ieee_mode=11acvht40
radio.1.low_txpower_mode=disabled
radio.1.mode=master
radio.1.obey=enabled
radio.1.polling=enabled
radio.1.polling_ff_dl_ratio=50
radio.1.polling_ff_dur=0
radio.1.polling_ff_flex=0
radio.1.polling_ff_sta_rx_rssi_th=0
radio.1.polling_11ac_11n_compat=0
radio.1.pollingnoack=0
radio.1.pollingpri=2
radio.1.ptpmode=1
radio.1.rc_mode=0
radio.1.reg_obey=disabled
radio.1.rx_sensitivity=-96
radio.1.scan_list.status=disabled
radio.1.scanbw.status=disabled
radio.1.status=enabled
radio.1.txpower=24
resolv.host.1.name=Climax to 494
resolv.host.1.status=enabled
resolv.nameserver.status=enabled
resolv.nameserver.1.ip=204.110.191.240
resolv.nameserver.1.status=enabled
resolv.nameserver.2.ip=204.110.191.250
resolv.nameserver.2.status=enabled
route.status=enabled
route.1.devname=br0
route.1.gateway=10.250.1.70
route.1.ip=0.0.0.0
route.1.netmask=0
route.1.status=enabled
snmp.community=kdyyJrT0Mm
snmp.contact=Verona Networks
snmp.location=vntx
snmp.status=enabled
sshd.auth.passwd=enabled
sshd.hostkey_dss=disabled
sshd.port=22
sshd.status=enabled
syslog.remote.port=514
syslog.remote.status=disabled
syslog.status=enabled
system.airosx.prov.status=enabled
system.autosend_crash_reports=disabled
system.cfg.editor.webui=2026-07-06T16:34:39.082Z
system.cfg.version=65547
system.date.status=disabled
system.external.reset=enabled
system.height=0
system.latitude=33.1801
system.leds.nightmode=disabled
system.longitude=-96.498
system.poe_passthrough=disabled
system.timezone=CST6CDT,M3.2.0,M11.1.0
system.timezone.name=America/Chicago
telnetd.port=23
telnetd.status=disabled
tshaper.status=disabled
unms.status=enabled
unms.ui_url=https://uisp.vntx.net
unms.uri=wss://uisp.vntx.net:443+xz9wxAnRIoFBZ30L4DUMQPPezD08hN-CXuge7VFMaIkAAAAA+allowUntrustedCertificate
update.check.status=enabled
users.status=enabled
users.1.name=ubnt
users.1.password=$1$CbZ01ZTh$yxjG1yPTbJ7wBlbif5hEC0
users.1.status=enabled
users.2.gid=100
users.2.shell=/bin/false
users.2.status=disabled
users.2.uid=100
wireless.status=enabled
wireless.1.ampdu.frames=32
wireless.1.ampdu.status=enabled
wireless.1.amsdu=3
wireless.1.devname=ath0
wireless.1.hide_ssid=disabled
wireless.1.l2_isolation=disabled
wireless.1.mac_acl.policy=allow
wireless.1.mac_acl.status=disabled
wireless.1.mcast.enhance=0
wireless.1.rate.auto=enabled
wireless.1.rate.mcs=-1
wireless.1.security.type=none
wireless.1.signal_led_status=enabled
wireless.1.signal_led1=94
wireless.1.signal_led2=80
wireless.1.signal_led3=73
wireless.1.signal_led4=65
wireless.1.ssid=vntx_pr_494
wireless.1.status=enabled
wireless.1.wds.status=enabled
wpasupplicant.device.1.devname=ath0
wpasupplicant.device.1.profile=AUTO
wpasupplicant.device.1.status=disabled
wpasupplicant.profile.1.name=AUTO
wpasupplicant.profile.1.network.1.key_mgmt.1.name=WPA-PSK
wpasupplicant.profile.1.network.1.pairwise.1.name=CCMP
wpasupplicant.profile.1.network.1.proto.1.name=RSN
wpasupplicant.profile.1.network.1.psk=0000:0000
wpasupplicant.profile.1.network.1.ssid=vntx_pr_494
wpasupplicant.profile.1.network.1.status=enabled
wpasupplicant.profile.1.network.2.key_mgmt.1.name=NONE
wpasupplicant.profile.1.network.2.priority=2
wpasupplicant.profile.1.network.2.status=disabled
wpasupplicant.status=disabled

108
cpes.txt Normal file
View file

@ -0,0 +1,108 @@
## ePMP Force 110 (1 devices)
00:04:56:F0:36:0F #125 Eddie Yarbrough 10.10.16.51
## ePMP Force 180 (36 devices)
00:04:56:2E:B9:2E #185 Joanna Rodriguez 10.10.160.16
00:04:56:2F:D7:CC #2098 Stephen Beegle 10.10.160.2
00:04:56:2E:C9:FF #1829 Ashley Smith 10.10.160.6
58:C1:7A:AD:14:9A #357 Brian Swedlund 10.10.160.7
58:C1:7A:AC:65:08 #2200 Jennifer Boon 10.10.16.38
00:04:56:44:5E:7B #343 Tim Bagert 10.10.16.40
58:C1:7A:AD:22:EC #1650 luis Cabrera 10.10.64.1
58:C1:7A:AC:64:51 #2454 Nancy Avellaneda 10.10.64.5
58:C1:7A:AC:62:5F #2206 Wanda Davis 10.10.96.102
00:04:56:2F:DB:A1 #355 Treceda Schmid 10.10.96.11
58:C1:7A:AC:69:46 #2190 Ray Schaak 10.10.96.17
00:04:56:2E:C1:6E #177 JERRY L JOHNSON 10.10.96.2
00:04:56:2F:D4:BD #283 Paul Sammataro 10.10.96.29
00:04:56:2F:CE:8D #2375 Yolanda Judge 10.10.96.35
00:04:56:2F:DA:84 #1908 Gabriela Sereno 10.10.96.39
58:C1:7A:AD:17:85 #2043 Angelica Cuayahuitl 10.10.96.45
58:C1:7A:AC:63:19 #215 Kileigh Even 10.10.96.46
58:C1:7A:AC:5F:11 #1538 Shirley Elmore 10.10.96.5
00:04:56:2E:B7:6C #1316 Iris Delira 10.10.96.51
00:04:56:44:54:0A #2046 HEATHER WOMACK 10.10.96.54
00:04:56:44:50:AA #2023 Billy Horton 10.10.96.55
58:C1:7A:B0:B6:1F #1128 Patricia Vasquez 10.10.96.58
00:04:56:2F:DC:4C #10040 Thomas Graham 10.10.144.10
58:C1:7A:AC:6A:C3 #10059 Nicholas Terry 10.10.144.11
00:04:56:2F:CD:C7 #84 Coy Ungemach 10.10.144.2
58:C1:7A:AD:15:7B #1906 Georgina Covarrubias 10.10.144.6
58:C1:7A:AD:1F:20 #246 Matt Budjenska 10.10.158.247
58:C1:7A:B0:B7:AE #1950 Leandra Beshea 10.10.128.1
00:04:56:2F:D9:10 #1971 Clifford Jennings 10.10.128.11
58:C1:7A:AD:13:D7 #100 Dave Kiowski 10.10.128.14
00:04:56:44:5A:37 #997 Heather Brister 10.10.128.25
00:04:56:44:52:ED #318 Shannon Brown 10.10.128.32
00:04:56:44:50:68 #1949 RICHARD CONKLIN 10.10.128.34
58:C1:7A:AC:5A:E5 #2489 Martha Flores 10.10.128.35
58:C1:7A:AC:E8:81 #2492 Kory Biggs Shop 10.10.128.43
00:04:56:2F:DA:E7 #2010 Brady Clayton 10.10.128.48
## ePMP Force 200 (59 devices)
00:04:56:DC:69:AE #932 Matt Kosarek 10.10.160.19
00:04:56:DB:D2:CA #1168 Stephen Day 10.10.160.22
00:04:56:DB:EC:11 #2037 Kevin Arana 10.10.160.9
00:04:56:DB:63:5E #2410 Carol Strickland 10.10.16.37
00:04:56:4A:12:EE #2133 Glenda Beauchamp 10.10.16.41
58:C1:7A:B1:05:12 #2275 John Vayo 10.10.16.47
58:C1:7A:B0:D7:10 #2233 Crystal Harney 10.10.16.50
00:04:56:DB:57:2B #855 Amber Prater 10.10.16.56
58:C1:7A:B1:09:F8 #2360 Doug Garber 10.10.16.57
00:04:56:DC:73:98 #2530 Donna Campbell 10.10.16.58
58:C1:7A:B3:07:A1 #435 Maria Cortes 10.10.64.12
00:04:56:DB:EB:18 #490 Terry Bates 10.10.64.8
58:C1:7A:B1:02:63 #192 Aurea Sanchez 10.10.96.10
00:04:56:DB:99:40 #2407 David Roesch 10.10.96.13
00:04:56:48:63:EF #587 Tamy Thomas 10.10.96.14
00:04:56:DB:C2:2C #497 Jody McCluskey 10.10.96.26
00:04:56:DB:EA:A0 #2487 Anthony Longo 10.10.96.33
00:04:56:4A:17:53 #10074 Pablo Martinez 10.10.96.36
00:04:56:DB:E9:FE #2479 Thomas Hernandez 10.10.96.38
00:04:56:DB:E5:ED #1788 Tim Bronschidle 10.10.96.40
58:C1:7A:B0:F3:39 #2335 Tiffany Stewart 10.10.96.42
00:04:56:DC:73:6B #10027 Irene Garibay 10.10.96.50
00:04:56:DC:6B:55 #10052 Khushboo Agarwal 10.10.96.53
58:C1:7A:B0:D7:0D #1601 Jerid Rutledge 10.10.96.65
00:04:56:DC:6C:9F #293 Jeff Harder 10.10.96.67
58:C1:7A:B3:06:A8 #875 Ken Patrick 10.10.96.7
00:04:56:DC:67:59 #1467 Terri Mack 10.10.96.72
00:04:56:DC:59:A6 #427 Ausencio Luna 10.10.96.73
00:04:56:DB:C2:11 #433 Balnca Diaz 10.10.96.77
00:04:56:DC:6D:77 #1712 Bobby Mack 10.10.96.84
00:04:56:48:66:E3 #10024 Janis Stewart 10.10.96.9
00:04:56:4A:15:B5 #10073 Jeffrey Harder 10.10.96.95
00:04:56:4A:13:7E #1441 Doris Avalos 10.10.144.16
58:C1:7A:B0:F7:8F #224 Leonard Lewis 10.10.144.20
00:04:56:DC:79:38 #477 Helen Lumpkin 10.10.144.24
00:04:56:DC:72:2A #735 FRED HECKEL 10.10.144.25
00:04:56:DB:E8:D5 #990 Jennifer Dunaway 10.10.144.27
00:04:56:DC:6A:65 #345 Tim Fisher 10.10.144.31
00:04:56:DB:AA:E6 #815 Terry Gortney 10.10.144.32
00:04:56:DC:5D:63 #243 Mark Nunnelee 10.10.144.35
00:04:56:DC:60:8D #1081 Abbie Andrews 10.10.144.36
00:04:56:DC:04:EF #301 Ron Berger 10.10.144.4
00:04:56:DC:72:24 #339 Terry Miesen 10.10.144.7
00:04:56:DB:97:F9 #848 Douglas Wilson 10.10.128.13
00:04:56:4A:17:B3 #2027 Chris Clayton 10.10.128.18
00:04:56:DC:69:F0 #2458 Marcus Patton 10.10.128.28
00:04:56:DB:E7:4C #46 Bernard Heer 10.10.128.3
00:04:56:DC:5E:50 #2465 Kory Biggs 10.10.128.33
00:04:56:DC:06:B7 #568 Frances Kirby 10.10.128.37
00:04:56:DC:05:04 #556 Mike Bell 10.10.128.45
58:C1:7A:B0:D0:20 #997 Heather Brister 10.10.128.50
00:04:56:DC:71:E8 #1879 Chris Telles 10.10.128.51
00:04:56:DC:6A:0B #1585 Tim Thomas 10.10.128.52
00:04:56:DC:66:06 #146 Geok Choon Pang 10.10.128.54
58:C1:7A:B0:F8:49 #2280 Ken Hall 10.10.128.6
58:C1:7A:B1:04:8B #2292 Joe Speciale 10.10.128.63
00:04:56:DB:EB:78 #809 Brad Gurney 10.10.128.8
58:C1:7A:B0:C1:3B #2295 Christine Hamparyan 10.10.142.252
58:C1:7A:B1:05:D2 #2218 Greg Hummel 10.10.142.253
## no response (1 devices)
58:C1:7A:B3:08:B8 #623 Michael Newby 10.10.96.91
## Ubiquiti (unknown model) (1 devices)
E0:63:DA:0F:B8:F5 #2529 Terry Rector 10.10.62.208

97
cpes_annotated.txt Normal file
View file

@ -0,0 +1,97 @@
MAC Model Account IP
------------------------------------------------------------------------------------------------------------------------
00:04:56:DC:60:8D PowerBeam M5 / Loco M5 | fw=5.10.4 #1081 Abbie Andrews 10.10.144.36
00:04:56:DB:57:2B PowerBeam M5 / Loco M5 | fw=5.11.0 #855 Amber Prater 10.10.16.56
58:C1:7A:AD:17:85 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #2043 Angelica Cuayahuitl 10.10.96.45
00:04:56:DB:EA:A0 PowerBeam M5 / Loco M5 | fw=5.10.4 #2487 Anthony Longo 10.10.96.33
00:04:56:2E:C9:FF NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #1829 Ashley Smith 10.10.160.6
58:C1:7A:B1:02:63 PowerBeam M5 / Loco M5 | fw=5.10.4 #192 Aurea Sanchez 10.10.96.10
00:04:56:DC:59:A6 PowerBeam M5 / Loco M5 | fw=5.10.4 #427 Ausencio Luna 10.10.96.73
00:04:56:DB:C2:11 PowerBeam M5 / Loco M5 | fw=5.11.1 #433 Balnca Diaz 10.10.96.77
00:04:56:DB:E7:4C PowerBeam M5 / Loco M5 | fw=5.11.0 #46 Bernard Heer 10.10.128.3
00:04:56:44:50:AA NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #2023 Billy Horton 10.10.96.55
00:04:56:DC:6D:77 PowerBeam M5 / Loco M5 | fw=5.10.4 #1712 Bobby Mack 10.10.96.84
00:04:56:DB:EB:78 PowerBeam M5 / Loco M5 | fw=5.11.0 #809 Brad Gurney 10.10.128.8
00:04:56:2F:DA:E7 NanoBeam M5 / LiteBeam M5 | fw=5.11.0 #2010 Brady Clayton 10.10.128.48
58:C1:7A:AD:14:9A NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #357 Brian Swedlund 10.10.160.7
00:04:56:DB:63:5E PowerBeam M5 / Loco M5 | fw=5.11.1 #2410 Carol Strickland 10.10.16.37
00:04:56:4A:17:B3 PowerBeam M5 / Loco M5 | fw=5.11.1 #2027 Chris Clayton 10.10.128.18
00:04:56:DC:71:E8 PowerBeam M5 / Loco M5 | fw=5.11.1 #1879 Chris Telles 10.10.128.51
58:C1:7A:B0:C1:3B PowerBeam M5 / Loco M5 | fw=5.10.4 #2295 Christine Hamparyan 10.10.142.252
00:04:56:2F:D9:10 NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #1971 Clifford Jennings 10.10.128.11
00:04:56:2F:CD:C7 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #84 Coy Ungemach 10.10.144.2
58:C1:7A:B0:D7:10 PowerBeam M5 / Loco M5 | fw=5.11.0 #2233 Crystal Harney 10.10.16.50
58:C1:7A:AD:13:D7 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #100 Dave Kiowski 10.10.128.14
00:04:56:DB:99:40 PowerBeam M5 / Loco M5 | fw=5.10.4 #2407 David Roesch 10.10.96.13
00:04:56:DC:73:98 PowerBeam M5 / Loco M5 | fw=5.11.0 #2530 Donna Campbell 10.10.16.58
00:04:56:4A:13:7E PowerBeam M5 / Loco M5 | fw=5.10.4 #1441 Doris Avalos 10.10.144.16
58:C1:7A:B1:09:F8 PowerBeam M5 / Loco M5 | fw=5.11.1 #2360 Doug Garber 10.10.16.57
00:04:56:DB:97:F9 PowerBeam M5 / Loco M5 | fw=5.11.1 #848 Douglas Wilson 10.10.128.13
00:04:56:DC:06:B7 PowerBeam M5 / Loco M5 | fw=5.11.1 #568 Frances Kirby 10.10.128.37
00:04:56:DC:72:2A PowerBeam M5 / Loco M5 | fw=5.10.4 #735 FRED HECKEL 10.10.144.25
00:04:56:2F:DA:84 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #1908 Gabriela Sereno 10.10.96.39
00:04:56:DC:66:06 PowerBeam M5 / Loco M5 | fw=5.11.1 #146 Geok Choon Pang 10.10.128.54
58:C1:7A:AD:15:7B NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #1906 Georgina Covarrubias 10.10.144.6
00:04:56:4A:12:EE PowerBeam M5 / Loco M5 | fw=5.11.0 #2133 Glenda Beauchamp 10.10.16.41
58:C1:7A:B1:05:D2 PowerBeam M5 / Loco M5 | fw=5.10.4 #2218 Greg Hummel 10.10.142.253
00:04:56:44:5A:37 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #997 Heather Brister 10.10.128.25
58:C1:7A:B0:D0:20 PowerBeam M5 / Loco M5 | fw=5.10.4 #997 Heather Brister 10.10.128.50
00:04:56:44:54:0A NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #2046 HEATHER WOMACK 10.10.96.54
00:04:56:DC:79:38 PowerBeam M5 / Loco M5 | fw=5.10.4 #477 Helen Lumpkin 10.10.144.24
00:04:56:DC:73:6B PowerBeam M5 / Loco M5 | fw=4.8.1 #10027 Irene Garibay 10.10.96.50
00:04:56:2E:B7:6C NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #1316 Iris Delira 10.10.96.51
00:04:56:48:66:E3 PowerBeam M5 / Loco M5 | fw=5.10.4 #10024 Janis Stewart 10.10.96.9
00:04:56:DC:6C:9F PowerBeam M5 / Loco M5 | fw=5.10.4 #293 Jeff Harder 10.10.96.67
00:04:56:4A:15:B5 PowerBeam M5 / Loco M5 | fw=5.10.4 #10073 Jeffrey Harder 10.10.96.95
58:C1:7A:AC:65:08 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #2200 Jennifer Boon 10.10.16.38
00:04:56:DB:E8:D5 PowerBeam M5 / Loco M5 | fw=5.10.4 #990 Jennifer Dunaway 10.10.144.27
58:C1:7A:B0:D7:0D PowerBeam M5 / Loco M5 | fw=5.10.4 #1601 Jerid Rutledge 10.10.96.65
00:04:56:2E:C1:6E NanoBeam M5 / LiteBeam M5 | fw=5.11.0 #177 JERRY L JOHNSON 10.10.96.2
00:04:56:2E:B9:2E NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #185 Joanna Rodriguez 10.10.160.16
00:04:56:DB:C2:2C PowerBeam M5 / Loco M5 | fw=5.10.4 #497 Jody McCluskey 10.10.96.26
58:C1:7A:B1:04:8B PowerBeam M5 / Loco M5 | fw=5.11.0 #2292 Joe Speciale 10.10.128.63
58:C1:7A:B1:05:12 PowerBeam M5 / Loco M5 | fw=5.11.0 #2275 John Vayo 10.10.16.47
58:C1:7A:B0:F8:49 PowerBeam M5 / Loco M5 | fw=5.11.0 #2280 Ken Hall 10.10.128.6
58:C1:7A:B3:06:A8 PowerBeam M5 / Loco M5 | fw=5.11.1 #875 Ken Patrick 10.10.96.7
00:04:56:DB:EC:11 PowerBeam M5 / Loco M5 | fw=5.11.1 #2037 Kevin Arana 10.10.160.9
00:04:56:DC:6B:55 PowerBeam M5 / Loco M5 | fw=5.10.4 #10052 Khushboo Agarwal 10.10.96.53
58:C1:7A:AC:63:19 NanoBeam M5 / LiteBeam M5 | fw=4.6.0.1 #215 Kileigh Even 10.10.96.46
00:04:56:DC:5E:50 PowerBeam M5 / Loco M5 | fw=5.11.1 #2465 Kory Biggs 10.10.128.33
58:C1:7A:AC:E8:81 NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #2492 Kory Biggs Shop 10.10.128.43
58:C1:7A:B0:B7:AE NanoBeam M5 / LiteBeam M5 | fw=5.11.0 #1950 Leandra Beshea 10.10.128.1
58:C1:7A:B0:F7:8F PowerBeam M5 / Loco M5 | fw=5.10.4 #224 Leonard Lewis 10.10.144.20
58:C1:7A:AD:22:EC NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #1650 luis Cabrera 10.10.64.1
00:04:56:DC:69:F0 PowerBeam M5 / Loco M5 | fw=5.11.1 #2458 Marcus Patton 10.10.128.28
58:C1:7A:B3:07:A1 PowerBeam M5 / Loco M5 | fw=5.11.1 #435 Maria Cortes 10.10.64.12
00:04:56:DC:5D:63 PowerBeam M5 / Loco M5 | fw=5.10.4 #243 Mark Nunnelee 10.10.144.35
58:C1:7A:AC:5A:E5 NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #2489 Martha Flores 10.10.128.35
58:C1:7A:AD:1F:20 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #246 Matt Budjenska 10.10.158.247
00:04:56:DC:69:AE PowerBeam M5 / Loco M5 | fw=5.11.1 #932 Matt Kosarek 10.10.160.19
58:C1:7A:B3:08:B8 PowerBeam M5 / Loco M5 | fw=5.10.4 #623 Michael Newby 10.10.96.91
00:04:56:DC:05:04 PowerBeam M5 / Loco M5 | fw=5.11.1 #556 Mike Bell 10.10.128.45
58:C1:7A:AC:64:51 NanoBeam M5 / LiteBeam M5 | fw=4.7 #2454 Nancy Avellaneda 10.10.64.5
58:C1:7A:AC:6A:C3 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #10059 Nicholas Terry 10.10.144.11
00:04:56:4A:17:53 PowerBeam M5 / Loco M5 | fw=5.10.4 #10074 Pablo Martinez 10.10.96.36
58:C1:7A:B0:B6:1F NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #1128 Patricia Vasquez 10.10.96.58
00:04:56:2F:D4:BD NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #283 Paul Sammataro 10.10.96.29
58:C1:7A:AC:69:46 NanoBeam M5 / LiteBeam M5 | fw=4.7 #2190 Ray Schaak 10.10.96.17
00:04:56:44:50:68 NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #1949 RICHARD CONKLIN 10.10.128.34
00:04:56:DC:04:EF PowerBeam M5 / Loco M5 | fw=5.10.4 #301 Ron Berger 10.10.144.4
00:04:56:44:52:ED NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #318 Shannon Brown 10.10.128.32
58:C1:7A:AC:5F:11 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #1538 Shirley Elmore 10.10.96.5
00:04:56:2F:D7:CC NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #2098 Stephen Beegle 10.10.160.2
00:04:56:DB:D2:CA PowerBeam M5 / Loco M5 | fw=5.11.1 #1168 Stephen Day 10.10.160.22
00:04:56:48:63:EF PowerBeam M5 / Loco M5 | fw=4.7.0.1 #587 Tamy Thomas 10.10.96.14
00:04:56:DC:67:59 PowerBeam M5 / Loco M5 | fw=5.10.4 #1467 Terri Mack 10.10.96.72
00:04:56:DB:EB:18 PowerBeam M5 / Loco M5 | fw=5.11.1 #490 Terry Bates 10.10.64.8
00:04:56:DB:AA:E6 PowerBeam M5 / Loco M5 | fw=5.10.4 #815 Terry Gortney 10.10.144.32
00:04:56:DC:72:24 PowerBeam M5 / Loco M5 | fw=5.10.4 #339 Terry Miesen 10.10.144.7
00:04:56:2F:DC:4C NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #10040 Thomas Graham 10.10.144.10
00:04:56:DB:E9:FE PowerBeam M5 / Loco M5 | fw=5.10.4 #2479 Thomas Hernandez 10.10.96.38
58:C1:7A:B0:F3:39 PowerBeam M5 / Loco M5 | fw=5.10.4 #2335 Tiffany Stewart 10.10.96.42
00:04:56:DC:6A:0B PowerBeam M5 / Loco M5 | fw=4.8.1 #1585 Tim Thomas 10.10.128.52
00:04:56:44:5E:7B NanoBeam M5 / LiteBeam M5 | fw=5.11.1 #343 Tim Bagert 10.10.16.40
00:04:56:DB:E5:ED PowerBeam M5 / Loco M5 | fw=5.10.4 #1788 Tim Bronschidle 10.10.96.40
00:04:56:DC:6A:65 PowerBeam M5 / Loco M5 | fw=5.10.4 #345 Tim Fisher 10.10.144.31
00:04:56:2F:DB:A1 NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #355 Treceda Schmid 10.10.96.11
00:04:56:2F:CE:8D NanoBeam M5 / LiteBeam M5 | fw=5.10.4 #2375 Yolanda Judge 10.10.96.35

119
cpes_by_model.txt Normal file
View file

@ -0,0 +1,119 @@
Identifying 91 devices via HTTPS...
20/91
40/91
60/91
80/91
91/91 done
## Cambium ePMP (unknown model) (1 devices)
58:C1:7A:B3:08:B8 #623 Michael Newby 10.10.96.91
## ePMP Force 110 (fw=Version 5.11.1) (1 devices)
00:04:56:F0:36:0F #125 Eddie Yarbrough 10.10.16.51
## ePMP Force 180 (fw=Version 5.10.4) (17 devices)
58:C1:7A:AD:17:85 #2043 Angelica Cuayahuitl 10.10.96.45
00:04:56:44:50:AA #2023 Billy Horton 10.10.96.55
00:04:56:2F:CD:C7 #84 Coy Ungemach 10.10.144.2
58:C1:7A:AD:13:D7 #100 Dave Kiowski 10.10.128.14
00:04:56:2F:DA:84 #1908 Gabriela Sereno 10.10.96.39
58:C1:7A:AD:15:7B #1906 Georgina Covarrubias 10.10.144.6
00:04:56:44:5A:37 #997 Heather Brister 10.10.128.25
00:04:56:2E:B7:6C #1316 Iris Delira 10.10.96.51
58:C1:7A:AC:65:08 #2200 Jennifer Boon 10.10.16.38
00:04:56:2E:B9:2E #185 Joanna Rodriguez 10.10.160.16
58:C1:7A:AD:1F:20 #246 Matt Budjenska 10.10.158.247
58:C1:7A:B0:B6:1F #1128 Patricia Vasquez 10.10.96.58
00:04:56:2F:D4:BD #283 Paul Sammataro 10.10.96.29
58:C1:7A:AC:5F:11 #1538 Shirley Elmore 10.10.96.5
00:04:56:2F:D7:CC #2098 Stephen Beegle 10.10.160.2
00:04:56:2F:DB:A1 #355 Treceda Schmid 10.10.96.11
00:04:56:2F:CE:8D #2375 Yolanda Judge 10.10.96.35
## ePMP Force 180 (fw=Version 5.11.0) (3 devices)
00:04:56:2F:DA:E7 #2010 Brady Clayton 10.10.128.48
00:04:56:2E:C1:6E #177 JERRY L JOHNSON 10.10.96.2
58:C1:7A:B0:B7:AE #1950 Leandra Beshea 10.10.128.1
## ePMP Force 180 (fw=Version 5.11.1) (10 devices)
00:04:56:2E:C9:FF #1829 Ashley Smith 10.10.160.6
58:C1:7A:AD:14:9A #357 Brian Swedlund 10.10.160.7
00:04:56:2F:D9:10 #1971 Clifford Jennings 10.10.128.11
00:04:56:44:54:0A #2046 HEATHER WOMACK 10.10.96.54
58:C1:7A:AC:E8:81 #2492 Kory Biggs Shop 10.10.128.43
58:C1:7A:AD:22:EC #1650 luis Cabrera 10.10.64.1
58:C1:7A:AC:5A:E5 #2489 Martha Flores 10.10.128.35
00:04:56:44:50:68 #1949 RICHARD CONKLIN 10.10.128.34
00:04:56:44:52:ED #318 Shannon Brown 10.10.128.32
00:04:56:44:5E:7B #343 Tim Bagert 10.10.16.40
## ePMP Force 200 (fw=Version 4.8.1) (1 devices)
00:04:56:DC:6A:0B #1585 Tim 10.10.128.52
## ePMP Force 200 (fw=Version 5.10.4) (26 devices)
00:04:56:DC:60:8D #1081 Abbie Andrews 10.10.144.36
00:04:56:DB:EA:A0 #2487 Anthony Longo 10.10.96.33
58:C1:7A:B1:02:63 #192 Aurea Sanchez 10.10.96.10
00:04:56:DC:59:A6 #427 Ausencio Luna 10.10.96.73
00:04:56:DC:6D:77 #1712 Bobby Mack 10.10.96.84
58:C1:7A:B0:C1:3B #2295 Christine Hamparyan 10.10.142.252
00:04:56:DB:99:40 #2407 David Roesch 10.10.96.13
00:04:56:4A:13:7E #1441 Doris Avalos 10.10.144.16
00:04:56:DC:72:2A #735 FRED HECKEL 10.10.144.25
58:C1:7A:B1:05:D2 #2218 Greg Hummel 10.10.142.253
58:C1:7A:B0:D0:20 #997 Heather Brister 10.10.128.50
00:04:56:DC:79:38 #477 Helen Lumpkin 10.10.144.24
00:04:56:DC:6C:9F #293 Jeff Harder 10.10.96.67
00:04:56:DB:E8:D5 #990 Jennifer Dunaway 10.10.144.27
58:C1:7A:B0:D7:0D #1601 Jerid Rutledge 10.10.96.65
00:04:56:DB:C2:2C #497 Jody McCluskey 10.10.96.26
58:C1:7A:B0:F7:8F #224 Leonard Lewis 10.10.144.20
00:04:56:DC:5D:63 #243 Mark Nunnelee 10.10.144.35
00:04:56:DC:04:EF #301 Ron Berger 10.10.144.4
00:04:56:DC:67:59 #1467 Terri Mack 10.10.96.72
00:04:56:DB:AA:E6 #815 Terry Gortney 10.10.144.32
00:04:56:DC:72:24 #339 Terry Miesen 10.10.144.7
00:04:56:DB:E9:FE #2479 Thomas Hernandez 10.10.96.38
58:C1:7A:B0:F3:39 #2335 Tiffany Stewart 10.10.96.42
00:04:56:DB:E5:ED #1788 Tim Bronschidle 10.10.96.40
00:04:56:DC:6A:65 #345 Tim Fisher 10.10.144.31
## ePMP Force 200 (fw=Version 5.11.0) (9 devices)
00:04:56:DB:57:2B #855 Amber Prater 10.10.16.56
00:04:56:DB:E7:4C #46 Bernard Heer 10.10.128.3
00:04:56:DB:EB:78 #809 Brad Gurney 10.10.128.8
58:C1:7A:B0:D7:10 #2233 Crystal Harney 10.10.16.50
00:04:56:DC:73:98 #2530 Donna Campbell 10.10.16.58
00:04:56:4A:12:EE #2133 Glenda Beauchamp 10.10.16.41
58:C1:7A:B1:04:8B #2292 Joe Speciale 10.10.128.63
58:C1:7A:B1:05:12 #2275 John Vayo 10.10.16.47
58:C1:7A:B0:F8:49 #2280 Ken Hall 10.10.128.6
## ePMP Force 200 (fw=Version 5.11.1) (17 devices)
00:04:56:DB:C2:11 #433 Balnca Diaz 10.10.96.77
00:04:56:DB:63:5E #2410 Carol Strickland 10.10.16.37
00:04:56:4A:17:B3 #2027 Chris Clayton 10.10.128.18
00:04:56:DC:71:E8 #1879 Chris Telles 10.10.128.51
58:C1:7A:B1:09:F8 #2360 Doug Garber 10.10.16.57
00:04:56:DB:97:F9 #848 Douglas Wilson 10.10.128.13
00:04:56:DC:06:B7 #568 Frances Kirby 10.10.128.37
00:04:56:DC:66:06 #146 Geok Choon Pang 10.10.128.54
58:C1:7A:B3:06:A8 #875 Ken Patrick 10.10.96.7
00:04:56:DB:EC:11 #2037 Kevin Arana 10.10.160.9
00:04:56:DC:5E:50 #2465 Kory Biggs 10.10.128.33
00:04:56:DC:69:F0 #2458 Marcus Patton 10.10.128.28
58:C1:7A:B3:07:A1 #435 Maria Cortes 10.10.64.12
00:04:56:DC:69:AE #932 Matt Kosarek 10.10.160.19
00:04:56:DC:05:04 #556 Mike Bell 10.10.128.45
00:04:56:DB:D2:CA #1168 Stephen Day 10.10.160.22
00:04:56:DB:EB:18 #490 Terry Bates 10.10.64.8
## no HTTPS response (5 devices)
58:C1:7A:AC:63:19 #215 Kileigh Even 10.10.96.46
58:C1:7A:AC:64:51 #2454 Nancy Avellaneda 10.10.64.5
58:C1:7A:AC:69:46 #2190 Ray Schaak 10.10.96.17
58:C1:7A:AC:62:5F #2206 Wanda Davis 10.10.96.102
00:04:56:48:63:EF #587 Tamy Thomas 10.10.96.14
## Ubiquiti (unknown model) (1 devices)
E0:63:DA:0F:B8:F5 #2529 Terry Rector 10.10.62.208

196
cpes_raw.txt Normal file
View file

@ -0,0 +1,196 @@
router=494 ip=10.10.160.16 mac=00:04:56:2E:B9:2E device=Ubiquiti (legacy)
account=#185 name='Joanna Rodriguez' host='Joanna Rodriguez'
router=494 ip=10.10.160.19 mac=00:04:56:DC:69:AE device=Ubiquiti (legacy)
account=#932 name='Matt Kosarek' host='Matt Kosarek'
router=494 ip=10.10.160.2 mac=00:04:56:2F:D7:CC device=Ubiquiti (legacy)
account=#2098 name='Stephen Beegle' host='Stephen Beegle'
router=494 ip=10.10.160.22 mac=00:04:56:DB:D2:CA device=Ubiquiti (legacy)
account=#1168 name='Stephen Day' host='Stephen Day'
router=494 ip=10.10.160.6 mac=00:04:56:2E:C9:FF device=Ubiquiti (legacy)
account=#1829 name='Ashley Smith' host='Ashley Smith'
router=494 ip=10.10.160.7 mac=58:C1:7A:AD:14:9A device=Cambium ePMP
account=#357 name='Brian Swedlund' host='Brian Swedlund'
router=494 ip=10.10.160.9 mac=00:04:56:DB:EC:11 device=Ubiquiti (legacy)
account=#2037 name='Kevin Arana' host='Kevin Arana'
router=982 ip=10.10.62.208 mac=E0:63:DA:0F:B8:F5 device=Ubiquiti PowerBeam M5 / Loco M5
account=#2529 name='Terry Rector' host='Terry-Rector'
router=climax ip=10.10.16.37 mac=00:04:56:DB:63:5E device=Ubiquiti (legacy)
account=#2410 name='Carol Strickland' host='Carol Strickland'
router=climax ip=10.10.16.38 mac=58:C1:7A:AC:65:08 device=Cambium ePMP
account=#2200 name='Jennifer Boon' host='Jennifer Boon'
router=climax ip=10.10.16.40 mac=00:04:56:44:5E:7B device=Ubiquiti (legacy)
account=#343 name='Tim Bagert' host='Tim Bagert'
router=climax ip=10.10.16.41 mac=00:04:56:4A:12:EE device=Ubiquiti (legacy)
account=#2133 name='Glenda Beauchamp' host='Glenda Beauchamp'
router=climax ip=10.10.16.47 mac=58:C1:7A:B1:05:12 device=Cambium ePMP
account=#2275 name='John Vayo' host='John Vayo'
router=climax ip=10.10.16.50 mac=58:C1:7A:B0:D7:10 device=Cambium ePMP
account=#2233 name='Crystal Harney' host='Crystal Harney'
router=climax ip=10.10.16.51 mac=00:04:56:F0:36:0F device=Ubiquiti (legacy)
account=#125 name='Eddie Yarbrough' host='Eddie Yarbrough'
router=climax ip=10.10.16.56 mac=00:04:56:DB:57:2B device=Ubiquiti (legacy)
account=#855 name='Amber Prater' host='Amber Prater'
router=climax ip=10.10.16.57 mac=58:C1:7A:B1:09:F8 device=Cambium ePMP
account=#2360 name='Doug Garber' host='Doug Garber'
router=climax ip=10.10.16.58 mac=00:04:56:DC:73:98 device=Ubiquiti (legacy)
account=#2530 name='Donna Campbell' host='Donna Campbell'
router=core ip=10.10.64.1 mac=58:C1:7A:AD:22:EC device=Cambium ePMP
account=#1650 name='luis Cabrera' host='Luis Cabrera'
router=core ip=10.10.64.12 mac=58:C1:7A:B3:07:A1 device=Cambium ePMP
account=#435 name='Maria Cortes' host='Maria Cortes'
router=core ip=10.10.64.5 mac=58:C1:7A:AC:64:51 device=Cambium ePMP
account=#2454 name='Nancy Avellaneda' host='Nancy Avellaneda'
router=core ip=10.10.64.8 mac=00:04:56:DB:EB:18 device=Ubiquiti (legacy)
account=#490 name='Terry Bates' host='Terry Bates'
router=culleoka ip=10.10.96.10 mac=58:C1:7A:B1:02:63 device=Cambium ePMP
account=#192 name='Aurea Sanchez' host='Aurea Sanchez'
router=culleoka ip=10.10.96.102 mac=58:C1:7A:AC:62:5F device=Cambium ePMP
account=#2206 name='Wanda Davis' host='Wanda Davis'
router=culleoka ip=10.10.96.11 mac=00:04:56:2F:DB:A1 device=Ubiquiti (legacy)
account=#355 name='Treceda Schmid' host='Treceda Schmid'
router=culleoka ip=10.10.96.13 mac=00:04:56:DB:99:40 device=Ubiquiti (legacy)
account=#2407 name='David Roesch' host='David Roesch'
router=culleoka ip=10.10.96.14 mac=00:04:56:48:63:EF device=Ubiquiti (legacy)
account=#587 name='Tamy Thomas' host='Tamy Thomas'
router=culleoka ip=10.10.96.17 mac=58:C1:7A:AC:69:46 device=Cambium ePMP
account=#2190 name='Ray Schaak' host='Ray Schaak'
router=culleoka ip=10.10.96.2 mac=00:04:56:2E:C1:6E device=Ubiquiti (legacy)
account=#177 name='JERRY L JOHNSON' host='Jerry Johnson'
router=culleoka ip=10.10.96.26 mac=00:04:56:DB:C2:2C device=Ubiquiti (legacy)
account=#497 name='Jody McCluskey' host='Jody McCluskey'
router=culleoka ip=10.10.96.29 mac=00:04:56:2F:D4:BD device=Ubiquiti (legacy)
account=#283 name='Paul Sammataro' host='Paul Sammataro'
router=culleoka ip=10.10.96.33 mac=00:04:56:DB:EA:A0 device=Ubiquiti (legacy)
account=#2487 name='Anthony Longo' host='Anthony Longo'
router=culleoka ip=10.10.96.35 mac=00:04:56:2F:CE:8D device=Ubiquiti (legacy)
account=#2375 name='Yolanda Judge' host='Yolanda Judge'
router=culleoka ip=10.10.96.36 mac=00:04:56:4A:17:53 device=Ubiquiti (legacy)
account=#10074 name='Pablo Martinez' host='Pablo Martinez'
router=culleoka ip=10.10.96.38 mac=00:04:56:DB:E9:FE device=Ubiquiti (legacy)
account=#2479 name='Thomas Hernandez' host='Thomas Hernandez'
router=culleoka ip=10.10.96.39 mac=00:04:56:2F:DA:84 device=Ubiquiti (legacy)
account=#1908 name='Gabriela Sereno' host='Gabriela Sereno'
router=culleoka ip=10.10.96.40 mac=00:04:56:DB:E5:ED device=Ubiquiti (legacy)
account=#1788 name='Tim Bronschidle' host='Tim Bronschidle'
router=culleoka ip=10.10.96.42 mac=58:C1:7A:B0:F3:39 device=Cambium ePMP
account=#2335 name='Tiffany Stewart' host='Tiffany Stewart'
router=culleoka ip=10.10.96.45 mac=58:C1:7A:AD:17:85 device=Cambium ePMP
account=#2043 name='Angelica Cuayahuitl' host='Angelica Cuayahuitl'
router=culleoka ip=10.10.96.46 mac=58:C1:7A:AC:63:19 device=Cambium ePMP
account=#215 name='Kileigh Even' host='Kileigh Even'
router=culleoka ip=10.10.96.5 mac=58:C1:7A:AC:5F:11 device=Cambium ePMP
account=#1538 name='Shirley Elmore' host='Shirley Elmore'
router=culleoka ip=10.10.96.50 mac=00:04:56:DC:73:6B device=Ubiquiti (legacy)
account=#10027 name='Irene Garibay' host='Irene Garibay'
router=culleoka ip=10.10.96.51 mac=00:04:56:2E:B7:6C device=Ubiquiti (legacy)
account=#1316 name='Iris Delira' host='Iris Delira'
router=culleoka ip=10.10.96.53 mac=00:04:56:DC:6B:55 device=Ubiquiti (legacy)
account=#10052 name='Khushboo Agarwal' host='Khushboo Agarwal'
router=culleoka ip=10.10.96.54 mac=00:04:56:44:54:0A device=Ubiquiti (legacy)
account=#2046 name='HEATHER WOMACK' host='Heather Womack'
router=culleoka ip=10.10.96.55 mac=00:04:56:44:50:AA device=Ubiquiti (legacy)
account=#2023 name='Billy Horton' host='Billy Horton'
router=culleoka ip=10.10.96.58 mac=58:C1:7A:B0:B6:1F device=Cambium ePMP
account=#1128 name='Patricia Vasquez' host='Patricia Vasquez'
router=culleoka ip=10.10.96.65 mac=58:C1:7A:B0:D7:0D device=Cambium ePMP
account=#1601 name='Jerid Rutledge' host='Jerid Rutledge'
router=culleoka ip=10.10.96.67 mac=00:04:56:DC:6C:9F device=Ubiquiti (legacy)
account=#293 name='Jeff Harder' host='Jeff Harder'
router=culleoka ip=10.10.96.7 mac=58:C1:7A:B3:06:A8 device=Cambium ePMP
account=#875 name='Ken Patrick' host='Ken Patrick'
router=culleoka ip=10.10.96.72 mac=00:04:56:DC:67:59 device=Ubiquiti (legacy)
account=#1467 name='Terri Mack' host='Terri Mack'
router=culleoka ip=10.10.96.73 mac=00:04:56:DC:59:A6 device=Ubiquiti (legacy)
account=#427 name='Ausencio Luna' host='Ausencio Luna'
router=culleoka ip=10.10.96.77 mac=00:04:56:DB:C2:11 device=Ubiquiti (legacy)
account=#433 name='Balnca Diaz' host='Balnca Diaz'
router=culleoka ip=10.10.96.84 mac=00:04:56:DC:6D:77 device=Ubiquiti (legacy)
account=#1712 name='Bobby Mack' host='Bobby Mack'
router=culleoka ip=10.10.96.9 mac=00:04:56:48:66:E3 device=Ubiquiti (legacy)
account=#10024 name='Janis Stewart' host='Janis Stewart'
router=culleoka ip=10.10.96.91 mac=58:C1:7A:B3:08:B8 device=Cambium ePMP
account=#623 name='Michael Newby' host='Michael Newby'
router=culleoka ip=10.10.96.95 mac=00:04:56:4A:15:B5 device=Ubiquiti (legacy)
account=#10073 name='Jeffrey Harder' host='Jeffrey Harder'
router=lowry ip=10.10.144.10 mac=00:04:56:2F:DC:4C device=Ubiquiti (legacy)
account=#10040 name='Thomas Graham' host='Thomas Graham'
router=lowry ip=10.10.144.11 mac=58:C1:7A:AC:6A:C3 device=Cambium ePMP
account=#10059 name='Nicholas Terry' host='Nicholas Terry'
router=lowry ip=10.10.144.16 mac=00:04:56:4A:13:7E device=Ubiquiti (legacy)
account=#1441 name='Doris Avalos' host='Doris Avalos'
router=lowry ip=10.10.144.2 mac=00:04:56:2F:CD:C7 device=Ubiquiti (legacy)
account=#84 name='Coy Ungemach' host='Coy Ungemach'
router=lowry ip=10.10.144.20 mac=58:C1:7A:B0:F7:8F device=Cambium ePMP
account=#224 name='Leonard Lewis' host='Leonard Lewis'
router=lowry ip=10.10.144.24 mac=00:04:56:DC:79:38 device=Ubiquiti (legacy)
account=#477 name='Helen Lumpkin' host='Helen Lumpkin'
router=lowry ip=10.10.144.25 mac=00:04:56:DC:72:2A device=Ubiquiti (legacy)
account=#735 name='FRED HECKEL' host='Fred Heckel'
router=lowry ip=10.10.144.27 mac=00:04:56:DB:E8:D5 device=Ubiquiti (legacy)
account=#990 name='Jennifer Dunaway' host='Jennifer Dunaway'
router=lowry ip=10.10.144.31 mac=00:04:56:DC:6A:65 device=Ubiquiti (legacy)
account=#345 name='Tim Fisher' host='Tim Fisher'
router=lowry ip=10.10.144.32 mac=00:04:56:DB:AA:E6 device=Ubiquiti (legacy)
account=#815 name='Terry Gortney' host='Terry Gortney'
router=lowry ip=10.10.144.35 mac=00:04:56:DC:5D:63 device=Ubiquiti (legacy)
account=#243 name='Mark Nunnelee' host='Mark Nunnelee'
router=lowry ip=10.10.144.36 mac=00:04:56:DC:60:8D device=Ubiquiti (legacy)
account=#1081 name='Abbie Andrews' host='Abbie Andrews'
router=lowry ip=10.10.144.4 mac=00:04:56:DC:04:EF device=Ubiquiti (legacy)
account=#301 name='Ron Berger' host='Ron Berger'
router=lowry ip=10.10.144.6 mac=58:C1:7A:AD:15:7B device=Cambium ePMP
account=#1906 name='Georgina Covarrubias' host='Georgina Covarrubias'
router=lowry ip=10.10.144.7 mac=00:04:56:DC:72:24 device=Ubiquiti (legacy)
account=#339 name='Terry Miesen' host='Terry Miesen'
router=lowry ip=10.10.158.247 mac=58:C1:7A:AD:1F:20 device=Cambium ePMP
account=#246 name='Matt Budjenska' host='Matt Budjenska'
router=newhope ip=10.10.128.1 mac=58:C1:7A:B0:B7:AE device=Cambium ePMP
account=#1950 name='Leandra Beshea' host='Leandra Beshea'
router=newhope ip=10.10.128.11 mac=00:04:56:2F:D9:10 device=Ubiquiti (legacy)
account=#1971 name='Clifford Jennings' host='Clifford M Jennings'
router=newhope ip=10.10.128.13 mac=00:04:56:DB:97:F9 device=Ubiquiti (legacy)
account=#848 name='Douglas Wilson' host='Douglas Wilson'
router=newhope ip=10.10.128.14 mac=58:C1:7A:AD:13:D7 device=Cambium ePMP
account=#100 name='Dave Kiowski' host='Dave Kiowski'
router=newhope ip=10.10.128.18 mac=00:04:56:4A:17:B3 device=Ubiquiti (legacy)
account=#2027 name='Chris Clayton' host='Chris Clayton'
router=newhope ip=10.10.128.25 mac=00:04:56:44:5A:37 device=Ubiquiti (legacy)
account=#997 name='Heather Brister' host='Heather Brister 2'
router=newhope ip=10.10.128.28 mac=00:04:56:DC:69:F0 device=Ubiquiti (legacy)
account=#2458 name='Marcus Patton' host='Marcus Patton'
router=newhope ip=10.10.128.3 mac=00:04:56:DB:E7:4C device=Ubiquiti (legacy)
account=#46 name='Bernard Heer' host='Bernard Heer'
router=newhope ip=10.10.128.32 mac=00:04:56:44:52:ED device=Ubiquiti (legacy)
account=#318 name='Shannon Brown' host='Shannon Brown'
router=newhope ip=10.10.128.33 mac=00:04:56:DC:5E:50 device=Ubiquiti (legacy)
account=#2465 name='Kory Biggs' host='Kory Biggs'
router=newhope ip=10.10.128.34 mac=00:04:56:44:50:68 device=Ubiquiti (legacy)
account=#1949 name='RICHARD CONKLIN' host='Richard Conklin'
router=newhope ip=10.10.128.35 mac=58:C1:7A:AC:5A:E5 device=Cambium ePMP
account=#2489 name='Martha Flores' host='Martha Flores'
router=newhope ip=10.10.128.37 mac=00:04:56:DC:06:B7 device=Ubiquiti (legacy)
account=#568 name='Frances Kirby' host='Frances Kirby'
router=newhope ip=10.10.128.43 mac=58:C1:7A:AC:E8:81 device=Cambium ePMP
account=#2492 name='Kory Biggs Shop' host='Kory Biggs Shop'
router=newhope ip=10.10.128.45 mac=00:04:56:DC:05:04 device=Ubiquiti (legacy)
account=#556 name='Mike Bell' host='Mike Bell'
router=newhope ip=10.10.128.48 mac=00:04:56:2F:DA:E7 device=Ubiquiti (legacy)
account=#2010 name='Brady Clayton' host='Brady Clayton'
router=newhope ip=10.10.128.50 mac=58:C1:7A:B0:D0:20 device=Cambium ePMP
account=#997 name='Heather Brister' host='Heather Brister'
router=newhope ip=10.10.128.51 mac=00:04:56:DC:71:E8 device=Ubiquiti (legacy)
account=#1879 name='Chris Telles' host='Chris Telles'
router=newhope ip=10.10.128.52 mac=00:04:56:DC:6A:0B device=Ubiquiti (legacy)
account=#1585 name='Tim Thomas' host='Tim Thomas'
router=newhope ip=10.10.128.54 mac=00:04:56:DC:66:06 device=Ubiquiti (legacy)
account=#146 name='Geok Choon Pang' host='Geok Choon Pang'
router=newhope ip=10.10.128.6 mac=58:C1:7A:B0:F8:49 device=Cambium ePMP
account=#2280 name='Ken Hall' host='Ken Hall'
router=newhope ip=10.10.128.63 mac=58:C1:7A:B1:04:8B device=Cambium ePMP
account=#2292 name='Joe Speciale' host='Joe Speciale'
router=newhope ip=10.10.128.8 mac=00:04:56:DB:EB:78 device=Ubiquiti (legacy)
account=#809 name='Brad Gurney' host='Brad Gurney'
router=newhope ip=10.10.142.252 mac=58:C1:7A:B0:C1:3B device=Cambium ePMP
account=#2295 name='Christine Hamparyan' host='Christine Hamparyan'
router=newhope ip=10.10.142.253 mac=58:C1:7A:B1:05:D2 device=Cambium ePMP
account=#2218 name='Greg Hummel' host='Greg Hummel'

View file

@ -0,0 +1,229 @@
# IPv6 Traceroute Collapse (6PE) Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Make IPv6 traffic transit the vntx backbone as MPLS-labeled frames so `traceroute6` collapses the internal core to ~1 hop, matching the existing IPv4 behaviour.
**Architecture:** Today LDP is IPv4-only and `/mpls settings propagate-ttl=no` is set fleet-wide, so labeled IPv4 hides the core. IPv6 is forwarded natively (hop-limit decremented every hop). To get the same collapse, IPv6 must ride the existing IPv4 LSPs — either via **6PE** (RFC 4798: labeled-unicast IPv6 in the global table, BGP next-hop = IPv4 loopback) or **VPNv6** (IPv6 in a VRF over the v4 core). Both are unproven on this fleet, so this plan is **spike-first**: Phase 1 is a disposable lab that produces a hard go/no-go before any production router is touched.
**Tech Stack:** RouterOS 7.21.4 (8 tower routers, arm64/tile), RouterOS 6.49.18 (edge), MP-BGP, LDP, OSPFv2/v3, Proxmox + CHR for the lab.
---
## Why this plan is spike-first (read before doing anything)
Verified facts that make the production syntax **unknowable today**:
1. **ROS7 6PE is undocumented.** MikroTik's MPLS Overview page never names 6PE. Community testing (forum, v7.8 CHR) reports "mixed/uncertain" results. There is no authoritative config reference.
2. **Possible hard conflict with the existing v6 iBGP.** The fleet already runs `afi=ipv6` iBGP (tower → core RR → edge) to carry the per-tower `/44` aggregates. A third-party note (netlab) states you *cannot run 6PE and plain IPv6 BGP-LU on the same node*. If that holds on ROS7, 6PE would **replace**, not augment, the current v6 iBGP — a much larger change.
3. **edge is RouterOS 6.49.18.** It is the PE toward TWC (AS11427). ROS6's MPLS/BGP stack is entirely separate from ROS7's, and ROS6 6PE support is at least as questionable. edge may need to stay native-v6 (its single upstream hop is cosmetically irrelevant anyway).
Therefore: **no production router is reconfigured until Phase 1 proves the mechanism in a lab and answers conflict-question #2.** Phases 24 deliberately do not contain exact commands — they are filled in from the spike's findings (Task 1.7).
**If you are tempted to skip Phase 1: don't.** A half-working 6PE deployment black-holes customer IPv6 fleet-wide.
---
## Phase 1 — Lab spike (disposable, zero production impact)
Build a 3-router CHR lab on graham's home Proxmox that mirrors a slice of the
backbone (`edge — core — verona`) plus a CE host at each end, prove that
labeled IPv6 collapses `traceroute6`, and determine whether 6PE coexists with
plain IPv6 iBGP.
**Topology:**
```
[CE-A]──ce-a/v6──[verona]──p2p v4 LDP──[core]──p2p v4 LDP──[edge]──ce-b/v6──[CE-B]
2606:db8:a::/64 2606:db8:b::/64
loopbacks: verona 10.0.0.1 core 10.0.0.2 edge 10.0.0.3 (v6: 2606:db8:0:10::1/2/3)
```
CE-A/CE-B can be CHRs or any Linux VM — they just need an IPv6 address and `traceroute6`.
### Task 1.1: Stand up the CHR lab VMs
**Files:**
- Create: `docs/plans/lab/6pe-lab-notes.md` (running log of what works — this becomes the source for Phases 24)
**Step 1:** On Proxmox, import the RouterOS CHR image (https://mikrotik.com/download → "Cloud Hosted Router" → 7.21.4 stable VMDK/qcow2). Create 5 VMs: `verona-lab`, `core-lab`, `edge-lab`, `ce-a`, `ce-b`. 256 MB RAM / 1 vCPU each is plenty. CHR is licence-free at ≤1 Mbps — fine for a lab.
**Step 2:** Create 4 isolated Proxmox bridges (no uplink): `vmbr-cea`, `vmbr-vc` (verona↔core), `vmbr-ce` (core↔edge), `vmbr-ceb`. Wire NICs:
- verona-lab: eth1→vmbr-cea, eth2→vmbr-vc
- core-lab: eth1→vmbr-vc, eth2→vmbr-ce
- edge-lab: eth1→vmbr-ce, eth2→vmbr-ceb
- ce-a: eth1→vmbr-cea ; ce-b: eth1→vmbr-ceb
**Step 3:** Boot all 5. Confirm console login on each.
**Step 4 (commit):**
```bash
cd /Users/graham/dev/network
git add docs/plans/lab/6pe-lab-notes.md
git commit -m "6pe lab: stand up 5-VM CHR topology"
```
Expected: lab notes file committed with the topology + VM IDs recorded.
### Task 1.2: Build the IPv4 + LDP core (mirror of production)
**Step 1:** On each lab router configure loopback, p2p /30s, OSPFv2, LDP, and `propagate-ttl=no` — a scaled-down copy of the real fleet. Example for `core-lab` (adapt addresses per router):
```rsc
/interface bridge add name=lo
/ip address add address=10.0.0.2/32 interface=lo
/ip address add address=10.1.12.2/30 interface=ether1 ;; to verona
/ip address add address=10.1.23.1/30 interface=ether2 ;; to edge
/routing ospf instance add name=v2 router-id=10.0.0.2
/routing ospf area add name=backbone instance=v2 area-id=0.0.0.0
/routing ospf interface-template add area=backbone interfaces=lo passive
/routing ospf interface-template add area=backbone interfaces=ether1,ether2 type=ptp
/mpls ldp add lsr-id=10.0.0.2 transport-addresses=10.0.0.2
/mpls ldp interface add interface=ether1
/mpls ldp interface add interface=ether2
/mpls settings set propagate-ttl=no
```
**Step 2: Verify the v4 core.** From `verona-lab`:
```rsc
/routing ospf neighbor print ;; expect Full to core
/mpls ldp neighbor print ;; expect operational to core
/mpls forwarding-table print ;; expect labels for 10.0.0.2, 10.0.0.3
/tool traceroute 10.0.0.3 ;; edge loopback — expect 1 hop (LDP + propagate-ttl=no)
```
Expected: traceroute to the far loopback shows **one hop**. This proves the lab reproduces the production v4 collapse. If it does not, fix the lab before continuing — Phase 1's whole value is a faithful mirror.
**Step 3 (commit):** Record the working core configs in `6pe-lab-notes.md`, commit.
### Task 1.3: Baseline — native IPv6, measure the problem
**Step 1:** Add native IPv6 + OSPFv3 across the lab core (loopbacks `2606:db8:0:10::1/2/3`, p2p `2606:db8:0:1N::/64`), and give CE-A/CE-B addresses in `2606:db8:a::/64` and `2606:db8:b::/64` with static routes or OSPFv3.
**Step 2: Measure baseline.** From CE-A:
```
traceroute6 2606:db8:b::1
```
Expected: **3+ hops** (verona, core, edge each appear). Record exact output in lab notes — this is the "before".
**Step 3 (commit):** Commit lab notes with the native-v6 baseline traceroute.
### Task 1.4: Add the existing-style IPv6 iBGP (reproduce the conflict surface)
**Step 1:** Configure v6 iBGP exactly like production: `verona-lab` and `edge-lab` as RR-clients of `core-lab`, `afi=ip,ipv6`, sessions over the v6 loopbacks, redistributing connected. This is the config that 6PE may conflict with.
**Step 2: Verify** v6 iBGP sessions establish and the CE prefixes are learned. Record in lab notes.
**Step 3 (commit).**
### Task 1.5: Attempt 6PE — the actual unknown
**This is the discovery task. Do not fabricate commands — find them.**
**Step 1:** Read, in this order, and capture findings in lab notes:
- MikroTik MPLS Overview + BGP doc pages (help.mikrotik.com) — search for "label", "ipv6", "6PE", "labeled-unicast".
- `/routing bgp connection` and `/routing bgp template` — dump every available property on a lab router (`/routing bgp connection print detail` on a connection with all afi values; try `afi=ipv6` plus any label-related sub-property the CLI tab-completes).
- The MikroTik forum thread "IPv6-on-MPLS BGP-free Core?" for working/failing examples.
**Step 2:** On the lab, attempt 6PE: make the v6 iBGP advertise labeled IPv6 with BGP next-hop = the IPv4 loopback (as `::ffff:10.0.0.x`). Try it **alongside** the Task 1.4 plain v6 iBGP first.
**Step 3: Decision question A — coexistence.** Does enabling 6PE break or get rejected next to the plain `afi=ipv6` iBGP? Record the exact behaviour. If they cannot coexist, note that production Phase 3 must *replace* the v6 iBGP, and re-test 1.5 with the plain v6 iBGP removed.
**Step 4 (commit):** Lab notes updated with the exact 6PE config that loads without error (or the proof that it can't).
### Task 1.6: Verify the collapse (the success criterion)
**Step 1:** Confirm labels are bound to IPv6 routes:
```rsc
/mpls forwarding-table print ;; expect 2606:db8:b::/64 etc. with a label
```
**Step 2:** From CE-A:
```
traceroute6 2606:db8:b::1
```
**Success = 1 hop across the lab core** (or core invisible), matching the v4 behaviour from Task 1.2.
**Step 3:** Failure-mode checks — confirm nothing else broke:
- CE-A ↔ CE-B large-packet ping (`ping6 -s 1452`) — no new MTU loss (label adds 4 bytes; lab p2p MTU must have headroom, same as the real `mpls-mtu=1508`).
- Kill the `core-lab``edge-lab` link, confirm v6 reconverges (or fails predictably).
**Step 4 (commit):** Lab notes updated with the before/after traceroute and the failure-mode results.
### Task 1.7: Write the go/no-go and fill in Phases 24
**Step 1:** In `6pe-lab-notes.md`, write a **GO / NO-GO** verdict answering:
- Does labeled IPv6 collapse `traceroute6` on ROS 7.21.4? (Task 1.6)
- Can 6PE coexist with the existing v6 iBGP, or must it replace it? (Task 1.5 Step 3)
- What is the **exact, tested** per-router 6PE config delta?
- Does anything require an MTU change beyond the existing `mpls-mtu=1508`?
**Step 2:** If **GO**: replace the "TBD pending spike" bodies of Phase 2 and Phase 3 below with the exact tested commands from the lab. If **NO-GO**: stop here, jump to "Alternatives", and bring the recommendation to graham.
**Step 3 (commit):**
```bash
git add docs/plans/lab/6pe-lab-notes.md docs/plans/2026-05-14-ipv6-traceroute-collapse-6pe.md
git commit -m "6pe lab: go/no-go verdict + fill production phases"
```
### Task 1.8: Tear down the lab
**Step 1:** Delete the 5 CHR VMs and 4 Proxmox bridges. The lab notes (committed) are the deliverable; the VMs are disposable.
**Step 2 (commit):** Note teardown in lab notes, commit.
---
## Phase 2 — Production pilot: verona ↔ climax ↔ core *(contingent on Phase 1 = GO)*
**Body TBD — populated from Task 1.7.** Constraints the filled-in version must honour:
- **Apply order matters.** Bring up the labeled-v6 path core-first, then climax, then verona, so a tower is never advertising labeled v6 toward a router that can't resolve it.
- **One change window, one link triangle.** Pilot only verona/climax/core; do not touch the other 5 towers until the pilot soaks ≥24h.
- **Each router task = change → verify → rollback-ready**, e.g.:
- apply 6PE delta on core
- `/mpls forwarding-table print` shows labels for the pilot towers' v6
- `/routing bgp advertisements` / `/routing route print` confirms labeled v6 NLRI
- from graham's home Mac: `traceroute6` to an off-net v6 host — core no longer a distinct hop
- `ping6 -s 1452` to `2606:4700:4700::1111` still 0% loss
- customer-facing: a known v6 PPPoE/wired client still reaches the internet
- **Rollback (per router):** the inverse delta, recorded in Task 1.7. Native v6 + OSPFv3 + the existing v6 iBGP must still be present underneath (do not delete them in the pilot — if 6PE coexists, native is the fallback; if it doesn't, Phase 2 rollback = re-add v6 iBGP).
- **Soak gate:** 24h with no v6 customer complaints + traceroute stays collapsed before Phase 3.
## Phase 3 — Fleet rollout: culleoka, newhope, lowry, 982, 494 *(contingent)*
**Body TBD — populated from Task 1.7.** Constraints:
- Roll out in the CGNAT-renumber order from `subnets.md` (one tower per change window).
- Same per-router verify set as Phase 2.
- newhope carries lowry's transit — do newhope before lowry.
- After each tower: `traceroute6` from home Mac still collapsed, customer v6 spot-check.
- Keep `mikrotik-tool` (`api`/`script`) as the push mechanism; one `.rsc` per router or a templated apply.
## Phase 4 — edge (RouterOS 6.49.18) *(contingent, likely "leave native")*
**Body TBD.** Default expectation from Phase 1 framing point #3: **edge stays native IPv6.** edge is one hop from core and one hop from TWC — collapsing it changes nothing a user sees. Only revisit if Task 1.7 specifically finds ROS6 6PE both works and is needed. If edge stays native, document that the v6 path is "labeled across the ROS7 core, native for the edge↔TWC hop" so the next operator isn't surprised.
---
## Rollback (whole project)
- **Phase 1:** delete VMs. Nothing to roll back.
- **Phase 2/3:** per-router inverse delta from Task 1.7, applied in reverse order (towers before core). Native v6 forwarding + OSPFv3 are never removed during pilot/rollout, so the floor is always "back to today's working native v6."
- **Trigger:** any v6 customer outage, `traceroute6` black-holing (not just collapsing), or MTU loss that won't resolve.
- The firewall, DNS, and home-router work already done (forward `accept src 2606:1c80::/32`, `v6-dns` resolvers, home `/56`) are **independent of this plan** and are not touched by any phase.
---
## Alternatives (if Phase 1 = NO-GO)
| Option | What it gives | Cost / risk |
|---|---|---|
| **Do nothing** | v6 traceroute keeps showing the core (internal hops currently appear as `*` — they don't even reply). Everything works. | Zero. The collapse is cosmetic. |
| **VPNv6** | IPv6 in a VRF over the v4 core — *is* labeled, so traceroute collapses. ROS7 documents `vpnv6`. | Large: every router's customer v6 (addresses, pools, PPP profiles, firewall, the home `/56`) moves into a VRF. Much bigger blast radius than 6PE. |
| **Make internal hops reply instead of hiding them** | Opposite of the goal, but "honest" traceroute — each router answers with ICMPv6 time-exceeded. | Small `chain=input` tweak. Only relevant if the real annoyance is the `* * *` lines, not the hop count. |
**Recommendation if NO-GO:** do nothing. The IPv4 collapse is a side-effect of MPLS existing for real routing reasons; chasing the same cosmetics for IPv6 via a VRF migration is not worth the blast radius. Revisit only if MikroTik documents real 6PE support in a later ROS7 release.
---
## Open questions for graham (resolve before Phase 2, ideally before Phase 1)
1. Is collapsing v6 traceroute worth a multi-window backbone change, or is it "nice to have"? (Sets how hard to push past a marginal Phase 1 result.)
2. Proxmox capacity at home for 5 throwaway CHR VMs — OK to spin up?
3. edge's eventual ROS6→ROS7 upgrade — if that's on the roadmap anyway, Phase 4 should wait for it rather than attempt ROS6 6PE.

3
gaiia/.direnv Normal file
View file

@ -0,0 +1,3 @@
export GAIIA_KEY=gaiia_sk_live_955b27e3680747a69ad40b403a912155_b0d1be3c
export EXPIRE_WF_ID=workflow_gbL1BTRA5Xt4WkMy2uRLdW

2
gaiia/.envrc Normal file
View file

@ -0,0 +1,2 @@
export GAIIA_KEY=gaiia_sk_live_955b27e3680747a69ad40b403a912155_b0d1be3c

17
gaiia/.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
__pycache__/
*.py[cod]
*.egg-info/
.eggs/
build/
dist/
.venv/
venv/
.env
.envrc.local
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/
*.swp
.DS_Store

21
gaiia/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Graham McIntire
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

198
gaiia/README.md Normal file
View file

@ -0,0 +1,198 @@
# gaiia-py
Python client for the [gaiia](https://app.gaiia.com/docs/overview) GraphQL API
and webhooks. Thin wrapper around the official GraphQL surface — no codegen, no
opinionated ORM. You write GraphQL, the client handles auth, pagination, rate
limits, GlobalID encoding, and webhook signature verification.
## Install
```bash
pip install gaiia
```
Requires Python 3.10+. Depends on `httpx`.
## Quick start
```python
from gaiia import GaiiaClient
with GaiiaClient(api_key="gaiia_sk_live_...", timezone="America/Chicago") as g:
data = g.query(
"query Q($first: Int!) { technicians(first: $first) { nodes { id firstName lastName } } }",
{"first": 10},
)
for t in data["technicians"]["nodes"]:
print(t)
```
If `api_key=` is omitted, the client reads `GAIIA_KEY` from the environment.
## Mutations
The `mutate()` helper unwraps the mutation result and raises
`GaiiaMutationError` when the response contains validation errors under
`data.<mutation>.errors`:
```python
res = g.mutate(
"""
mutation A($i: AssignTechnicianToWorkOrderInput!) {
assignTechnicianToWorkOrder(input: $i) {
workOrder { id status startDate }
errors { code message }
}
}
""",
"assignTechnicianToWorkOrder",
{"i": {
"workOrderId": "work_order_...",
"technicianId": "technician_...",
"startDate": "2026-05-12T14:00:00Z",
}},
)
print(res["workOrder"]["id"])
```
## Pagination
`iter_nodes()` paginates a Relay Connection automatically. Your query must
accept an `$after: String` variable and pass it to the paginated field:
```python
for tech in g.iter_nodes(
"""
query Q($after: String) {
technicians(first: 50, after: $after) {
nodes { id firstName lastName }
pageInfo { hasNextPage endCursor }
}
}
""",
"technicians",
):
print(tech["firstName"], tech["lastName"])
```
Default page size is 50, max 250 (per the gaiia docs). Some fields cap lower —
check the field's reference page.
For bulk extracts (millions of rows, reporting, analytics), gaiia recommends
[Snowflake](https://app.gaiia.com/docs/rate-limits) rather than the GraphQL
API.
## Rate limiting
After every call, `client.last_rate_limit` is populated from the response
headers (`X-Rate-Limit-*`). If gaiia returns a `RATE_LIMITED` error, the
client raises `GaiiaRateLimitedError` with `limit`, `used`, `remaining`, and
`retry_at`:
```python
from gaiia import GaiiaClient, GaiiaRateLimitedError
with GaiiaClient(max_retries=2) as g:
try:
g.query("{ ... }")
except GaiiaRateLimitedError as e:
print("retry after", e.retry_at)
```
Setting `max_retries` > 0 makes the client sleep until `retry_at` and retry up
to that many times before re-raising.
## GlobalIDs
```python
from gaiia import encode_global_id, decode_global_id
encode_global_id("Account", "3c3b1978-6a68-4a13-bdc2-2d51c8ef7519")
# 'account_8rnXNuR5sKP5uNwoPL41Zp'
decode_global_id("account_8rnXNuR5sKP5uNwoPL41Zp")
# ('account', '3c3b1978-6a68-4a13-bdc2-2d51c8ef7519')
```
The encoding follows gaiia's `short-uuid` setup: Flickr Base-58 of the UUID's
128-bit integer, left-padded to 22 chars, prefixed with the snake-cased type.
## Webhooks
```python
from gaiia import verify_webhook_signature, WebhookSignatureError
try:
verify_webhook_signature(
header_value=request.headers["X-Gaiia-Webhook-Signature"],
raw_body=request.body_bytes, # the exact bytes gaiia signed
secret=os.environ["GAIIA_WEBHOOK_SECRET"],
tolerance_seconds=300,
)
except WebhookSignatureError:
return 400
```
The verifier:
- Parses `t=...,v1=...` (multiple `v1=` allowed for key rotation),
- Computes `HMAC-SHA256(secret, "${t}.${rawBody}")`,
- Constant-time compares against each `v1=` candidate,
- Rejects timestamps outside the tolerance window.
See `examples/webhook_flask.py` for a complete handler.
## Async
`AsyncGaiiaClient` mirrors `GaiiaClient` for `asyncio` code:
```python
import asyncio
from gaiia import AsyncGaiiaClient
async def main():
async with AsyncGaiiaClient(timezone="America/Chicago") as g:
data = await g.query("{ technicians(first: 5) { nodes { id firstName lastName } } }")
print(data)
asyncio.run(main())
```
`async for` over `iter_nodes()` paginates lazily.
## Timezones
gaiia stores all timestamps in UTC. Pass `timezone="America/Chicago"` (or
whatever IANA TZ applies) at the client level — it becomes the `x-timezone`
header on every request, which gaiia uses for scheduling/availability logic.
Override per-call with `g.query(..., timezone="America/Phoenix")`.
For display, query the work order's `location.timezone` and format times in
that zone — not the caller's.
## Error model
| Class | When it's raised |
| ------------------------ | ---------------------------------------------------------------------- |
| `GaiiaTransportError` | Network failure, non-JSON response, HTTP 5xx. |
| `GaiiaAPIError` | Top-level GraphQL `errors[]` returned (schema/auth/permission). |
| `GaiiaRateLimitedError` | Subclass of `GaiiaAPIError` for `RATE_LIMITED`. |
| `GaiiaMutationError` | Mutation `data.<m>.errors[]` is non-empty (validation/business rule). |
All inherit from `GaiiaError`.
## Development
```bash
git clone https://github.com/grahammcintire/gaiia-py
cd gaiia-py
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check .
mypy src/gaiia
```
## License
MIT.

View file

@ -0,0 +1,116 @@
{
"name": "Account Status - RADIUS Suspension",
"description": "EVENT: When account status changes to SUSPENDED or INACTIVE, update RADIUS usergroup to suspended. When it returns to ACTIVE, restore normal groups.",
"trigger": {
"type": "EVENT",
"events": ["account.status_updated"],
"nextSteps": [{ "slug": "get-account" }]
},
"steps": [
{
"name": "Get account status and products",
"slug": "get-account",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output, state }) => {\n const acct = output.response.account;\n if (!acct) {\n workflow.fail('Account not found', 'ACCOUNT_NOT_FOUND');\n }\n const status = acct.status?.name?.toUpperCase() || '';\n const products = [];\n for (const sub of (acct.billingSubscriptions?.nodes || [])) {\n const p = sub.productVersion?.product;\n if (p && !products.includes(p.id)) {\n products.push(p.id);\n }\n }\n return { status, products, accountId: acct.id };\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query GetAccount($id: GlobalID!) {\n account(id: $id) {\n id\n status { name }\n billingSubscriptions {\n nodes {\n productVersion {\n product { id name }\n }\n }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => ({ id: state.input.objectId })"
}
],
"nextSteps": [{ "slug": "check-radius-exists" }]
},
{
"name": "Check if account has RADIUS usernames",
"slug": "check-radius-exists",
"type": "TYPESCRIPT_CODE",
"nodeSlug": "execute-typescript-code",
"allowSkipExecution": false,
"outputMapper": "({ output, state }) => {\n const acct = state.steps['get-account'].output;\n acct.hasRadius = output && output.length > 0;\n return acct;\n}",
"inputs": [
{
"key": "function",
"type": "FUNCTION",
"value": "async ({ state, utils }) => {\n const { accountId } = state.steps['get-account'].output;\n const existingData = await utils.integration.datastore.getItem(`radiusAccount.${accountId}`);\n const accounts = existingData ? JSON.parse(existingData) : [];\n return accounts;\n}"
}
],
"nextSteps": [{ "slug": "should-suspend" }]
},
{
"name": "Should suspend?",
"slug": "should-suspend",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const { status, hasRadius } = state.steps['check-radius-exists'].output;\n return hasRadius && (status === 'SUSPENDED' || status === 'INACTIVE');\n}",
"elseSteps": [{ "slug": "should-reactivate" }],
"thenSteps": [{ "slug": "update-groups-suspended" }]
},
{
"name": "Update RADIUS groups to SUSPENDED",
"slug": "update-groups-suspended",
"type": "GAIIA_START_WORKFLOW_EXECUTION",
"nodeSlug": "start-workflow-execution",
"allowSkipExecution": false,
"inputs": [
{
"key": "workflowId",
"type": "LITERAL",
"value": "string:workflow_fLXTNCvridPqwEEXxf3CK1"
},
{
"key": "input",
"type": "FUNCTION",
"value": "({ state }) => {\n const { accountId, products, status } = state.steps['check-radius-exists'].output;\n return {\n accountId,\n status,\n productIds: products\n };\n}"
},
{
"key": "fireAndForget",
"type": "LITERAL",
"value": "boolean:true"
}
],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Should reactivate?",
"slug": "should-reactivate",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const { status, hasRadius } = state.steps['check-radius-exists'].output;\n return hasRadius && status === 'ACTIVE';\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "update-groups-active" }]
},
{
"name": "Update RADIUS groups to ACTIVE",
"slug": "update-groups-active",
"type": "GAIIA_START_WORKFLOW_EXECUTION",
"nodeSlug": "start-workflow-execution",
"allowSkipExecution": false,
"inputs": [
{
"key": "workflowId",
"type": "LITERAL",
"value": "string:workflow_fLXTNCvridPqwEEXxf3CK1"
},
{
"key": "input",
"type": "FUNCTION",
"value": "({ state }) => {\n const { accountId, products } = state.steps['check-radius-exists'].output;\n return {\n accountId,\n status: 'ACTIVE',\n productIds: products\n };\n}"
},
{
"key": "fireAndForget",
"type": "LITERAL",
"value": "boolean:true"
}
],
"nextSteps": [{ "slug": "end" }]
}
]
}

View file

@ -0,0 +1,78 @@
{
"name": "Cleanup Stale BYOD Items",
"description": "SCHEDULED (nightly 2am): Unassigns Customer Provided Router items that have no active IP assignment. Keeps items with active IPs intact.",
"trigger": {
"type": "SCHEDULE",
"cron": "0 2 * * *",
"nextSteps": [{ "slug": "find-stale-byod" }]
},
"steps": [
{
"name": "Find stale BYOD items",
"slug": "find-stale-byod",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const items = output.response.inventoryItems.nodes;\n const stale = [];\n for (const item of items) {\n const assigns = item.ipAddressAssignments?.nodes || [];\n const hasActive = assigns.some((a) => a.isActive !== false);\n if (!hasActive) {\n const assignee = item.assignation?.assignee || {};\n if (assignee.__typename === 'Account' && assignee.id) {\n stale.push({ inventoryId: item.id, accountId: assignee.id, accountName: assignee.name || '' });\n }\n }\n }\n return stale;\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query {\n inventoryItems(\n first: 250\n filters: { modelId: { equals: \"inventory_model_522SxVU9VvPaMXYqWNbfbx\" }, assigneeType: { equals: ACCOUNT } }\n ) {\n nodes {\n id\n ipAddressAssignments {\n nodes { ipAddress macAddress isActive }\n }\n assignation {\n assignee { __typename ... on Account { id name } }\n }\n }\n totalCount\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ secrets, state, utils, variables }) => ({})"
}
],
"nextSteps": [{ "slug": "if-any-stale" }]
},
{
"name": "Any stale items?",
"slug": "if-any-stale",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const items = state.steps['find-stale-byod'].output;\n return Array.isArray(items) && items.length > 0;\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "unassign-stale" }]
},
{
"name": "Unassign each stale item",
"slug": "unassign-stale",
"type": "PARALLEL_LOOP",
"nodeSlug": "parallel-loop",
"maxConcurrentIterations": 10,
"inputs": [
{
"key": "iterable",
"type": "FUNCTION",
"value": "({ state }) => state.steps['find-stale-byod'].output"
}
],
"initialSteps": [{ "slug": "unassign-one" }],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Unassign from account",
"slug": "unassign-one",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"parentParallelLoopSlug": "unassign-stale",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation UnassignItem($input: UnassignCustomerOwnedItemInput!) {\n unassignCustomerOwnedItem(input: $input) {\n customerOwnedItem { id }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const item = state.currentParallelLoopIterations['unassign-stale'];\n return { input: { id: item.inventoryId } };\n}"
}
],
"nextSteps": [{ "slug": "unassign-stale-end" }]
}
]
}

143
gaiia/clear_all_ips.py Normal file
View file

@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""
Clear all MAC->IP address assignments from customer inventory items.
Two-phase approach:
1. List all active IP assignments via paginated V1 public API
2. Expire each one via startWorkflowExecution mutation
Before running:
1. Create the "Expire Single IP Assignment" workflow in Gaiia UI
(use expire_one_workflow.json)
2. Copy its workflow ID and set EXPIRE_WF_ID below
3. source .envrc
4. uv run python3 clear_all_ips.py --dry-run (preview)
5. uv run python3 clear_all_ips.py (execute)
"""
from gaiia import GaiiaClient, GaiiaMutationError
import json
import os
import sys
import time
EXPIRE_WF_ID = os.environ.get("EXPIRE_WF_ID", "")
def list_all_assignments(client):
"""Paginate through all inventory items and return active MAC->IP pairs."""
all_assignments = []
after = None
page = 0
while True:
page += 1
query = """
query Q($after: String) {
inventoryItems(
filters: {assigneeType: {equals: ACCOUNT}}
first: 250
after: $after
) {
nodes {
ipAddressAssignments {
nodes { ipAddress macAddress isActive }
}
}
pageInfo { hasNextPage endCursor }
totalCount
}
}
"""
data = client.query(query, {"after": after} if after else {})
result = data['inventoryItems']
if page == 1:
print(f"Total inventory items: {result['totalCount']}")
for item in result['nodes']:
for a in item.get('ipAddressAssignments', {}).get('nodes', []):
if a.get('isActive') != False:
all_assignments.append({
'macAddress': a['macAddress'],
'ipAddress': a['ipAddress']
})
page_info = result['pageInfo']
sys.stderr.write(f"\r Page {page}: {len(all_assignments)} assignments so far...")
sys.stderr.flush()
if not page_info.get('hasNextPage'):
break
after = page_info['endCursor']
sys.stderr.write("\n")
return all_assignments
def expire_one(client, mac_address, ip_address, dry_run=False):
"""Call the Expire Single IP Assignment workflow via startWorkflowExecution."""
if dry_run:
print(f" [DRY-RUN] {mac_address} -> {ip_address}")
return True
mutation = """
mutation Expire($input: StartWorkflowExecutionInput!) {
startWorkflowExecution(input: $input) {
workflowExecution { id status }
}
}
"""
try:
client.query(mutation, {
"input": {
"workflowId": EXPIRE_WF_ID,
"input": {"macAddress": mac_address, "ipAddress": ip_address}
}
})
return True
except GaiiaMutationError as e:
print(f" FAILED {mac_address} -> {ip_address}: {e}")
return False
def main():
if not EXPIRE_WF_ID:
print("ERROR: Set EXPIRE_WF_ID env var to the workflow ID of 'Expire Single IP Assignment'")
print("Example: EXPIRE_WF_ID=workflow_xxx uv run python3 clear_all_ips.py --dry-run")
sys.exit(1)
dry_run = "--dry-run" in sys.argv
with GaiiaClient(timezone='America/Chicago') as g:
print("Listing all active IP assignments...")
assignments = list_all_assignments(g)
print(f"\nFound {len(assignments)} active IP assignments")
if dry_run:
print("\n--dry-run mode: showing what would be expired --")
else:
print(f"\nExpiring via workflow {EXPIRE_WF_ID}...")
succeeded = 0
failed = 0
for i, a in enumerate(assignments):
if not dry_run:
sys.stderr.write(f"\r {i+1}/{len(assignments)}: {a['macAddress']} -> {a['ipAddress']}")
sys.stderr.flush()
if expire_one(g, a['macAddress'], a['ipAddress'], dry_run):
succeeded += 1
else:
failed += 1
if not dry_run:
time.sleep(0.1) # avoid rate limiting
if not dry_run:
sys.stderr.write("\n")
print(f"\nDone: {succeeded} succeeded, {failed} failed")
if dry_run:
print("(nothing was changed — remove --dry-run to execute)")
if __name__ == '__main__':
main()

View file

@ -0,0 +1,85 @@
{
"name": "Clear All IP Assignments",
"description": "ONE-SHOT: Expires all MAC→IP address assignments across all customer inventory items. Run once, then delete this workflow.",
"trigger": {
"type": "MANUAL",
"nextSteps": [
{ "slug": "find-assigned-items" }
]
},
"steps": [
{
"name": "Find all inventory items assigned to accounts",
"slug": "find-assigned-items",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const items = output.response.inventoryItems.nodes;\n const withIps = [];\n for (const item of items) {\n const assignments = item.ipAddressAssignments?.nodes || [];\n for (const a of assignments) {\n if (a.isActive !== false) {\n withIps.push({\n macAddress: a.macAddress,\n ipAddress: a.ipAddress\n });\n }\n }\n }\n return withIps;\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query AllAssignedItems($filters: InventoryItemsQueryFilters, $first: Int, $after: String) {\n inventoryItems(filters: $filters, first: $first, after: $after) {\n nodes {\n ipAddressAssignments {\n nodes {\n ipAddress\n macAddress\n isActive\n }\n }\n }\n pageInfo { hasNextPage endCursor }\n totalCount\n }\n}"
},
{
"key": "variables",
"type": "LITERAL",
"value": "json:{\"filters\":{\"assigneeType\":{\"equals\":\"ACCOUNT\"}},\"first\":250}"
}
],
"nextSteps": [
{ "slug": "if-any-assignments" }
]
},
{
"name": "Any assignments to clear?",
"slug": "if-any-assignments",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const items = state.steps['find-assigned-items'].output;\n return items && items.length > 0;\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "expire-assignments" }]
},
{
"name": "Expire each assignment",
"slug": "expire-assignments",
"type": "PARALLEL_LOOP",
"nodeSlug": "parallel-loop",
"maxConcurrentIterations": 10,
"inputs": [
{
"key": "iterable",
"type": "FUNCTION",
"value": "({ state }) => state.steps['find-assigned-items'].output"
}
],
"initialSteps": [
{ "slug": "expire-one" }
],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Expire single assignment",
"slug": "expire-one",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"parentParallelLoopSlug": "expire-assignments",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation ExpireAssignation($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations {\n macAddress\n ipAddress\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const item = state.currentParallelLoopIterations['expire-assignments'];\n return {\n input: {\n assignations: {\n macAddress: item.macAddress,\n ipAddress: item.ipAddress,\n expired: true\n }\n }\n };\n}"
}
],
"nextSteps": [
{ "slug": "expire-assignments-end" }
]
}
]
}

View file

@ -0,0 +1,29 @@
{
"name": "Test - List IPs",
"trigger": {
"type": "MANUAL",
"nextSteps": [{ "slug": "list" }]
},
"steps": [
{
"name": "List assigned items",
"slug": "list",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query { inventoryItems(filters: {assigneeType: {equals: ACCOUNT}}, first: 5) { totalCount } }"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ secrets, state, utils, variables }) => ({})"
}
],
"nextSteps": [{ "slug": "end" }]
}
]
}

View file

@ -0,0 +1,27 @@
{
"name": "Test - Expire One IP",
"description": "Bare minimum V0 expire test with hardcoded MAC+IP",
"trigger": { "type": "MANUAL", "nextSteps": [{ "slug": "expire" }] },
"steps": [
{
"name": "Expire",
"slug": "expire",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation ExpireOne($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "LITERAL",
"value": "json:{\"input\":{\"assignations\":{\"macAddress\":\"DC:9F:DB:92:0D:AA\",\"ipAddress\":\"10.10.16.74\",\"expired\":true}}}"
}
],
"nextSteps": [{ "slug": "end" }]
}
]
}

249
gaiia/compare_pppoe.py Normal file
View file

@ -0,0 +1,249 @@
"""Compare PPPoE active sessions from climax MikroTik against Gaiia accounts.
Checks:
1. Is the PPPoE username found as a Gaiia account?
2. Does the Gaiia account's inventory item IP match the PPPoE IP?
Usage: cd /Users/graham/dev/network/gaiia && uv run python compare_pppoe.py
"""
from __future__ import annotations
import os
import sys
from typing import Any
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from gaiia.client import GaiiaClient
# PPPoE active sessions from climax (2026-07-02)
PPPOE_SESSIONS: list[dict[str, str]] = [
{"name": "charlesboone", "address": "100.64.7.247", "mac": "C8:9E:43:E3:99:EA"},
{"name": "amberprater", "address": "100.64.7.243", "mac": "6C:CD:D6:2C:31:94"},
{"name": "matthewgoodwin", "address": "100.64.7.241", "mac": "34:98:B5:44:42:A4"},
{"name": "robbymccollom", "address": "100.64.7.239", "mac": "D8:B3:70:92:0C:C6"},
{"name": "gordonhamilton", "address": "100.64.7.237", "mac": "A0:63:91:3A:29:B1"},
{"name": "chasewilliams", "address": "100.64.7.235", "mac": "38:94:ED:5B:82:E9"},
{"name": "cynthiajones", "address": "100.64.7.233", "mac": "E4:FA:C4:56:CA:D9"},
{"name": "janicealexander", "address": "100.64.7.225", "mac": "E0:D3:62:E8:65:C5"},
{"name": "michaelray", "address": "204.110.188.38", "mac": "9C:05:D6:59:FF:63"},
{"name": "marysmelser", "address": "100.64.7.218", "mac": "00:01:9F:33:54:B9"},
{"name": "eddieyarbrough", "address": "100.64.7.216", "mac": "10:C3:7B:42:D8:B8"},
{"name": "timgilbert", "address": "100.64.7.213", "mac": "94:A6:7E:EE:98:B8"},
{"name": "chadwhitsell", "address": "100.64.7.211", "mac": "04:95:E6:76:3C:91"},
{"name": "donnacampbell", "address": "100.64.7.209", "mac": "74:FE:CE:3A:6D:AC"},
{"name": "bryangoulart", "address": "100.64.7.205", "mac": "8C:90:2D:E9:90:7A"},
{"name": "crystalharney", "address": "100.64.7.201", "mac": "98:25:4A:04:B6:43"},
{"name": "johnvayo", "address": "100.64.7.197", "mac": "98:25:4A:04:B5:89"},
{"name": "richardbarragan", "address": "100.64.7.193", "mac": "8C:90:2D:E9:98:E1"},
{"name": "carolstrickland", "address": "100.64.7.189", "mac": "E8:DA:00:18:54:45"},
{"name": "timbagert", "address": "100.64.7.140", "mac": "74:FE:CE:3A:6D:8E"},
{"name": "douggarber", "address": "100.64.7.136", "mac": "00:01:9F:33:79:A9"},
{"name": "janetkern", "address": "100.64.7.61", "mac": "28:C6:8E:C1:06:AF"},
{"name": "gregmcintire", "address": "100.64.7.59", "mac": "66:62:8B:10:10:65"},
{"name": "jenniferboon", "address": "100.64.7.35", "mac": "98:25:4A:04:BA:EA"},
{"name": "glendabeauchamp", "address": "100.64.7.15", "mac": "8C:90:2D:E9:97:85"},
{"name": "jmichaelculverhouse", "address": "100.64.7.250", "mac": "34:98:B5:31:D3:3A"},
{"name": "elviraquezada", "address": "100.64.7.249", "mac": "E0:C2:50:47:DC:1A"},
{"name": "tammieventris", "address": "100.64.7.232", "mac": "98:25:4A:04:A5:E1"},
{"name": "taekim", "address": "204.110.188.42", "mac": "9C:5C:8E:44:85:B4"},
{"name": "ruthfengler", "address": "100.64.7.246", "mac": "A0:63:91:25:F6:E5"},
{"name": "buddyswan", "address": "100.64.7.245", "mac": "00:01:9F:34:D2:21"},
{"name": "rhondabolton", "address": "100.64.7.231", "mac": "A0:04:60:94:D5:FF"},
]
# Known PPPoE-name → Gaiia-displayName mismatches
MANUAL_MAP: dict[str, str] = {
"bryangoulart": "Brian Goulart", # typo in PPPoE username
}
def flatten(name: str) -> str:
"""Lowercase and remove spaces/punctuation from a name."""
return name.lower().replace(" ", "").replace("-", "").replace("'", "")
def fetch_all_accounts(client: GaiiaClient) -> list[dict[str, Any]]:
"""Fetch all Gaiia accounts (id, readableId, displayName)."""
query = """
query($after: String) {
accounts(first: 50, after: $after) {
nodes { id readableId displayName }
pageInfo { hasNextPage endCursor }
totalCount
}
}
"""
accounts: list[dict[str, Any]] = []
cursor: str | None = None
has_next = True
while has_next:
variables: dict[str, Any] = {}
if cursor:
variables["after"] = cursor
data = client.query(query, variables)
conn = data["accounts"]
accounts.extend(conn.get("nodes") or [])
has_next = conn["pageInfo"]["hasNextPage"]
cursor = conn["pageInfo"].get("endCursor")
print(f" Fetched {len(accounts)}/{conn.get('totalCount', '?')} accounts...",
file=sys.stderr)
return accounts
def fetch_account_inventory(
client: GaiiaClient, account_id: str
) -> list[dict[str, Any]]:
"""Fetch inventory items for a specific account, including IP addresses."""
query = """
query($id: GlobalID!) {
account(id: $id) {
assignedInventoryItems(first: 10) {
nodes {
inventoryItem {
id
ipAddressV4
ipAddressV6
model {
name
manufacturer { name }
}
}
}
}
}
}
"""
data = client.query(query, {"id": account_id})
acct = data.get("account")
if not acct:
return []
items = acct.get("assignedInventoryItems", {})
assignations = items.get("nodes") or []
# Extract the actual inventory items from assignations
result = []
for assign in assignations:
item = assign.get("inventoryItem")
if item:
result.append(item)
return result
def main() -> None:
api_key = os.environ.get("GAIIA_KEY")
if not api_key:
print("Error: GAIIA_KEY env var not set", file=sys.stderr)
sys.exit(1)
client = GaiiaClient(api_key, timezone="America/Chicago")
print("Fetching all accounts from Gaiia...", file=sys.stderr)
accounts = fetch_all_accounts(client)
print(f"Total: {len(accounts)} accounts\n", file=sys.stderr)
# Build lookup: flattened displayName → account
by_flat: dict[str, dict[str, Any]] = {}
for a in accounts:
by_flat[flatten(a.get("displayName", ""))] = a
# --- Step 1: Match PPPoE names to Gaiia accounts ---
print("=" * 75)
print("CLIMAX PPPoE → GAIIA ACCOUNT MATCHING")
print("=" * 75)
matched: list[dict[str, Any]] = []
unmatched: list[dict[str, Any]] = []
for session in PPPOE_SESSIONS:
pname = session["name"]
flat = flatten(pname)
account = by_flat.get(flat)
# Try manual map
if not account and pname in MANUAL_MAP:
mapped_flat = flatten(MANUAL_MAP[pname])
account = by_flat.get(mapped_flat)
session["_account"] = account
if account:
matched.append(session)
else:
unmatched.append(session)
print(f"{'PPPoE':<25} {'PPPoE IP':<18} {'Gaiia Name':<25} {'Account ID':<8}")
print("-" * 78)
for s in matched:
acc = s["_account"]
print(f"{s['name']:<25} {s['address']:<18} {acc['displayName']:<25} {acc['readableId']:<8}")
for s in unmatched:
print(f"{s['name']:<25} {s['address']:<18} {'(not in Gaiia)':<25} {'-':<8}")
print(f"\nMatched: {len(matched)} | Not in Gaiia: {len(unmatched)}")
if unmatched:
print("Not in Gaiia:")
for s in unmatched:
print(f" - {s['name']} ({s['address']})")
# --- Step 2: Compare IP addresses from inventory items ---
print(f"\n{'=' * 75}")
print("IP ADDRESS COMPARISON (PPPoE active IP vs Gaiia inventory item IP)")
print("=" * 75)
ip_matched = 0
ip_mismatched = 0
ip_no_inventory = 0
ip_not_checked = 0
for s in matched:
acc = s["_account"]
pppoe_ip = s["address"]
pppoe_name = s["name"]
# Fetch inventory items for this account
items = fetch_account_inventory(client, acc["id"])
s["_items"] = items
if not items:
print(f"{pppoe_name:<25} {pppoe_ip:<18} (no inventory items in Gaiia)")
ip_no_inventory += 1
continue
# Check if any inventory item has an IP matching the PPPoE IP
found_match = False
for item in items:
item_ip = item.get("ipAddressV4")
if item_ip == pppoe_ip:
model = item.get("model", {})
model_name = model.get("name", "?") if model else "?"
print(f"{pppoe_name:<25} {pppoe_ip:<18} MATCH (item: {model_name})")
ip_matched += 1
found_match = True
break
if not found_match:
item_ips = [i.get("ipAddressV4") for i in items if i.get("ipAddressV4")]
if item_ips:
print(f"{pppoe_name:<25} {pppoe_ip:<18} MISMATCH (Gaiia has: {', '.join(item_ips)})")
ip_mismatched += 1
else:
print(f"{pppoe_name:<25} {pppoe_ip:<18} NO IP SET (has inventory items but no IPs)")
ip_no_inventory += 1
print(f"\n--- Summary ---")
print(f"Accounts in Gaiia: {len(matched)}/{len(PPPOE_SESSIONS)}")
print(f"Not in Gaiia: {len(unmatched)}")
print(f"IP matches PPPoE: {ip_matched}")
print(f"IP mismatch (different IP): {ip_mismatched}")
print(f"No inventory / no IP in Gaiia: {ip_no_inventory}")
if unmatched:
print(f"\nAccounts missing from Gaiia (need investigation):")
for s in unmatched:
mac = s.get("mac", "")
print(f" {s['name']} IP={s['address']} MAC={mac}")
client.close()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,83 @@
{
"name": "Daily Preseem Site Inventory Sync",
"description": "SCHEDULED (daily 3am): Iterates all network sites, extracts MACs from assigned equipment fields, syncs each item to Preseem access points with its site name as tower.",
"trigger": {
"type": "SCHEDULE",
"cron": "0 3 * * *",
"nextSteps": [{ "slug": "fetch-sites" }]
},
"steps": [
{
"name": "Fetch all network sites with assigned equipment",
"slug": "fetch-sites",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const sites = output.response.networkSites.nodes;\n const items = [];\n for (const site of sites) {\n for (const assign of (site.assignedInventoryItems?.nodes || [])) {\n const inv = assign.inventoryItem || {};\n const model = inv.model?.name || '';\n // Extract MAC from any field whose name contains 'mac' (case-insensitive)\n const macs = [];\n for (const f of (inv.fields?.nodes || [])) {\n const fieldName = (f.modelField?.name || '').toLowerCase();\n const data = (f.data || '').trim();\n if (fieldName.includes('mac') && data && data !== '00:00:00:00:00:00') {\n // Normalize MAC: strip '1:' prefix, pad segments, uppercase\n const cleaned = data.startsWith('1:') ? data.slice(2) : data;\n const parts = cleaned.split(':');\n if (parts.length === 6) {\n const normalized = parts\n .map((p) => p.padStart(2, '0').toUpperCase())\n .join(':');\n macs.push(normalized);\n }\n }\n }\n if (macs.length > 0) {\n items.push({\n inventoryId: inv.id,\n model,\n siteName: site.name,\n macs: [...new Set(macs)]\n });\n }\n }\n }\n return items;\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query {\n networkSites(first: 250) {\n nodes {\n id\n name\n assignedInventoryItems {\n nodes {\n inventoryItem {\n id\n model { name }\n ipAddressV4\n fields {\n nodes { data modelField { name } }\n }\n }\n }\n }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ secrets, state, utils, variables }) => ({})"
}
],
"nextSteps": [{ "slug": "if-any-items" }]
},
{
"name": "Any items to sync?",
"slug": "if-any-items",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const items = state.steps['fetch-sites'].output;\n return Array.isArray(items) && items.length > 0;\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "sync-to-preseem" }]
},
{
"name": "Sync each item to Preseem",
"slug": "sync-to-preseem",
"type": "PARALLEL_LOOP",
"nodeSlug": "parallel-loop",
"maxConcurrentIterations": 10,
"inputs": [
{
"key": "iterable",
"type": "FUNCTION",
"value": "({ state }) => state.steps['fetch-sites'].output"
}
],
"initialSteps": [{ "slug": "preseem-put-ap" }],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "PUT to Preseem access points",
"slug": "preseem-put-ap",
"type": "PRESEEM_REST_REQUEST",
"nodeSlug": "preseem-rest-request",
"parentParallelLoopSlug": "sync-to-preseem",
"allowSkipExecution": false,
"inputs": [
{
"key": "method",
"type": "LITERAL",
"value": "string:PUT"
},
{
"key": "path",
"type": "FUNCTION",
"value": "({ state }) => {\n const item = state.currentParallelLoopIterations['sync-to-preseem'];\n return `/access_points/${item.inventoryId}`;\n}"
},
{
"key": "body",
"type": "FUNCTION",
"value": "({ state }) => {\n const item = state.currentParallelLoopIterations['sync-to-preseem'];\n return {\n id: item.inventoryId,\n name: `${item.model} (${item.macs[0]})`,\n tower: item.siteName\n };\n}"
}
],
"nextSteps": [{ "slug": "sync-to-preseem-end" }]
}
]
}

View file

@ -0,0 +1,227 @@
{
"name": "DHCP Option 82 Auth",
"description": "WEBHOOK: Always upserts IP. If Option 82 present AND MAC linked to an account, syncs all inventoried MACs to Preseem with plan details.",
"trigger": {
"type": "WEBHOOK",
"inputJsonSchema": {
"type": "object",
"required": ["bound", "mac", "ip", "server", "remoteId"],
"properties": {
"bound": { "type": "string" },
"mac": { "type": "string" },
"ip": { "type": "string" },
"server": { "type": "string" },
"remoteId": { "type": "string" }
}
},
"nextSteps": [{ "slug": "check-inventory" }]
},
"steps": [
{
"name": "Find inventory item by MAC",
"slug": "check-inventory",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output, state }) => {\n const items = output.response.findInventoryItemsFromDeviceIdentifiers;\n // Accept items assigned to Accounts OR NetworkSites\n const assigned = items.filter(\n (item) => {\n const type = item.inventoryItem?.assignation?.assignee?.__typename;\n return type === 'Account' || type === 'NetworkSite';\n }\n );\n if (assigned.length === 0) return null;\n const inv = assigned[0].inventoryItem;\n const assignee = inv.assignation.assignee;\n return {\n assigneeId: assignee.id,\n assigneeType: assignee.__typename,\n assigneeName: assignee.name || ''\n };\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query FindItem($input: FindInventoryItemsFromDeviceIdentifiersQueryInput!) {\n findInventoryItemsFromDeviceIdentifiers(input: $input) {\n identifier\n inventoryItem {\n id\n model { name }\n assignation {\n assignee {\n __typename\n ... on Account { id name }\n }\n }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return { input: { identifiers: [formattedMac] } };\n}"
}
],
"nextSteps": [{ "slug": "is-connect" }]
},
{
"name": "Connect or disconnect?",
"slug": "is-connect",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n return state.input.bound === '1';\n}",
"elseSteps": [{ "slug": "expire-ip" }],
"thenSteps": [{ "slug": "has-account-connect" }]
},
{
"name": "Account found? (connect)",
"slug": "has-account-connect",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n return state.steps['check-inventory'].output != null;\n}",
"elseSteps": [{ "slug": "upsert-ip-no-account" }],
"thenSteps": [{ "slug": "upsert-ip-with-account" }]
},
{
"name": "Upsert IP (no account)",
"slug": "upsert-ip-no-account",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation UpsertIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: { macAddress: formattedMac, ipAddress: state.input.ip, expired: false }\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Upsert IP (with account)",
"slug": "upsert-ip-with-account",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation UpsertIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: { macAddress: formattedMac, ipAddress: state.input.ip, expired: false }\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "should-sync-preseem" }]
},
{
"name": "Has Option 82 and account?",
"slug": "should-sync-preseem",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'Account';\n}",
"elseSteps": [{ "slug": "should-sync-site" }],
"thenSteps": [{ "slug": "get-account-macs-and-plan" }]
},
{
"name": "Get all inventoried MACs and plan",
"slug": "get-account-macs-and-plan",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output, state }) => {\n const account = output.response.account;\n if (!account) {\n workflow.fail('Account not found', 'ACCOUNT_NOT_FOUND');\n }\n const macs = [];\n for (const assign of (account.assignedInventoryItems?.nodes || [])) {\n const inv = assign.inventoryItem || {};\n for (const f of (inv.fields?.nodes || [])) {\n const fieldName = (f.modelField?.name || '').toLowerCase();\n const data = (f.data || '').trim();\n if (fieldName.includes('mac') && data && data !== '00:00:00:00:00:00') {\n const cleaned = data.startsWith('1:') ? data.slice(2) : data;\n const parts = cleaned.split(':');\n if (parts.length === 6) {\n macs.push(parts.map((p) => p.padStart(2, '0').toUpperCase()).join(':'));\n }\n }\n }\n for (const a of (inv.ipAddressAssignments?.nodes || [])) {\n if (a.macAddress) macs.push(a.macAddress.toUpperCase());\n }\n }\n const plan = (account.billingSubscriptions?.nodes || [])\n .map((s) => s.productVersion?.product?.slug)\n .find(Boolean) || '';\n return { macs: [...new Set(macs)], plan, gaiiaId: account.readableId };\n}",
"inputs": [
{
"key": "query",
"type": "FUNCTION",
"value": "({ state }) => {\n const accountId = state.steps['check-inventory'].output.accountId;\n return `query { account(id: \"${accountId}\") { readableId assignedInventoryItems { nodes { inventoryItem { fields { nodes { data modelField { name } } } ipAddressAssignments { nodes { macAddress } } } } } billingSubscriptions { nodes { productVersion { product { slug } } } } } }`;\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => ({})"
}
],
"nextSteps": [{ "slug": "preseem-update" }]
},
{
"name": "Update Preseem",
"slug": "preseem-update",
"type": "PRESEEM_REST_REQUEST",
"nodeSlug": "preseem-rest-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "method",
"type": "LITERAL",
"value": "string:PUT"
},
{
"key": "path",
"type": "FUNCTION",
"value": "({ state }) => {\n const { gaiiaId } = state.steps['get-account-macs-and-plan'].output;\n return `/services/${gaiiaId}`;\n}"
},
{
"key": "body",
"type": "FUNCTION",
"value": "({ state }) => {\n const { gaiiaId, macs, plan } = state.steps['get-account-macs-and-plan'].output;\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const connectingMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toLowerCase();\n const framedIp = state.input.ip;\n const inventoriedMacs = macs.map((m) => m.toLowerCase());\n const attachments = inventoriedMacs\n .filter((m) => state.input.bound === '0' || m !== connectingMac)\n .map((m) => ({ cpe_mac: m }));\n if (state.input.bound === '1') {\n attachments.push({ cpe_mac: connectingMac, network_prefixes: [framedIp] });\n }\n return {\n id: String(gaiiaId),\n attachments,\n account: String(gaiiaId),\n package: plan\n };\n}"
}
],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Expire IP",
"slug": "expire-ip",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation ExpireIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: { macAddress: formattedMac, ipAddress: state.input.ip, expired: true }\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "should-sync-preseem-disconnect" }]
},
{
"name": "Network site with Option 82?",
"slug": "should-sync-site",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'NetworkSite';\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "preseem-site-update" }]
},
{
"name": "Update Preseem (site)",
"slug": "preseem-site-update",
"type": "PRESEEM_REST_REQUEST",
"nodeSlug": "preseem-rest-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "method",
"type": "LITERAL",
"value": "string:PUT"
},
{
"key": "path",
"type": "FUNCTION",
"value": "({ state }) => {\n const inv = state.steps['check-inventory'].output;\n return `/access_points/${inv.assigneeId}`;\n}"
},
{
"key": "body",
"type": "FUNCTION",
"value": "({ state }) => {\n const inv = state.steps['check-inventory'].output;\n const rawMac = state.input.mac;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const mac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toLowerCase();\n return {\n id: inv.assigneeId,\n name: inv.assigneeName || inv.assigneeId,\n tower: inv.assigneeName || '',\n ip_address: state.input.ip\n };\n}"
}
],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Has Option 82 and account? (disconnect)",
"slug": "should-sync-preseem-disconnect",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'Account';\n}",
"elseSteps": [{ "slug": "should-sync-site-disconnect" }],
"thenSteps": [{ "slug": "get-account-macs-and-plan" }]
},
{
"name": "Network site? (disconnect)",
"slug": "should-sync-site-disconnect",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const inv = state.steps['check-inventory'].output;\n if (inv == null) return false;\n if (!state.input.remoteId || state.input.remoteId === '') return false;\n return inv.assigneeType === 'NetworkSite';\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "preseem-site-update" }]
}
]
}

39
gaiia/examples/basic.py Normal file
View file

@ -0,0 +1,39 @@
"""Minimal usage example.
Set GAIIA_KEY in your env, then ``python examples/basic.py``.
"""
from __future__ import annotations
import os
from gaiia import GaiiaClient
def main() -> None:
with GaiiaClient(timezone="America/Chicago") as g:
techs = g.query("{ technicians(first: 5) { nodes { id readableId firstName lastName } } }")
for t in techs["technicians"]["nodes"]:
print(f"{t['readableId']:>4} {t['firstName']} {t['lastName']} {t['id']}")
print("rate-limit:", g.last_rate_limit)
print("\npaginating all technicians:")
for t in g.iter_nodes(
"""
query Q($after: String) {
technicians(first: 50, after: $after) {
nodes { id readableId firstName lastName }
pageInfo { hasNextPage endCursor }
}
}
""",
"technicians",
):
print(f" {t['readableId']:>4} {t['firstName']} {t['lastName']}")
if __name__ == "__main__":
if not os.environ.get("GAIIA_KEY"):
raise SystemExit("Set GAIIA_KEY in your env first.")
main()

View file

@ -0,0 +1,34 @@
"""Verify gaiia webhooks in a Flask endpoint.
pip install flask gaiia
GAIIA_WEBHOOK_SECRET=whsec_... flask --app examples/webhook_flask.py run
"""
from __future__ import annotations
import json
import os
from flask import Flask, abort, request
from gaiia import WebhookSignatureError, verify_webhook_signature
app = Flask(__name__)
SECRET = os.environ["GAIIA_WEBHOOK_SECRET"]
@app.post("/gaiia/webhook")
def webhook() -> tuple[str, int]:
raw = request.get_data()
try:
verify_webhook_signature(
header_value=request.headers.get("X-Gaiia-Webhook-Signature", ""),
raw_body=raw,
secret=SECRET,
)
except WebhookSignatureError as e:
abort(400, description=str(e))
event = json.loads(raw)
app.logger.info("gaiia event: %s", event.get("type"))
return "", 204

View file

@ -0,0 +1,37 @@
{
"name": "Expire Single IP Assignment",
"trigger": {
"type": "MANUAL",
"inputJsonSchema": {
"type": "object",
"required": ["macAddress", "ipAddress"],
"properties": {
"macAddress": { "type": "string" },
"ipAddress": { "type": "string" }
}
},
"nextSteps": [{ "slug": "expire" }]
},
"steps": [
{
"name": "Expire assignment",
"slug": "expire",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation Expire($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => ({\n input: {\n assignations: {\n macAddress: state.input.macAddress,\n ipAddress: state.input.ipAddress,\n expired: true\n }\n }\n})"
}
],
"nextSteps": [{ "slug": "end" }]
}
]
}

307
gaiia/fuzzy_match.py Normal file
View file

@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""
Fuzzy match unmatched RADIUS users against Gaiia accounts.
Smarter matching: requires first-name correlation when matching on last name,
handles substring traps ("kim" in "kimberly"), etc.
"""
import csv
import json
import re
import subprocess
import sys
from difflib import SequenceMatcher
GAIIA_KEY = "gaiia_sk_live_955b27e3680747a69ad40b403a912155_b0d1be3c"
GAIIA_ENDPOINT = "https://api.gaiia.com/api/v1"
def fetch_all_accounts():
accounts = []
cursor = None
while True:
after_arg = f', after: "{cursor}"' if cursor else ""
query = f"""
query {{
accounts(first: 250{after_arg}) {{
nodes {{ id readableId name displayName }}
pageInfo {{ hasNextPage endCursor }}
}}
}}
"""
result = subprocess.run(
["curl", "-s", "-X", "POST", GAIIA_ENDPOINT,
"-H", "Content-Type: application/json",
"-H", f"X-Gaiia-Api-Key: {GAIIA_KEY}",
"-H", "x-timezone: America/Chicago",
"-d", json.dumps({"query": query})],
capture_output=True, text=True, timeout=30,
)
data = json.loads(result.stdout)
for n in data["data"]["accounts"]["nodes"]:
name = n.get("displayName") or n.get("name") or ""
if name:
accounts.append((n["id"], name.strip(), n["readableId"]))
page_info = data["data"]["accounts"]["pageInfo"]
if not page_info["hasNextPage"]:
break
cursor = page_info["endCursor"]
return accounts
def normalize(s):
return re.sub(r'[^\w\s]', '', s.lower()).strip()
def split_name(display_name):
"""Split into first name(s) and last name."""
parts = normalize(display_name).split()
if len(parts) == 1:
return parts[0], ""
# Filter out suffixes, LLC, numbers, etc.
content_parts = [p for p in parts if not re.match(r'^(#?\d+|llc|inc|ltd|shop|jr|sr)$', p)]
if len(content_parts) >= 2:
return " ".join(content_parts[:-1]), content_parts[-1]
return content_parts[0], ""
def first_name_match_score(username, last_name, first_name_str):
"""How well does the first name correlate with the username? Returns 0-1.
The first initial must appear as the FIRST character of the username
portion before the last name (natural "firstname+lastname" order).
This prevents 'a' from "Amy" spuriously matching inside "ryan".
"""
if not first_name_str:
return 0.5 # neutral
# Find where last name appears in username
last_idx = username.find(last_name) if last_name else -1
username_before_last = username[:last_idx] if last_idx > 0 else username
first_names = first_name_str.split()
best = 0.0
for fn in first_names:
if len(fn) < 2:
continue
fn_init = fn[0]
if fn in username:
return 1.0 # full first name in username
# First initial must be the FIRST character before the last name
if username_before_last and username_before_last[0] == fn_init:
best = max(best, 0.7)
# Fuzzy match
ratio = SequenceMatcher(None, fn, username).ratio()
best = max(best, ratio * 0.8)
return best
def match_unmatched(unmatched_users, accounts):
"""Fuzzy match with smarter scoring."""
# Manual overrides from user knowledge
MANUAL_MATCHES = {
# (username, readable_id) -> forces match to specific Gaiia account
"barbihardin": "James Hardin",
"daycor": ("Cathy Day", 755),
"tech": ("Graham McIntire", 1),
# Add more as discovered
}
# Build lookup by name
accounts_by_name = {normalize(name): (aid, name, rid) for aid, name, rid in accounts}
results = []
for user in unmatched_users:
username = user["username"].lower()
username_no_digits = re.sub(r'\d+$', '', username)
# Check manual overrides first
if username in MANUAL_MATCHES:
override = MANUAL_MATCHES[username]
# tuple form: (display_name, readable_id)
if isinstance(override, tuple):
target_name, target_rid = override
for aid, name, rid in accounts:
if normalize(name) == normalize(target_name) and rid == target_rid:
results.append({
**user,
"gaiia_id": aid, "gaiia_name": name,
"gaiia_readable_id": rid,
"match_type": "fuzzy(manual_override)",
})
break
else:
# Fallback: match by name only
norm_target = normalize(target_name)
if norm_target in accounts_by_name:
aid, name, rid = accounts_by_name[norm_target]
results.append({
**user,
"gaiia_id": aid, "gaiia_name": name,
"gaiia_readable_id": rid,
"match_type": "fuzzy(manual_override)",
})
continue
continue
# String form: match by name
target_name = override
norm_target = normalize(target_name)
if norm_target in accounts_by_name:
aid, name, rid = accounts_by_name[norm_target]
results.append({
**user,
"gaiia_id": aid,
"gaiia_name": name,
"gaiia_readable_id": rid,
"match_type": "fuzzy(manual_override)",
})
continue
best_match = None
best_score = 0.0
best_reason = ""
for acct_id, acct_name, readable_id in accounts:
first_names, last_name = split_name(acct_name)
aname = normalize(acct_name)
aname_parts = aname.split()
score = 0.0
reason = ""
# ---- Strategy 1: last name match + first name correlation ----
if last_name and len(last_name) >= 3:
# Check that last_name appears in username as a distinct substring
# (not "kim" inside "kimberly")
idx = username.find(last_name)
if idx >= 0:
# Check it's a reasonable boundary match
# Allow: at start, at end, or surrounded by non-alpha
before_ok = idx == 0 or not username[idx-1].isalpha()
after_ok = (idx + len(last_name) == len(username)) or not username[idx+len(last_name)].isalpha()
if not before_ok and not after_ok:
# "kim" inside "kimberly" - reject this last name match
pass
else:
# Check first name correlation
fn_score = first_name_match_score(username, last_name, first_names)
if fn_score >= 0.5:
score = 0.7 + (fn_score * 0.2)
reason = f"last+first: {last_name}+{first_names}(fn={fn_score:.2f})"
elif fn_score > 0:
score = 0.6 + fn_score * 0.1
reason = f"last+weakfirst: {last_name}+{first_names}(fn={fn_score:.2f})"
else:
# Last name match but no first name correlation - low confidence
score = 0.55
reason = f"lastonly_nofirst: {last_name}"
# ---- Strategy 2: Sequence matcher on nodigits variant ----
if score < 0.6 and username_no_digits != username:
seq_score = SequenceMatcher(None, username_no_digits, aname).ratio()
if seq_score > 0.75:
score = max(score, seq_score * 0.9)
reason = f"seq_nodigits: {seq_score:.2f}"
# ---- Strategy 3: Direct sequence match ----
if score < 0.6:
seq_score = SequenceMatcher(None, username, aname).ratio()
if seq_score > 0.75:
score = max(score, seq_score * 0.9)
reason = f"sequence: {seq_score:.2f}"
# ---- Strategy 4: All account name parts in username ----
if score < 0.6:
all_found = True
for part in aname_parts:
if len(part) >= 3 and part not in username:
all_found = False
break
if all_found and len(aname_parts) >= 2:
score = 0.85
reason = f"allparts: {aname}"
if score > best_score:
best_score = score
best_match = (acct_id, acct_name, readable_id, score)
best_reason = reason
# Threshold: 0.7+
if best_match and best_score >= 0.7:
results.append({
**user,
"gaiia_id": best_match[0],
"gaiia_name": best_match[1],
"gaiia_readable_id": best_match[2],
"match_type": f"fuzzy({best_reason})",
})
else:
results.append({
**user,
"gaiia_id": user.get("gaiia_id", ""),
"gaiia_name": user.get("gaiia_name", ""),
"gaiia_readable_id": user.get("gaiia_readable_id", ""),
"match_type": f"still_unmatched(best: {best_reason}, score: {round(best_score,3)})",
})
return results
def resolve_ambiguous(ambiguous_rows, accounts):
"""For ambiguous matches (duplicate Gaiia names), pick the lowest readable_id."""
results = []
for row in ambiguous_rows:
ambig_names = row["gaiia_name"].replace("AMBIGUOUS: ", "").split(", ")
candidates = [(a_id, name, r_id) for a_id, name, r_id in accounts if name in ambig_names]
if candidates:
candidates.sort(key=lambda x: x[2])
row["gaiia_id"] = candidates[0][0]
row["gaiia_name"] = candidates[0][1]
row["gaiia_readable_id"] = candidates[0][2]
row["match_type"] = "ambiguous_resolved(lowest_id)"
else:
row["match_type"] = "ambiguous_unresolved"
results.append(row)
return results
def main():
reader = csv.DictReader(sys.stdin)
all_rows = list(reader)
unmatched = [r for r in all_rows if r["match_type"] == "no_match"]
ambiguous = [r for r in all_rows if r["match_type"] == "ambiguous"]
print(f"Unmatched: {len(unmatched)}, Ambiguous: {len(ambiguous)}", file=sys.stderr)
print("Fetching Gaiia accounts...", file=sys.stderr)
accounts = fetch_all_accounts()
print(f"Fetched {len(accounts)} accounts", file=sys.stderr)
matched_results = match_unmatched(unmatched, accounts)
ambig_results = resolve_ambiguous(ambiguous, accounts)
all_results = matched_results + ambig_results
fieldnames = ["radius_id", "username", "password", "gaiia_id", "gaiia_name", "gaiia_readable_id", "match_type"]
writer = csv.DictWriter(sys.stdout, fieldnames=fieldnames)
writer.writeheader()
for r in all_results:
writer.writerow({k: r.get(k, "") for k in fieldnames})
newly_matched = sum(1 for r in matched_results if r["gaiia_id"])
still = sum(1 for r in matched_results if "still_unmatched" in r.get("match_type", ""))
print(f"\nNewly matched: {newly_matched}, Still unmatched: {still}", file=sys.stderr)
print("\nStill unmatched:", file=sys.stderr)
for r in matched_results:
if "still_unmatched" in r.get("match_type", ""):
print(f" {r['username']:30s} ({r['match_type']})", file=sys.stderr)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,438 @@
[
{
"macAddress": "DC:9F:DB:92:0D:AA",
"ipAddress": "10.10.16.74"
},
{
"macAddress": "00:27:22:14:A7:F0",
"ipAddress": "10.10.96.80"
},
{
"macAddress": "DC:9F:DB:1E:D0:24",
"ipAddress": "10.10.96.3"
},
{
"macAddress": "04:18:D6:44:4A:46",
"ipAddress": "10.10.96.56"
},
{
"macAddress": "00:27:22:14:2D:BB",
"ipAddress": "10.10.96.76"
},
{
"macAddress": "00:04:56:61:07:7D",
"ipAddress": "10.10.96.6"
},
{
"macAddress": "00:01:9F:3B:D5:99",
"ipAddress": "100.64.25.213"
},
{
"macAddress": "00:01:9F:5C:A0:49",
"ipAddress": "100.64.174.228"
},
{
"macAddress": "E0:63:DA:90:74:76",
"ipAddress": "10.10.96.96"
},
{
"macAddress": "E0:63:DA:96:C1:3F",
"ipAddress": "10.10.0.33"
},
{
"macAddress": "74:AC:B9:7A:17:9A",
"ipAddress": "10.10.96.1"
},
{
"macAddress": "00:04:56:2F:D8:4D",
"ipAddress": "10.10.128.10"
},
{
"macAddress": "74:AC:B9:7A:14:01",
"ipAddress": "10.10.96.59"
},
{
"macAddress": "00:01:9F:5C:A0:61",
"ipAddress": "100.64.28.254"
},
{
"macAddress": "00:01:9F:6B:0F:29",
"ipAddress": "100.64.19.35"
},
{
"macAddress": "FC:EC:DA:CE:68:19",
"ipAddress": "10.10.16.70"
},
{
"macAddress": "FC:EC:DA:CE:69:97",
"ipAddress": "10.10.16.36"
},
{
"macAddress": "00:04:56:C2:87:DD",
"ipAddress": "10.10.144.3"
},
{
"macAddress": "00:04:56:C1:BA:BE",
"ipAddress": "10.10.128.5"
},
{
"macAddress": "00:04:56:FE:42:87",
"ipAddress": "10.10.128.12"
},
{
"macAddress": "00:04:56:FE:31:A3",
"ipAddress": "10.10.128.40"
},
{
"macAddress": "00:01:9F:1E:9B:19",
"ipAddress": "100.64.0.11"
},
{
"macAddress": "00:04:56:FE:47:0F",
"ipAddress": "10.10.128.41"
},
{
"macAddress": "18:E8:29:38:D3:45",
"ipAddress": "10.10.16.48"
},
{
"macAddress": "18:E8:29:38:D4:2C",
"ipAddress": "10.10.16.69"
},
{
"macAddress": "18:E8:29:38:D3:BC",
"ipAddress": "10.10.0.11"
},
{
"macAddress": "FC:EC:DA:6C:FD:08",
"ipAddress": "10.10.96.68"
},
{
"macAddress": "74:83:C2:64:4A:22",
"ipAddress": "10.10.16.72"
},
{
"macAddress": "74:83:C2:64:4A:B2",
"ipAddress": "10.10.96.78"
},
{
"macAddress": "E4:38:83:A0:56:B7",
"ipAddress": "10.10.0.18"
},
{
"macAddress": "E4:38:83:A0:56:0A",
"ipAddress": "10.10.16.67"
},
{
"macAddress": "E4:38:83:A0:57:88",
"ipAddress": "10.10.0.13"
},
{
"macAddress": "E4:38:83:A0:59:53",
"ipAddress": "10.10.0.58"
},
{
"macAddress": "D0:21:F9:AF:23:3E",
"ipAddress": "10.10.62.200"
},
{
"macAddress": "D0:21:F9:F0:9F:A4",
"ipAddress": "10.10.62.203"
},
{
"macAddress": "58:C1:7A:B3:09:72",
"ipAddress": "10.10.16.59"
},
{
"macAddress": "D0:21:F9:F0:9F:F4",
"ipAddress": "10.10.62.215"
},
{
"macAddress": "E4:38:83:AA:19:E6",
"ipAddress": "10.10.0.15"
},
{
"macAddress": "70:A7:41:4A:3C:55",
"ipAddress": "10.10.160.12"
},
{
"macAddress": "D0:21:F9:AF:23:02",
"ipAddress": "10.10.62.202"
},
{
"macAddress": "58:C1:7A:B3:08:B8",
"ipAddress": "10.10.96.91"
},
{
"macAddress": "00:04:56:DB:7D:86",
"ipAddress": "100.64.174.215"
},
{
"macAddress": "E0:63:DA:0F:B7:8B",
"ipAddress": "10.10.62.253"
},
{
"macAddress": "00:01:9F:33:54:E9",
"ipAddress": "100.64.25.32"
},
{
"macAddress": "E4:38:83:AA:17:36",
"ipAddress": "10.10.160.1"
},
{
"macAddress": "00:01:9F:33:54:C1",
"ipAddress": "100.64.0.221"
},
{
"macAddress": "E4:38:83:AA:17:A6",
"ipAddress": "10.10.160.4"
},
{
"macAddress": "D0:21:F9:F0:9D:E1",
"ipAddress": "10.10.62.212"
},
{
"macAddress": "70:A7:41:4A:3C:3E",
"ipAddress": "10.10.0.5"
},
{
"macAddress": "00:01:9F:34:D4:11",
"ipAddress": "100.64.174.207"
},
{
"macAddress": "D0:21:F9:F0:9F:32",
"ipAddress": "10.10.62.201"
},
{
"macAddress": "00:04:56:DB:EC:AA",
"ipAddress": "10.10.64.6"
},
{
"macAddress": "F4:E2:C6:30:3E:1E",
"ipAddress": "10.10.96.61"
},
{
"macAddress": "F4:E2:C6:30:23:C5",
"ipAddress": "10.10.0.8"
},
{
"macAddress": "F4:E2:C6:30:2C:4E",
"ipAddress": "10.10.96.12"
},
{
"macAddress": "F4:E2:C6:30:28:40",
"ipAddress": "10.10.96.20"
},
{
"macAddress": "F4:E2:C6:30:35:33",
"ipAddress": "10.10.0.1"
},
{
"macAddress": "F4:E2:C6:30:3E:29",
"ipAddress": "10.10.96.60"
},
{
"macAddress": "F4:E2:C6:30:34:B5",
"ipAddress": "10.10.96.25"
},
{
"macAddress": "F4:E2:C6:30:3D:03",
"ipAddress": "10.10.16.60"
},
{
"macAddress": "F4:E2:C6:30:3D:E4",
"ipAddress": "10.10.96.83"
},
{
"macAddress": "F0:A7:31:74:A0:14",
"ipAddress": "100.64.25.85"
},
{
"macAddress": "F4:E2:C6:30:27:A5",
"ipAddress": "10.10.0.2"
},
{
"macAddress": "F0:A7:31:74:A0:4A",
"ipAddress": "100.64.25.176"
},
{
"macAddress": "F4:E2:C6:30:35:1D",
"ipAddress": "10.10.96.69"
},
{
"macAddress": "F4:E2:C6:30:31:14",
"ipAddress": "10.10.0.24"
},
{
"macAddress": "F4:E2:C6:30:2A:72",
"ipAddress": "10.10.0.22"
},
{
"macAddress": "E8:DA:00:17:B1:A9",
"ipAddress": "100.64.174.203"
},
{
"macAddress": "E8:DA:00:17:B1:E5",
"ipAddress": "100.64.25.74"
},
{
"macAddress": "F4:E2:C6:30:39:C0",
"ipAddress": "10.10.0.70"
},
{
"macAddress": "F4:E2:C6:30:2E:06",
"ipAddress": "10.10.96.22"
},
{
"macAddress": "F4:E2:C6:30:3F:F0",
"ipAddress": "10.10.96.69"
},
{
"macAddress": "00:04:56:DB:EE:F3",
"ipAddress": "10.10.160.5"
},
{
"macAddress": "F4:E2:C6:30:29:CF",
"ipAddress": "10.10.96.88"
},
{
"macAddress": "F4:E2:C6:30:31:02",
"ipAddress": "10.10.0.43"
},
{
"macAddress": "70:A7:41:16:63:D1",
"ipAddress": "10.10.96.71"
},
{
"macAddress": "F4:E2:C6:30:31:ED",
"ipAddress": "10.10.0.45"
},
{
"macAddress": "F4:E2:C6:30:24:65",
"ipAddress": "10.10.96.21"
},
{
"macAddress": "00:04:56:EF:9A:15",
"ipAddress": "100.64.19.57"
},
{
"macAddress": "00:04:56:60:95:0D",
"ipAddress": "10.10.144.9"
},
{
"macAddress": "00:04:56:60:8D:FB",
"ipAddress": "10.10.64.6"
},
{
"macAddress": "00:04:56:F0:36:65",
"ipAddress": "10.10.128.31"
},
{
"macAddress": "00:04:56:E8:3D:CF",
"ipAddress": "10.10.144.28"
},
{
"macAddress": "00:04:56:FA:A6:49",
"ipAddress": "10.10.128.39"
},
{
"macAddress": "78:8A:20:3C:3B:4F",
"ipAddress": "10.10.96.70"
},
{
"macAddress": "F4:E2:C6:30:48:0E",
"ipAddress": "10.10.0.39"
},
{
"macAddress": "F4:E2:C6:30:4A:E6",
"ipAddress": "10.10.96.63"
},
{
"macAddress": "F4:E2:C6:30:47:E8",
"ipAddress": "10.10.96.32"
},
{
"macAddress": "F4:E2:C6:30:46:EA",
"ipAddress": "10.10.0.52"
},
{
"macAddress": "F4:E2:C6:30:4A:82",
"ipAddress": "10.10.16.62"
},
{
"macAddress": "F4:E2:C6:30:4B:56",
"ipAddress": "10.10.0.51"
},
{
"macAddress": "D8:44:89:A6:FF:01",
"ipAddress": "100.64.25.97"
},
{
"macAddress": "F4:E2:C6:30:4B:5E",
"ipAddress": "10.10.16.54"
},
{
"macAddress": "D8:44:89:A7:08:BB",
"ipAddress": "100.64.25.55"
},
{
"macAddress": "F4:E2:C6:30:3D:42",
"ipAddress": "10.10.0.46"
},
{
"macAddress": "D8:44:89:A7:0A:29",
"ipAddress": "100.64.25.183"
},
{
"macAddress": "74:AC:B9:F2:08:EC",
"ipAddress": "10.10.16.45"
},
{
"macAddress": "F4:92:BF:D8:52:B0",
"ipAddress": "10.10.0.10"
},
{
"macAddress": "74:AC:B9:F2:09:37",
"ipAddress": "10.10.16.65"
},
{
"macAddress": "00:04:56:44:E0:32",
"ipAddress": "10.10.144.37"
},
{
"macAddress": "00:04:56:44:F5:D7",
"ipAddress": "10.10.96.16"
},
{
"macAddress": "74:AC:B9:F2:08:5C",
"ipAddress": "10.10.16.63"
},
{
"macAddress": "00:04:56:44:52:2D",
"ipAddress": "10.10.128.23"
},
{
"macAddress": "00:04:56:2F:CD:D0",
"ipAddress": "10.10.128.26"
},
{
"macAddress": "00:04:56:44:52:C6",
"ipAddress": "10.10.96.27"
},
{
"macAddress": "00:04:56:2F:CD:16",
"ipAddress": "10.10.16.39"
},
{
"macAddress": "00:04:56:2F:DB:53",
"ipAddress": "10.10.128.7"
},
{
"macAddress": "F4:92:BF:D8:55:4C",
"ipAddress": "10.10.16.66"
},
{
"macAddress": "00:04:56:44:F6:2E",
"ipAddress": "10.10.128.29"
}
]

View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""List all customer inventory items with active IP assignments.
Uses the Gaiia public API - run with GAIIA_KEY set (source .envrc)."""
from gaiia import GaiiaClient
import json
def main():
with GaiiaClient(timezone='America/Chicago') as g:
# Paginate through all inventory items assigned to accounts
all_assignments = []
after = None
page = 0
while True:
page += 1
query = """
query GetAssignments($after: String) {
inventoryItems(
filters: {assigneeType: {equals: ACCOUNT}}
first: 250
after: $after
) {
nodes {
ipAddressAssignments {
nodes { ipAddress macAddress isActive }
}
}
pageInfo { hasNextPage endCursor }
totalCount
}
}
"""
data = g.query(query, {"after": after} if after else {})
result = data['inventoryItems']
if page == 1:
print(f"Total inventory items assigned to accounts: {result['totalCount']}")
for item in result['nodes']:
for a in item.get('ipAddressAssignments', {}).get('nodes', []):
if a.get('isActive') != False: # active or None
all_assignments.append({
'macAddress': a['macAddress'],
'ipAddress': a['ipAddress']
})
page_info = result['pageInfo']
if not page_info.get('hasNextPage'):
break
after = page_info['endCursor']
print(f"Active IP assignments found: {len(all_assignments)}")
print()
# Print first 20
for a in all_assignments[:20]:
print(f" {a['macAddress']} -> {a['ipAddress']}")
if len(all_assignments) > 20:
print(f" ... and {len(all_assignments) - 20} more")
# Save to file
with open('/Users/graham/dev/network/gaiia/ip_assignments.json', 'w') as f:
json.dump(all_assignments, f, indent=2)
print(f"\nSaved {len(all_assignments)} assignments to ip_assignments.json")
if __name__ == '__main__':
main()

202
gaiia/match_radius.py Normal file
View file

@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""
Match RADIUS PPPoE usernames to Gaiia accounts by exact name-to-username mapping.
Deterministic only no fuzzy matching. Leaves ambiguous/unmatched rows blank.
A Gaiia displayName like "Jose Ortiz" generates candidate usernames:
joseortiz, jortiz, joseo, jose, ortiz
The RADIUS username must match one of these exactly.
"""
import csv
import json
import re
import subprocess
import sys
from collections import defaultdict
GAIIA_KEY = "gaiia_sk_live_955b27e3680747a69ad40b403a912155_b0d1be3c"
GAIIA_ENDPOINT = "https://api.gaiia.com/api/v1"
def fetch_all_accounts():
"""Fetch all Gaiia accounts with id and displayName."""
accounts = []
cursor = None
while True:
after_arg = f', after: "{cursor}"' if cursor else ""
query = f"""
query {{
accounts(first: 250{after_arg}) {{
nodes {{ id readableId name displayName }}
pageInfo {{ hasNextPage endCursor }}
}}
}}
"""
result = subprocess.run(
[
"curl", "-s",
"-X", "POST", GAIIA_ENDPOINT,
"-H", "Content-Type: application/json",
"-H", f"X-Gaiia-Api-Key: {GAIIA_KEY}",
"-H", "x-timezone: America/Chicago",
"-d", json.dumps({"query": query}),
],
capture_output=True, text=True, timeout=30,
)
data = json.loads(result.stdout)
nodes = data["data"]["accounts"]["nodes"]
for n in nodes:
name = n.get("displayName") or n.get("name") or ""
if name:
accounts.append((n["id"], name.strip(), n["readableId"]))
page_info = data["data"]["accounts"]["pageInfo"]
if not page_info["hasNextPage"]:
break
cursor = page_info["endCursor"]
return accounts
def generate_username_variants(display_name):
"""Generate candidate PPPoE-style usernames from a Gaiia display name.
"Jose Ortiz" -> {joseortiz, jortiz, jose, ortiz}
"Graham McIntire" -> {grahammcintire, gmcintire, graham, mcintire}
"Kory Biggs Shop" -> {korybiggsshop, kbiggs, kory, biggs, ...}
"Mid Valley Pumping LLC" -> {midvalleypumpingllc, midvalley, ...}
"""
name = display_name.lower().strip()
# Remove punctuation
name = re.sub(r'[^\w\s]', '', name)
name = re.sub(r'\s+', ' ', name).strip()
parts = name.split()
variants = set()
# Full concatenation: firstlast with no spaces
variants.add("".join(parts))
# First initial + last name: e.g. jortiz, gmcintire
if len(parts) >= 2:
variants.add(parts[0][0] + parts[-1])
# Also first name + last name initial
variants.add(parts[0] + parts[-1][0])
# Each individual part
for p in parts:
variants.add(p)
# First + last (just the two parts concatenated)
if len(parts) >= 2:
variants.add(parts[0] + parts[-1])
# Handle numbered suffixes (e.g. "Perla Chavez 2" -> "perlachavez2")
if len(parts) >= 3 and re.match(r'^\d+$', parts[-1]):
variants.add("".join(parts[:-1]) + parts[-1])
return variants
def parse_radius_input(text):
"""Parse the pipe-delimited RADIUS user table."""
users = []
pattern = r'\|\s*(\d+)\s*\|\s*(\S+)\s*\|\s*Clear(?:t|T)ext-Password\s*\|\s*:=\s*\|\s*(\S+)\s*\|'
for match in re.finditer(pattern, text):
users.append({
"radius_id": int(match.group(1)),
"username": match.group(2),
"password": match.group(3),
})
return users
def match_users(radius_users, accounts):
"""Match RADIUS users to Gaiia accounts by exact username variant matching."""
# Build mapping: username_variant -> list of (account_id, account_name, readable_id)
variant_map = defaultdict(list)
for acct_id, acct_name, readable_id in accounts:
for variant in generate_username_variants(acct_name):
variant_map[variant].append((acct_id, acct_name, readable_id))
# Track which Gaiia accounts have been used (to avoid double-matching)
used_accounts = set()
results = []
for user in radius_users:
username = user["username"].lower()
candidates = variant_map.get(username, [])
# Filter out already-used accounts
available = [(a, n, r) for a, n, r in candidates if a not in used_accounts]
if len(available) == 1:
acct_id, acct_name, readable_id = available[0]
used_accounts.add(acct_id)
results.append({
**user,
"gaiia_id": acct_id,
"gaiia_name": acct_name,
"gaiia_readable_id": readable_id,
"match_type": "exact",
})
elif len(available) > 1:
# Multiple candidates — leave blank for manual review
results.append({
**user,
"gaiia_id": "",
"gaiia_name": f"AMBIGUOUS: {', '.join(n for _,n,_ in available)}",
"gaiia_readable_id": "",
"match_type": "ambiguous",
})
else:
results.append({
**user,
"gaiia_id": "",
"gaiia_name": "",
"gaiia_readable_id": "",
"match_type": "no_match",
})
return results
def main():
text = sys.stdin.read()
radius_users = parse_radius_input(text)
print(f"Parsed {len(radius_users)} RADIUS users", file=sys.stderr)
print("Fetching Gaiia accounts...", file=sys.stderr)
accounts = fetch_all_accounts()
print(f"Fetched {len(accounts)} Gaiia accounts with names", file=sys.stderr)
results = match_users(radius_users, accounts)
writer = csv.writer(sys.stdout)
writer.writerow(["radius_id", "username", "password", "gaiia_id", "gaiia_name", "gaiia_readable_id", "match_type"])
for r in results:
writer.writerow([
r["radius_id"], r["username"], r["password"],
r["gaiia_id"], r["gaiia_name"], r["gaiia_readable_id"], r["match_type"],
])
matched = sum(1 for r in results if r["gaiia_id"])
ambiguous = sum(1 for r in results if r["match_type"] == "ambiguous")
unmatched = sum(1 for r in results if r["match_type"] == "no_match")
print(f"\nMatched: {matched}, Ambiguous: {ambiguous}, No match: {unmatched} (total: {len(results)})", file=sys.stderr)
if ambiguous:
print("\nAMBIGUOUS (multiple Gaiia accounts match same username):", file=sys.stderr)
for r in results:
if r["match_type"] == "ambiguous":
print(f" {r['username']}: {r['gaiia_name']}", file=sys.stderr)
if unmatched:
print("\nNO MATCH:", file=sys.stderr)
for r in results:
if r["match_type"] == "no_match":
print(f" {r['username']}", file=sys.stderr)
if __name__ == "__main__":
main()

101
gaiia/output.txt Normal file
View file

@ -0,0 +1,101 @@
Fetching all accounts from Gaiia...
Fetched 50/458 accounts...
Fetched 100/458 accounts...
Fetched 150/458 accounts...
Fetched 200/458 accounts...
Fetched 250/458 accounts...
Fetched 300/458 accounts...
Fetched 350/458 accounts...
Fetched 400/458 accounts...
Fetched 450/458 accounts...
Fetched 458/458 accounts...
Total: 458 accounts
===========================================================================
CLIMAX PPPoE → GAIIA ACCOUNT MATCHING
===========================================================================
PPPoE PPPoE IP Gaiia Name Account ID
------------------------------------------------------------------------------
charlesboone 100.64.7.247 Charles Boone 62
amberprater 100.64.7.243 Amber Prater 855
matthewgoodwin 100.64.7.241 Matthew Goodwin 2453
robbymccollom 100.64.7.239 Robby McCollom 297
chasewilliams 100.64.7.235 Chase Williams 2042
cynthiajones 100.64.7.233 Cynthia Jones 1778
janicealexander 100.64.7.225 Janice Alexander 165
michaelray 204.110.188.38 Michael Ray 379
marysmelser 100.64.7.218 Mary Smelser 2385
eddieyarbrough 100.64.7.216 Eddie Yarbrough 125
timgilbert 100.64.7.213 Tim Gilbert 346
chadwhitsell 100.64.7.211 Chad Whitsell 61
donnacampbell 100.64.7.209 Donna Campbell 2530
bryangoulart 100.64.7.205 Brian Goulart 2076
crystalharney 100.64.7.201 Crystal Harney 2233
johnvayo 100.64.7.197 John Vayo 2275
richardbarragan 100.64.7.193 richard barragan 1314
carolstrickland 100.64.7.189 Carol Strickland 2410
timbagert 100.64.7.140 Tim Bagert 343
douggarber 100.64.7.136 Doug Garber 2360
janetkern 100.64.7.61 Janet Kern 449
gregmcintire 100.64.7.59 Greg McIntire 380
jenniferboon 100.64.7.35 Jennifer Boon 2200
glendabeauchamp 100.64.7.15 Glenda Beauchamp 2133
jmichaelculverhouse 100.64.7.250 J'michael Culverhouse 1838
elviraquezada 100.64.7.249 Elvira Quezada 960
taekim 204.110.188.42 Tae Kim 408
ruthfengler 100.64.7.246 Ruth Fengler 304
rhondabolton 100.64.7.231 Rhonda Bolton 295
gordonhamilton 100.64.7.237 (not in Gaiia) -
tammieventris 100.64.7.232 (not in Gaiia) -
buddyswan 100.64.7.245 (not in Gaiia) -
Matched: 29 | Not in Gaiia: 3
Not in Gaiia:
- gordonhamilton (100.64.7.237)
- tammieventris (100.64.7.232)
- buddyswan (100.64.7.245)
===========================================================================
IP ADDRESS COMPARISON (PPPoE active IP vs Gaiia inventory item IP)
===========================================================================
charlesboone 100.64.7.247 MISMATCH (Gaiia has: 10.10.16.73)
amberprater 100.64.7.243 MISMATCH (Gaiia has: 10.10.16.56)
matthewgoodwin 100.64.7.241 MISMATCH (Gaiia has: 10.10.16.34)
robbymccollom 100.64.7.239 MISMATCH (Gaiia has: 10.10.16.69)
chasewilliams 100.64.7.235 MISMATCH (Gaiia has: 10.10.16.66)
cynthiajones 100.64.7.233 MISMATCH (Gaiia has: 10.10.16.72)
janicealexander 100.64.7.225 MISMATCH (Gaiia has: 10.10.16.65)
michaelray 204.110.188.38 (no inventory items in Gaiia)
marysmelser 100.64.7.218 MATCH (item: AC1200MS)
eddieyarbrough 100.64.7.216 NO IP SET (has inventory items but no IPs)
timgilbert 100.64.7.213 MISMATCH (Gaiia has: 10.10.16.71)
chadwhitsell 100.64.7.211 MISMATCH (Gaiia has: 10.10.16.75)
donnacampbell 100.64.7.209 MATCH (item: AC1200-Mesh)
bryangoulart 100.64.7.205 MISMATCH (Gaiia has: 10.10.16.62)
crystalharney 100.64.7.201 MATCH (item: AC1200-Mesh)
johnvayo 100.64.7.197 MATCH (item: AC1200-Mesh)
richardbarragan 100.64.7.193 MATCH (item: AC1200-Mesh)
carolstrickland 100.64.7.189 MISMATCH (Gaiia has: 10.10.16.37)
timbagert 100.64.7.140 MATCH (item: AC1200-Mesh)
douggarber 100.64.7.136 MISMATCH (Gaiia has: 10.10.16.57)
janetkern 100.64.7.61 MISMATCH (Gaiia has: 10.10.16.54)
gregmcintire 100.64.7.59 (no inventory items in Gaiia)
jenniferboon 100.64.7.35 MATCH (item: AC1200-Mesh)
glendabeauchamp 100.64.7.15 MATCH (item: AC1200-Mesh)
jmichaelculverhouse 100.64.7.250 NO IP SET (has inventory items but no IPs)
elviraquezada 100.64.7.249 MISMATCH (Gaiia has: 10.10.16.67)
taekim 204.110.188.42 MISMATCH (Gaiia has: 10.10.16.70)
ruthfengler 100.64.7.246 MISMATCH (Gaiia has: 10.10.16.46)
rhondabolton 100.64.7.231 MISMATCH (Gaiia has: 10.10.16.61)
--- Summary ---
Accounts in Gaiia: 29/32
Not in Gaiia: 3
IP matches PPPoE: 8
IP mismatch (different IP): 17
No inventory / no IP in Gaiia: 4
Accounts missing from Gaiia (need investigation):
gordonhamilton IP=100.64.7.237 MAC=A0:63:91:3A:29:B1
tammieventris IP=100.64.7.232 MAC=98:25:4A:04:A5:E1
buddyswan IP=100.64.7.245 MAC=00:01:9F:34:D2:21

View file

@ -0,0 +1,198 @@
{
"name": "pppoe auth",
"description": "WEBHOOK: PPPoE connect/disconnect → account lookup → Preseem sync. Connect (bound=1): BYOD if needed, assign IP, add MAC to Preseem with all inventoried equipment. Disconnect (bound=0): remove MAC from Preseem.",
"trigger": {
"type": "WEBHOOK",
"inputJsonSchema": {
"type": "object",
"required": ["bound", "callingstationid", "framedip", "username"],
"properties": {
"bound": { "type": "string" },
"framedip": { "type": "string" },
"username": { "type": "string" },
"callingstationid": { "type": "string" }
}
},
"nextSteps": [{ "slug": "get-account-id" }]
},
"steps": [
{
"name": "Get Account ID from RADIUS Username",
"slug": "get-account-id",
"type": "CUSTOM_WORKFLOW",
"nodeSlug": "get-account-id-from-radius-username",
"allowSkipExecution": false,
"inputs": [
{ "key": "username", "type": "STATE", "value": "input.username" }
],
"nextSteps": [{ "slug": "is-connect" }],
"errorSteps": [{ "slug": "end", "errorCodes": ["USERNAME_NOT_FOUND"] }]
},
{
"name": "Is this a connect?",
"slug": "is-connect",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n return state.input.bound === '1';\n}",
"elseSteps": [{ "slug": "expire-ip-assignment" }],
"thenSteps": [{ "slug": "check-inventory" }]
},
{
"name": "Expire IP assignment on disconnect",
"slug": "expire-ip-assignment",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation ExpireIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.callingstationid;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: {\n macAddress: formattedMac,\n ipAddress: state.input.framedip,\n expired: true\n }\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "get-account-macs-and-plan" }]
},
{
"name": "Check if MAC is already inventoried",
"slug": "check-inventory",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output, state }) => {\n const items = output.response.findInventoryItemsFromDeviceIdentifiers;\n const assigned = items.filter(\n (item) => item.inventoryItem?.assignation?.assignee?.__typename === 'Account'\n );\n return assigned;\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query FindItem($input: FindInventoryItemsFromDeviceIdentifiersQueryInput!) {\n findInventoryItemsFromDeviceIdentifiers(input: $input) {\n identifier\n inventoryItem {\n id\n model { name }\n assignation {\n assignee {\n __typename\n ... on Account { id name }\n }\n }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.callingstationid;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return { input: { identifiers: [formattedMac] } };\n}"
}
],
"nextSteps": [{ "slug": "if-byod-needed" }]
},
{
"name": "Is BYOD needed?",
"slug": "if-byod-needed",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const found = state.steps['check-inventory'].output;\n return !found || found.length === 0;\n}",
"elseSteps": [{ "slug": "assign-ip-address" }],
"thenSteps": [{ "slug": "find-byod-model" }]
},
{
"name": "Find Customer Provided Router model",
"slug": "find-byod-model",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const model = output.response.inventoryModels.nodes[0];\n if (!model) {\n workflow.fail('Customer Provided Router model not found', 'MODEL_NOT_FOUND');\n }\n const macField = model.fields.nodes.find(\n (field) => field.name === 'MAC Address'\n );\n if (!macField) {\n workflow.fail('MAC Address field not found on Customer Provided Router model', 'MAC_FIELD_NOT_FOUND');\n }\n return { id: model.id, macFieldId: macField.id };\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query GetModel($filters: InventoryModelQueryFilters) {\n inventoryModels(filters: $filters) {\n nodes {\n id\n name\n fields { nodes { id name } }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ secrets, state, utils, variables }) => {\n return { filters: { name: { equals: 'Customer Provided Router' } } };\n}"
}
],
"nextSteps": [{ "slug": "create-byod" }]
},
{
"name": "Create BYOD inventory item",
"slug": "create-byod",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation AddInventoryItems($input: CreateInventoryItemsInput!) {\n addInventoryItems(input: $input) {\n inventoryItemsCreationResult { addedItems { id } }\n errors { code message }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.callingstationid;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':');\n const accountId = state.steps['get-account-id'].output.accounts[0].accountId;\n const { id: modelId, macFieldId } = state.steps['find-byod-model'].output;\n return {\n input: {\n items: [{\n fields: [{ modelFieldId: macFieldId, data: formattedMac }],\n modelId: modelId,\n equipmentConditionType: 'NEW',\n purchasePriceInCents: 0,\n status: 'FUNCTIONAL',\n assignation: { assigneeId: accountId, assigneeType: 'ACCOUNT' }\n }]\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "assign-ip-address" }]
},
{
"name": "Assign IP address to MAC",
"slug": "assign-ip-address",
"type": "GAIIA_API_GATEWAY_V0_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-internal-api-gateway-graphql-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:mutation UpsertIp($input: UpsertMacAddressIpAddressAssignationsInput!) {\n upsertMacAddressIpAddressAssignations(input: $input) {\n assignations { macAddress ipAddress }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const rawMac = state.input.callingstationid;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const formattedMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toUpperCase();\n return {\n input: {\n assignations: {\n macAddress: formattedMac,\n ipAddress: state.input.framedip,\n expired: false\n }\n }\n };\n}"
}
],
"nextSteps": [{ "slug": "get-account-macs-and-plan" }]
},
{
"name": "Get all inventoried MACs and plan",
"slug": "get-account-macs-and-plan",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output, state }) => {\n const account = output.response.account;\n if (!account) {\n workflow.fail('Account not found', 'ACCOUNT_NOT_FOUND');\n }\n const macs = [];\n for (const assign of (account.assignedInventoryItems?.nodes || [])) {\n const inv = assign.inventoryItem || {};\n for (const f of (inv.fields?.nodes || [])) {\n const fieldName = (f.modelField?.name || '').toLowerCase();\n const data = (f.data || '').trim();\n if (fieldName.includes('mac') && data && data !== '00:00:00:00:00:00') {\n const cleaned = data.startsWith('1:') ? data.slice(2) : data;\n const parts = cleaned.split(':');\n if (parts.length === 6) {\n macs.push(parts.map((p) => p.padStart(2, '0').toUpperCase()).join(':'));\n }\n }\n }\n for (const a of (inv.ipAddressAssignments?.nodes || [])) {\n if (a.macAddress) macs.push(a.macAddress.toUpperCase());\n }\n }\n const plan = (account.billingSubscriptions?.nodes || [])\n .map((s) => s.productVersion?.product?.slug)\n .find(Boolean) || '';\n return { macs: [...new Set(macs)], plan, gaiiaId: account.readableId };\n}",
"inputs": [
{
"key": "query",
"type": "FUNCTION",
"value": "({ state }) => {\n const accountId = state.steps['get-account-id'].output.accounts[0].accountId;\n return `query { account(id: \"${accountId}\") { readableId assignedInventoryItems { nodes { inventoryItem { fields { nodes { data modelField { name } } } ipAddressAssignments { nodes { macAddress } } } } } billingSubscriptions { nodes { productVersion { product { slug } } } } } }`;\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => ({})"
}
],
"nextSteps": [{ "slug": "preseem-update" }]
},
{
"name": "Update Preseem",
"slug": "preseem-update",
"type": "PRESEEM_REST_REQUEST",
"nodeSlug": "preseem-rest-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "method",
"type": "LITERAL",
"value": "string:PUT"
},
{
"key": "path",
"type": "FUNCTION",
"value": "({ state }) => {\n const { gaiiaId } = state.steps['get-account-macs-and-plan'].output;\n return `/services/${gaiiaId}`;\n}"
},
{
"key": "body",
"type": "FUNCTION",
"value": "({ state }) => {\n const { gaiiaId, macs, plan } = state.steps['get-account-macs-and-plan'].output;\n const rawMac = state.input.callingstationid;\n const cleanedMac = rawMac.startsWith('1:') ? rawMac.slice(2) : rawMac;\n const connectingMac = cleanedMac\n .split(':')\n .map((segment) => segment.padStart(2, '0'))\n .join(':')\n .toLowerCase();\n const framedIp = state.input.framedip;\n // On connect (bound=1): include connecting MAC with its IP. On disconnect (bound=0): exclude it.\n const inventoriedMacs = macs.map((m) => m.toLowerCase());\n const attachments = inventoriedMacs\n .filter((m) => state.input.bound === '0' || m !== connectingMac)\n .map((m) => ({ cpe_mac: m }));\n if (state.input.bound === '1') {\n attachments.push({ cpe_mac: connectingMac, network_prefixes: [framedIp] });\n }\n return {\n id: String(gaiiaId),\n attachments,\n account: String(gaiiaId),\n package: plan\n };\n}"
}
],
"nextSteps": [{ "slug": "end" }]
}
]
}

63
gaiia/pyproject.toml Normal file
View file

@ -0,0 +1,63 @@
[build-system]
requires = ["hatchling>=1.18"]
build-backend = "hatchling.build"
[project]
name = "gaiia"
version = "0.1.0"
description = "Python client for the gaiia GraphQL API and webhooks."
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
authors = [{ name = "Graham McIntire", email = "graham@mcintire.me" }]
keywords = ["gaiia", "graphql", "api", "wisp", "isp", "client"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Software Development :: Libraries :: Python Modules",
"Typing :: Typed",
]
dependencies = ["httpx>=0.27"]
[project.optional-dependencies]
dev = [
"pytest>=8",
"pytest-asyncio>=0.23",
"respx>=0.21",
"mypy>=1.10",
"ruff>=0.5",
]
[project.urls]
Homepage = "https://app.gaiia.com/docs/overview"
Documentation = "https://app.gaiia.com/docs/overview"
Source = "https://github.com/grahammcintire/gaiia-py"
Issues = "https://github.com/grahammcintire/gaiia-py/issues"
[tool.hatch.build.targets.sdist]
include = ["src/gaiia", "README.md", "LICENSE"]
[tool.hatch.build.targets.wheel]
packages = ["src/gaiia"]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
[tool.ruff]
line-length = 100
target-version = "py310"
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "SIM"]
[tool.mypy]
python_version = "3.10"
strict = true
warn_unused_ignores = true

View file

@ -0,0 +1,68 @@
radius_id,username,password,gaiia_id,gaiia_name,gaiia_readable_id,match_type
29521,tiffanyyarley-olenak,98HTZ9Q8FARG,account_cwmjmXkrYeba8h8RGqLm6E,Tiffany Yarley-Olenak,2393,fuzzy(sequence: 0.95)
29395,vanessagomez1,J5JEW3HEBVAF,account_gb82EhqTwbNWWEovPu4oKZ,Vanessa Gomez,2264,fuzzy(last+first: gomez+vanessa(fn=1.00))
29611,brittannieredden1,TYFTUEZ7429,account_bB6RDmNpkzndVbSh9CvgaL,Brittannie Redden,10017,fuzzy(last+first: redden+brittannie(fn=1.00))
29614,samanthatheis,kf6u8djigk,account_x46hMNq15qHmEvP6fNFdfm,Samatha Theis,10023,fuzzy(last+first: theis+samatha(fn=0.70))
29216,wanxiaxong,BER4NEK9M7NA,account_tiy5YHD34KpwPzUTXyRVFJ,Wanxia Song,2077,fuzzy(sequence: 0.86)
29215,bryangoulart,DTNQFE4CXSFX,account_9sksoKmcJGxqhz4M4aD169,Brian Goulart,2076,fuzzy(last+first: goulart+brian(fn=0.70))
29602,tech,brussels-crucial-smashing,account_jUFnZ52urgAMNgp9XhT4bs,Graham McIntire,1,fuzzy(manual_override)
28935,chrislewis1,V9DS4E4Y7UTT,account_osB3NhyGdoWJRtpL5X2bfD,chris lewis,1787,fuzzy(last+first: lewis+chris(fn=1.00))
28917,crankkeith,kcrank10615,account_iSuzVkNmem52NdXXh1Savu,Keith Crank,209,fuzzy(last+first: crank+keith(fn=1.00))
28703,grahammcintire2,U5Y3hA3g*14L,account_jUFnZ52urgAMNgp9XhT4bs,Graham McIntire,1,fuzzy(last+first: mcintire+graham(fn=1.00))
28702,chrissyeagle2,NJPCGPBNFB,account_3uu7k4euycmKnZii9DQAj2,Chrissy Eagle,74,fuzzy(last+first: eagle+chrissy(fn=1.00))
28671,melodymccarty,U3WLEGEB,account_18HG9hdwwtZM83J9kyGMs6,Melody McCarthy,1397,fuzzy(sequence: 0.93)
28642,barbihardin,ZENETJ75TEVQ,account_bb7iRuPixxutKZxnURfK5U,James Hardin,1475,fuzzy(manual_override)
28628,jackworthy,2VUR7AAFWZZQ,account_hgoipVYXydD1yVSXTD9QGP,John Worthy,1461,fuzzy(last+first: worthy+john(fn=0.70))
28542,michaelray2,jjPgDcotL6rtY,account_8Rh7edwTjNBYw9FJSq4WRT,Michael Ray,379,fuzzy(last+first: ray+michael(fn=1.00))
28458,kennethcampbell2,V5TKW8L6RXFF,account_jF69QnP5j9FtfLgdAiUKfE,Kenneth D. Campbell,213,fuzzy(last+first: campbell+kenneth d(fn=1.00))
28296,cinthiapereira1,9WSGXM9LBXGZ,account_57mDq22QWRV8tdPvk74Uvj,Cinthia Pereira,1102,fuzzy(last+first: pereira+cinthia(fn=1.00))
29531,daycor,U45YBPSARH7N,account_aZmcKqobMWGKhxagDdLCFA,Cathy Day,755,fuzzy(manual_override)
29620,sayedahaque,YR4Sytbu,account_4hoTCjwTatthCg7zp2Cnfe,Syeda Haque,10048,fuzzy(last+first: haque+syeda(fn=0.70))
27417,donniecampbell,6a2671bc0c1,account_4ww28eUsFRv8Yh5RkWe9cz,Danny Campbell,92,fuzzy(last+first: campbell+danny(fn=0.70))
27418,alanwyatt,i1412r53MYr,account_2cnVCpSsYocCYpMEwwBBrY,Allen Wyatt,27,fuzzy(last+first: wyatt+allen(fn=0.70))
27436,clayrobertson2,C2147288546R,account_jgtX7TysTqcobVVBtSZEc2,Clay Robertson,77,fuzzy(last+first: robertson+clay(fn=1.00))
27501,jonnytaylor,Zb6677&4!yky,account_1fDruGS6CFTBUCGuzVZYTG,Johnny Taylor,190,fuzzy(last+first: taylor+johnny(fn=0.70))
27579,donnance,9727346814,account_s9ZW1FpS3wkDoh31CjU1Zw,Dana Nance,89,fuzzy(last+first: nance+dana(fn=0.70))
27838,consueloluna1830,C8736gfd0,account_jw16PcKtprHpPkSAEhvsaN,Consuelo Luna,81,fuzzy(last+first: luna+consuelo(fn=1.00))
27912,blancadiaz,B548gdu55D,account_ktdNSpKRV3ja7RMi7pZRBB,Balnca Diaz,433,fuzzy(last+first: diaz+balnca(fn=0.70))
27639,choonpang,CP2143855677cp,account_o7aKk2FGKJAcmrXxMfUCpX,Geok Choon Pang,146,fuzzy(last+first: pang+geok choon(fn=1.00))
27618,tammieventris,DR45QC74M,account_fMQ6qiwkPFWP8a86exFw4A,Terry Ventris,340,fuzzy(last+first: ventris+terry(fn=0.70))
27939,reynamoore,R92nbskba7iM,account_k8gD8YGUQzwWCE7JT9EfYc,Rebekah Moore,386,fuzzy(last+first: moore+rebekah(fn=0.70))
27781,pambanschbach,Q2p68#*9vwF2,account_6hGXtJ7RV7mqZYfd5AyE1f,Pam Bansbach,383,fuzzy(sequence: 0.88)
28107,jacquelinewilder,HEY36LUGYCUQ,account_hHa9Z64B9okZ3yccQP9WpR,Jacquelin Wilder,892,fuzzy(last+first: wilder+jacquelin(fn=1.00))
29554,marcuspatton,GEUXX27VLZXK,account_dxY6AZnLdFUWup8Sf6XER1,Marcus Patton,2458,ambiguous_resolved(lowest_id)
29606,mariacastanon,iu297gkid9,account_qh8bFPvWzyAqHqwHMDkThW,Maria Castanon,10007,ambiguous_resolved(lowest_id)
28800,kennethcampbell,!9727342446!,account_jF69QnP5j9FtfLgdAiUKfE,Kenneth D. Campbell,213,ambiguous_resolved(lowest_id)
28358,stephenday,MSVGQ8X38YLN,account_j1UFX8wqzorqSetUje5dTJ,Stephen Day,1168,ambiguous_resolved(lowest_id)
29630,sriahobilamuth,RTCESW24,account_7PgSr8dVuafybJdrXND3V9,Sri Ahobilamuth,113,ambiguous_resolved(lowest_id)
27404,grahammcintire,U5Y3hA3g*14L,account_jUFnZ52urgAMNgp9XhT4bs,Graham McIntire,1,ambiguous_resolved(lowest_id)
29524,chandparvathaneni,TK4QA3XM5XK5,,,,"still_unmatched(best: , score: 0.0)"
29358,radiantlifeministries,12FDIUNF5L7vM,,,,"still_unmatched(best: , score: 0.0)"
29296,midvalleyconcrete,BD58JYT2PJ6A,,,,"still_unmatched(best: , score: 0.0)"
29244,janiscable,JHC7U554JBTR,,,,"still_unmatched(best: last+weakfirst: cable+gary w(fn=0.11), score: 0.611)"
29150,donwilhelmus,2V4NEL87RM39,,,,"still_unmatched(best: , score: 0.0)"
29135,kavalleriefarm,8DFNNTPBPZGY,,,,"still_unmatched(best: , score: 0.0)"
29077,kellygarza,4696591662,,,,"still_unmatched(best: last+weakfirst: garza+esparanza(fn=0.42), score: 0.642)"
29070,1stchoiceliving,YVWJWRPEVYNH,,,,"still_unmatched(best: , score: 0.0)"
28879,kimberlyrichards,7PQA6J8RPZ9Z,,,,"still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)"
28599,ryanrodriguez,9DFYWJ47YWUP,,,,"still_unmatched(best: last+weakfirst: rodriguez+mike(fn=0.19), score: 0.619)"
28460,saidaacosta,V29897X4,,,,"still_unmatched(best: last+weakfirst: acosta+alma(fn=0.21), score: 0.621)"
28422,jackiehendricks,UQR5HGFCJ4XX,,,,"still_unmatched(best: last+weakfirst: hendricks+sue(fn=0.09), score: 0.609)"
28400,bonniestone,BH8BV44TTWUU,,,,"still_unmatched(best: , score: 0.0)"
28336,buddyswan,N7XUYUD7J9SD,,,,"still_unmatched(best: , score: 0.0)"
28268,alberto,TICOGUILLO,,,,"still_unmatched(best: , score: 0.0)"
27408,penneywarner,b4h53N6c#5wo,,,,"still_unmatched(best: , score: 0.0)"
29626,karengreen,UTKCYI674,,,,"still_unmatched(best: last+weakfirst: green+dominique(fn=0.17), score: 0.617)"
27441,samanthasmith,22jmR9X#93j5,,,,"still_unmatched(best: last+weakfirst: smith+kathy(fn=0.27), score: 0.627)"
27489,jenniferreed,683P7V7R,,,,"still_unmatched(best: , score: 0.0)"
27482,irvinbarojas,yDeEJzYY08,,,,"still_unmatched(best: , score: 0.0)"
27463,dennispereyra,82s62kd9XHhd,,,,"still_unmatched(best: , score: 0.0)"
27709,sensibleheatsystems,SHQWERY23,,,,"still_unmatched(best: , score: 0.0)"
27704,lambandlion,LL9727363567ll,,,,"still_unmatched(best: , score: 0.0)"
27625,mattbud,kjklfda79sd,,,,"still_unmatched(best: , score: 0.0)"
127621,richardhill,RH9729788124rh,,,,"still_unmatched(best: last+weakfirst: hill+ellen(fn=0.20), score: 0.62)"
27955,evlynrichter,FRQD738BGRC,,,,"still_unmatched(best: last+weakfirst: richter+brent(fn=0.19), score: 0.619)"
27755,gordonhamilton,c3495Rpuq5Sh,,,,"still_unmatched(best: last+weakfirst: hamilton+sheryl f(fn=0.16), score: 0.616)"
27793,branchrvpark,XfKxVZIKmm,,,,"still_unmatched(best: , score: 0.0)"
29601,kimberlyrichards2,K84f7r10GT108,,,,"still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)"
28213,krisdougherty,RWEH75UJ38,,,,"still_unmatched(best: , score: 0.0)"
1 radius_id username password gaiia_id gaiia_name gaiia_readable_id match_type
2 29521 tiffanyyarley-olenak 98HTZ9Q8FARG account_cwmjmXkrYeba8h8RGqLm6E Tiffany Yarley-Olenak 2393 fuzzy(sequence: 0.95)
3 29395 vanessagomez1 J5JEW3HEBVAF account_gb82EhqTwbNWWEovPu4oKZ Vanessa Gomez 2264 fuzzy(last+first: gomez+vanessa(fn=1.00))
4 29611 brittannieredden1 TYFTUEZ7429 account_bB6RDmNpkzndVbSh9CvgaL Brittannie Redden 10017 fuzzy(last+first: redden+brittannie(fn=1.00))
5 29614 samanthatheis kf6u8djigk account_x46hMNq15qHmEvP6fNFdfm Samatha Theis 10023 fuzzy(last+first: theis+samatha(fn=0.70))
6 29216 wanxiaxong BER4NEK9M7NA account_tiy5YHD34KpwPzUTXyRVFJ Wanxia Song 2077 fuzzy(sequence: 0.86)
7 29215 bryangoulart DTNQFE4CXSFX account_9sksoKmcJGxqhz4M4aD169 Brian Goulart 2076 fuzzy(last+first: goulart+brian(fn=0.70))
8 29602 tech brussels-crucial-smashing account_jUFnZ52urgAMNgp9XhT4bs Graham McIntire 1 fuzzy(manual_override)
9 28935 chrislewis1 V9DS4E4Y7UTT account_osB3NhyGdoWJRtpL5X2bfD chris lewis 1787 fuzzy(last+first: lewis+chris(fn=1.00))
10 28917 crankkeith kcrank10615 account_iSuzVkNmem52NdXXh1Savu Keith Crank 209 fuzzy(last+first: crank+keith(fn=1.00))
11 28703 grahammcintire2 U5Y3hA3g*14L account_jUFnZ52urgAMNgp9XhT4bs Graham McIntire 1 fuzzy(last+first: mcintire+graham(fn=1.00))
12 28702 chrissyeagle2 NJPCGPBNFB account_3uu7k4euycmKnZii9DQAj2 Chrissy Eagle 74 fuzzy(last+first: eagle+chrissy(fn=1.00))
13 28671 melodymccarty U3WLEGEB account_18HG9hdwwtZM83J9kyGMs6 Melody McCarthy 1397 fuzzy(sequence: 0.93)
14 28642 barbihardin ZENETJ75TEVQ account_bb7iRuPixxutKZxnURfK5U James Hardin 1475 fuzzy(manual_override)
15 28628 jackworthy 2VUR7AAFWZZQ account_hgoipVYXydD1yVSXTD9QGP John Worthy 1461 fuzzy(last+first: worthy+john(fn=0.70))
16 28542 michaelray2 jjPgDcotL6rtY account_8Rh7edwTjNBYw9FJSq4WRT Michael Ray 379 fuzzy(last+first: ray+michael(fn=1.00))
17 28458 kennethcampbell2 V5TKW8L6RXFF account_jF69QnP5j9FtfLgdAiUKfE Kenneth D. Campbell 213 fuzzy(last+first: campbell+kenneth d(fn=1.00))
18 28296 cinthiapereira1 9WSGXM9LBXGZ account_57mDq22QWRV8tdPvk74Uvj Cinthia Pereira 1102 fuzzy(last+first: pereira+cinthia(fn=1.00))
19 29531 daycor U45YBPSARH7N account_aZmcKqobMWGKhxagDdLCFA Cathy Day 755 fuzzy(manual_override)
20 29620 sayedahaque YR4Sytbu account_4hoTCjwTatthCg7zp2Cnfe Syeda Haque 10048 fuzzy(last+first: haque+syeda(fn=0.70))
21 27417 donniecampbell 6a2671bc0c1 account_4ww28eUsFRv8Yh5RkWe9cz Danny Campbell 92 fuzzy(last+first: campbell+danny(fn=0.70))
22 27418 alanwyatt i1412r53MYr account_2cnVCpSsYocCYpMEwwBBrY Allen Wyatt 27 fuzzy(last+first: wyatt+allen(fn=0.70))
23 27436 clayrobertson2 C2147288546R account_jgtX7TysTqcobVVBtSZEc2 Clay Robertson 77 fuzzy(last+first: robertson+clay(fn=1.00))
24 27501 jonnytaylor Zb6677&4!yky account_1fDruGS6CFTBUCGuzVZYTG Johnny Taylor 190 fuzzy(last+first: taylor+johnny(fn=0.70))
25 27579 donnance 9727346814 account_s9ZW1FpS3wkDoh31CjU1Zw Dana Nance 89 fuzzy(last+first: nance+dana(fn=0.70))
26 27838 consueloluna1830 C8736gfd0 account_jw16PcKtprHpPkSAEhvsaN Consuelo Luna 81 fuzzy(last+first: luna+consuelo(fn=1.00))
27 27912 blancadiaz B548gdu55D account_ktdNSpKRV3ja7RMi7pZRBB Balnca Diaz 433 fuzzy(last+first: diaz+balnca(fn=0.70))
28 27639 choonpang CP2143855677cp account_o7aKk2FGKJAcmrXxMfUCpX Geok Choon Pang 146 fuzzy(last+first: pang+geok choon(fn=1.00))
29 27618 tammieventris DR45QC74M account_fMQ6qiwkPFWP8a86exFw4A Terry Ventris 340 fuzzy(last+first: ventris+terry(fn=0.70))
30 27939 reynamoore R92nbskba7iM account_k8gD8YGUQzwWCE7JT9EfYc Rebekah Moore 386 fuzzy(last+first: moore+rebekah(fn=0.70))
31 27781 pambanschbach Q2p68#*9vwF2 account_6hGXtJ7RV7mqZYfd5AyE1f Pam Bansbach 383 fuzzy(sequence: 0.88)
32 28107 jacquelinewilder HEY36LUGYCUQ account_hHa9Z64B9okZ3yccQP9WpR Jacquelin Wilder 892 fuzzy(last+first: wilder+jacquelin(fn=1.00))
33 29554 marcuspatton GEUXX27VLZXK account_dxY6AZnLdFUWup8Sf6XER1 Marcus Patton 2458 ambiguous_resolved(lowest_id)
34 29606 mariacastanon iu297gkid9 account_qh8bFPvWzyAqHqwHMDkThW Maria Castanon 10007 ambiguous_resolved(lowest_id)
35 28800 kennethcampbell !9727342446! account_jF69QnP5j9FtfLgdAiUKfE Kenneth D. Campbell 213 ambiguous_resolved(lowest_id)
36 28358 stephenday MSVGQ8X38YLN account_j1UFX8wqzorqSetUje5dTJ Stephen Day 1168 ambiguous_resolved(lowest_id)
37 29630 sriahobilamuth RTCESW24 account_7PgSr8dVuafybJdrXND3V9 Sri Ahobilamuth 113 ambiguous_resolved(lowest_id)
38 27404 grahammcintire U5Y3hA3g*14L account_jUFnZ52urgAMNgp9XhT4bs Graham McIntire 1 ambiguous_resolved(lowest_id)
39 29524 chandparvathaneni TK4QA3XM5XK5 still_unmatched(best: , score: 0.0)
40 29358 radiantlifeministries 12FDIUNF5L7vM still_unmatched(best: , score: 0.0)
41 29296 midvalleyconcrete BD58JYT2PJ6A still_unmatched(best: , score: 0.0)
42 29244 janiscable JHC7U554JBTR still_unmatched(best: last+weakfirst: cable+gary w(fn=0.11), score: 0.611)
43 29150 donwilhelmus 2V4NEL87RM39 still_unmatched(best: , score: 0.0)
44 29135 kavalleriefarm 8DFNNTPBPZGY still_unmatched(best: , score: 0.0)
45 29077 kellygarza 4696591662 still_unmatched(best: last+weakfirst: garza+esparanza(fn=0.42), score: 0.642)
46 29070 1stchoiceliving YVWJWRPEVYNH still_unmatched(best: , score: 0.0)
47 28879 kimberlyrichards 7PQA6J8RPZ9Z still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)
48 28599 ryanrodriguez 9DFYWJ47YWUP still_unmatched(best: last+weakfirst: rodriguez+mike(fn=0.19), score: 0.619)
49 28460 saidaacosta V29897X4 still_unmatched(best: last+weakfirst: acosta+alma(fn=0.21), score: 0.621)
50 28422 jackiehendricks UQR5HGFCJ4XX still_unmatched(best: last+weakfirst: hendricks+sue(fn=0.09), score: 0.609)
51 28400 bonniestone BH8BV44TTWUU still_unmatched(best: , score: 0.0)
52 28336 buddyswan N7XUYUD7J9SD still_unmatched(best: , score: 0.0)
53 28268 alberto TICOGUILLO still_unmatched(best: , score: 0.0)
54 27408 penneywarner b4h53N6c#5wo still_unmatched(best: , score: 0.0)
55 29626 karengreen UTKCYI674 still_unmatched(best: last+weakfirst: green+dominique(fn=0.17), score: 0.617)
56 27441 samanthasmith 22jmR9X#93j5 still_unmatched(best: last+weakfirst: smith+kathy(fn=0.27), score: 0.627)
57 27489 jenniferreed 683P7V7R still_unmatched(best: , score: 0.0)
58 27482 irvinbarojas yDeEJzYY08 still_unmatched(best: , score: 0.0)
59 27463 dennispereyra 82s62kd9XHhd still_unmatched(best: , score: 0.0)
60 27709 sensibleheatsystems SHQWERY23 still_unmatched(best: , score: 0.0)
61 27704 lambandlion LL9727363567ll still_unmatched(best: , score: 0.0)
62 27625 mattbud kjklfda79sd still_unmatched(best: , score: 0.0)
63 127621 richardhill RH9729788124rh still_unmatched(best: last+weakfirst: hill+ellen(fn=0.20), score: 0.62)
64 27955 evlynrichter FRQD738BGRC still_unmatched(best: last+weakfirst: richter+brent(fn=0.19), score: 0.619)
65 27755 gordonhamilton c3495Rpuq5Sh still_unmatched(best: last+weakfirst: hamilton+sheryl f(fn=0.16), score: 0.616)
66 27793 branchrvpark XfKxVZIKmm still_unmatched(best: , score: 0.0)
67 29601 kimberlyrichards2 K84f7r10GT108 still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)
68 28213 krisdougherty RWEH75UJ38 still_unmatched(best: , score: 0.0)

View file

@ -0,0 +1,416 @@
radius_id,username,password,gaiia_id,gaiia_name,gaiia_readable_id,match_type
29581,joseortiz,J2WEYT7782UJ,account_4jKF2XxS1tjfAiTJLsFcGJ,Jose Ortiz,2485,exact
29578,perlachavez2,U4BBTBMCJZST,account_sSL9o2BAvbEHXAkxWtPXxV,Perla Chavez 2,2482,exact
29575,thomashernandez,X6BZLA7SC3XM,account_wL9tsaty83QCJpNGNXeSzZ,Thomas Hernandez,2479,exact
29572,williamarmstead,SAQ4XEKXDC58,account_5Eqdv89tv7SUwTjGqQWJdh,William Armstead,2476,exact
29570,julierose,MLM8R2WAGY52,account_9hvebUFe6UDfaZb9omVkB8,Julie Rose,2474,exact
29571,derrickmccausland,ETNLKZHACGF7,account_89xduFTiVvRoEQpENiaVmT,Derrick Mccausland,2475,exact
29567,sherrifredrick,7P52BVMTSNN6,account_3eF55epzgGSPhDU1DFBm53,Sherri Fredrick,2471,exact
29565,shelbyburris,3YAL9DWFZUPT,account_9GNShE9ayAX3fkjwSryGq7,Shelby Burris,2469,exact
29564,priscillacrenshaw,AXBDLXSGXMLR,account_2MK9ykEGzWyxvg1SZMmagK,Priscilla Crenshaw,2468,exact
29561,korybiggs,4FJC7DXAW5RV,account_ftDbZQGSaf8dowpdjC44sU,Kory Biggs,2465,exact
29563,kristinjimenez,9FAYXMRDU69T,account_bdpYxupJ3cfQ5bmhNroy8i,Kristin Jimenez,2467,exact
29558,jessicaweddle,VV96G36AEEDA,account_khW6HArsQ677gL18hcQnEN,Jessica Weddle,2462,exact
29557,yolandamedrano,R948UL4KZRA4,account_4tZA9NMygTcq3fidSxDz5h,Yolanda Medrano,2461,exact
29555,williambowland,K7HYNWGE25XX,account_6fhZdgAsyd9zJ2FiXURCJ2,William Bowland,2459,exact
29553,shacaciabillings,8AZLJCDFYFYV,account_pDDXT9Lh8QsE2roPyeVfS8,Shacacia Billings,2457,exact
29552,guadalupelugo,DQG8XCMFMVH5,account_haPf3cXEdK4TQMzqZi5LYV,Guadalupe Lugo,2456,exact
29551,brandonhardesty,KMRHVPEXBEZ8,account_izj2Jd1nYA49rAcvNu6TfZ,Brandon Hardesty,2455,exact
29550,nancyavellaneda,CXATYV7NGZN2,account_99jecysxoYGBHUhd97ZYmt,Nancy Avellaneda,2454,exact
29549,matthewgoodwin,VSPM7N9UNU6Y,account_ocezHaQ4JPvu1EbRMk4hu5,Matthew Goodwin,2453,exact
29545,donmckinney,C6AQ6UVD6R7R,account_viJJswZvVXEbxLBikRjGJz,Don Mckinney,2449,exact
29544,natashaelmore,8G5QJ8R7MK8Q,account_bGYACsz7a5kdyDBDecBTZY,Natasha Elmore,2448,exact
29543,shamsbashir,WLBGY4JMN4VA,account_vtGoVXJRkxWw3ZEYDKi9Bu,Shams Bashir,2415,exact
29541,douglasglover,9Y6H9X3HCG3D,account_hzZvaSXgCuhMUTXjpaCQjA,Douglas Glover,2413,exact
29538,carolstrickland,547P6AQ8RCD4,account_u8W2U31Z3LJgzNcD2qNfcX,Carol Strickland,2410,exact
29537,carmenlopez,TPLND3H2FWTQ,account_75ZP8Rhk7DBvwEA83HaWBB,Carmen Lopez,2409,exact
29534,johnlooper,ZPL8LRY2TGRH,account_iijdXYXJAhg1mDm1FRuUrc,John Looper,2406,exact
29535,davidroesch,ZHB2BS968DTU,account_bHVKB5E3eJFmbqnDwXAxFw,David Roesch,2407,exact
29530,derekrodriguez,AP7Z594DMR7E,account_8MmcYW5A3KpMsyBJjiQMfE,Derek Rodriguez,2402,exact
29522,joshuasoliz,PE7MHRZWUEQM,account_fPwqeAaDrYSWtRyVjBk8HD,Joshua Soliz,2394,exact
29514,ericbarrett,5S2CV9FEFMKG,account_fhkdvYwigwTL9hR4Stx8Tf,Eric Barrett,2386,exact
29513,marysmelser,ZS74NS5BBPX3,account_sN8UFBKJy6HHSCXSQp8oTB,Mary Smelser,2385,exact
29506,nicolemaenn,B7KS27WL5WSN,account_pJFWGZXwDitJCT55tcuqeE,Nicole Maenn,2378,exact
29503,yolandajudge,6V8RLNK75LM8,account_cWR4qvSM1SepG3DuoD8rUz,Yolanda Judge,2375,exact
29501,craighovde,AVZXWBX8GQTC,account_q9j2WgU1236uDnx5rrEx8U,Craig Hovde,2373,exact
29498,joselucas,ZHMW9TE8QHRX,account_izByTUMCPK7UT8k6Zncc4e,Jose Lucas,2370,exact
29488,douggarber,WXP9J5SRJWEJ,account_vyxx47PALJmyUZZe3m495N,Doug Garber,2360,exact
29482,carmencleghorn,VH3CBTJZAJ2Y,account_cRZQwgdsQLcok1dzXy6U5c,Carmen Cleghorn,2354,exact
29472,rubenreyez,66384Y7B4WR4,account_aC929RXGkB1nsEHu95kg5v,Ruben Reyez,2344,exact
29464,tiffanystewart,3EGVCS6RMF3Z,account_h3oACsArSoq5fZ6vRyH9Br,Tiffany Stewart,2335,exact
29449,dejadodson,FLR4DJHPX6TG,account_57jxCEcSpkMPUEVxDoZddc,Deja Dodson,2320,exact
29447,coreyball,3JAUWZNAR9MB,account_8fvkQS2hERUdtSF7DXrzpw,Corey Ball,2318,exact
29442,toniyoung,RJ24VTRM7JA9,account_mXgG1ZhfDfrnkU6usym3kG,Toni Young,2312,exact
29434,melanieweddle,XQ5L7PUM9RP6,account_piQoRw8BbqZpckbweTGdDu,Melanie Weddle,2304,exact
29431,zackknuckey,VP5CCX8LTE47,account_uh8Yfj5mdjaQjofM19p9MU,Zack Knuckey,2301,exact
29425,christinehamparyan,WEYX9R5P7J5L,account_aJuvQYQACJ5Ms48bAR4hWY,Christine Hamparyan,2295,exact
29422,joespeciale,UH6ZCM9WLGRQ,account_2BwbPKVKpj7QnFFpdZVqn3,Joe Speciale,2292,exact
29420,rogerbatt,KGDJZ7V66QQK,account_2gaAXfFo32nXEh7qA2yX8M,Roger Batt,2290,exact
29419,kalebjames,KMQDGPD5XB6Z,account_67qbP4FpqwKmqSNjSmtCBv,Kaleb James,2289,exact
29416,stevemolina,BLLHWPY9RCTC,account_c5VgNVVcBTRkHjbzFDEpHk,Steve Molina,2286,exact
29414,mariatrejo,EYPWCQKKWYZH,account_ahaKfCrUTPA2mx7oM2i7Xu,Maria Trejo,2284,exact
29410,kenhall,LX897BGJNMK5,account_gZPLTs7y5We8wW5MZyZjkX,Ken Hall,2280,exact
29407,deewilliams,R8AGX3E8WAHN,account_kjSroty8KmGKCTEArNC9Pa,Dee Williams,2276,exact
29406,johnvayo,DJJNUEEHGAW5,account_7hUzHAkpHagRdjPWYoRP9R,John Vayo,2275,exact
29398,anthonythompson,WB4E4MPJVHTB,account_4CLddQNDWC76dtRwmYRdeZ,Anthony Thompson,2267,exact
29608,stevecombest,STVN23552Wt,account_aM6WQ3aysaRPx4zpUh3zWD,Steve Combest,10015,exact
29607,luisvelira,uygo875491!,account_3wT7DTZj4An5jaitLTTWTA,Luis Velira,10013,exact
29609,keithtucker,14FYJSNUU48,account_7Wjw5Lru3JmVxq4s6cRqvn,Keith Tucker,10016,exact
29610,angelatrevino,KJGU39565,account_4yPBWjMpwT4tVHJyVTDiYX,Angela Trevino,10014,exact
29612,amymontelongo,ig76f7fuvu,account_o7rGD6dKJP27Sf9AUMK2e2,Amy Montelongo,10020,exact
29613,michaelasmith,MS21353GHY,account_t23HG5uEXET828jSbivozo,Michaela Smith,10022,exact
29615,janisstewart,JD834HCUW1C,account_fztV7NVRixwgnD6n392ZfX,Janis Stewart,10024,exact
29616,irenegaribay,uky75dkc,account_ctACFtZH9mDX1E9tVgkpzy,Irene Garibay,10027,exact
29383,leonardocoronel,TXPC494,account_bLMBbmXsvurdQQaSokRzTf,Leonardo Coronel,2248,exact
29374,alejandrolimon,MM4BSQS7FRTD,account_gqFbPtxAGRTSizXXLFB9j3,Alejandro Limon,2239,exact
29368,crystalharney,A7W26H834WLJ,account_bzPJ8YAjKNt5Bn7zhYf8zw,Crystal Harney,2233,exact
29355,olgagutierrez,WERWRMUDBZ85,account_hswERjNGcJtQnPWCkMnEQs,Olga Gutierrez,2220,exact
29353,greghummel,JV84HSVAK6XR,account_myUaLbfFVED7hqEBBY1Pe4,Greg Hummel,2218,exact
29349,bettymitchell,8CHHCNBWQLRV,account_t4wwpPyK98WBZUqLnwXKNU,Betty Mitchell,2214,exact
29341,wandadavis,JXQXXZ9MKFX6,account_iy49cEMsdawi18rTXarw73,Wanda Davis,2206,exact
29338,scottanderson,RJ49A43SG3RA,account_sDPK3iLsxwrLzndTwh2uxb,Scott Anderson,2203,exact
29335,jenniferboon,28ESU8ELVELP,account_3aCW8JeazPNe4UxjtGXzrp,Jennifer Boon,2200,exact
29331,marialopez,P3FNSPT7MEYM,account_mjnZnogDMDws9ueMDxVzaP,Maria Lopez,2196,exact
29329,cynthiasandlin,MCDTLMVFQXAV,account_sFzFdm2TTpyRaur4JfF3bv,Cynthia Sandlin,2194,exact
29328,bethanybell,3UUC5X27PETS,account_wnvmzBJwB3r7pG6a6WNANx,Bethany Bell,2193,exact
29323,garrettojeda,U5PRQYZE2MM3,account_chsr6oKpnEYuEKkMXn7CKF,Garrett Ojeda,2188,exact
29325,rayschaak,XSNLP62EK6VZ,account_qhFf5wpTsv3FySr63biaRp,Ray Schaak,2190,exact
29327,carlaswearingen,E6DQ4B2T9JZD,account_rzEn7BHU6Dmd6PkRwRpNXB,Carla Swearingen,2192,exact
29320,lesliebewley,55KFSJMEDYLK,account_jeuzxdVtC5u4W8utfKf5vc,Leslie Bewley,2185,exact
29317,delainawaite,EM7TWHUS4SYU,account_kvePVE9fNZCokbkkvpBvLK,Delaina Waite,2182,exact
29307,esparanzagarza,VLL9ZNPRSTG5,account_dXq5FqPpKhAwE96NT5D9Jk,Esparanza Garza,2172,exact
29273,glendabeauchamp,RKA3J8WSPYJP,account_fJVr5CSPdsgWtLNRWkidro,Glenda Beauchamp,2133,exact
29256,glindarichter,VXMLMDAFLNJ3,account_2ngDjpr4VqDCPsv2SNMHvS,Glinda Richter,2117,exact
29251,nhumorrison,EC32VDFKB9CB,account_hZpk1bujwvFdyctrbfJtjS,Nhu Morrison,2111,exact
29245,brittanyworley,NBKYERRBSBH4,account_r39MCaQ811VXnw6QbhSG4b,Brittany Worley,2106,exact
29243,dustynfisher,XASFU3DBPZ5M,account_sAYv3pn9D1KCwWr1TzGyeu,Dustyn Fisher,2104,exact
29237,stephenbeegle,RU5GZVTVACRC,account_7RPLicRiJMb3Y1x3C4tEQJ,Stephen Beegle,2098,exact
29229,roseanndellavalle,5GAF6JHUWWYP,account_wTXj1c59SU9iRzeRmgv2dT,Roseann Dellavalle,2090,exact
29231,duanewright,VXK4FBLVFE7F,account_3Hwm7znAWKLPMhmY2avtnG,Duane Wright,2092,exact
29222,pablohernandez,P3YB6A4JGEDF,account_vkMiPuMKu6XmQt1G9CP1op,Pablo Hernandez,2083,exact
29217,pavelavanesov,KP8ZSGC2DG2U,account_kNWT9ewUppfxMtxpcgScKZ,Pavel Avanesov,2078,exact
29204,sandrahoughton,5XR4QMPBXVWY,account_sDBLs44VLbWy9ZRhuDAkpL,Sandra Houghton,2065,exact
29203,adamstaeben,GP7RANJ8UCYP,account_u39DmoPasPkSoYJ3bTG8kz,Adam Staeben,2064,exact
29190,russmott,J8ZN888UQSLR,account_7QghTwmH6MwtZdTrvur8uB,Russ Mott,2051,exact
29189,karenstewart,JSKTFAJ2JYDX,account_wSUPq2ZjUF1Va7S3vap91M,Karen Stewart,2050,exact
29185,heatherwomack,89W7S5Q7UAHP,account_kSE9uAW6WPbt4msXe4mzHs,HEATHER WOMACK,2046,exact
29183,wyndijames,CPSDNRQU95HJ,account_hJwbN2EUqGCtNj45JCyQda,Wyndi James,2044,exact
29182,angelicacuayahuitl,VK8PZW3LK5F3,account_sooioQ729DC21LJa1zKMAN,Angelica Cuayahuitl,2043,exact
29181,chasewilliams,W6AMPQEPY3BC,account_iQqfYFDTb4UxtHcgrGt8EP,Chase Williams,2042,exact
29176,kevinarana,JAJYSEG75UZS,account_oR5nKGJTvRLhkCyUGG1B1L,Kevin Arana,2037,exact
29166,chrisclayton,BFJ462PLHJFV,account_aamYj92hC8r7nxvbAfiHCE,Chris Clayton,2027,exact
29162,billyhorton,MPTX573ZUYYG,account_4DuPXfEqx5pRPftTGKkbhS,Billy Horton,2023,exact
29151,chisediquezada,RJBYQ2NUB829,account_1KjfxaKx8MvMEwb7uU3eSC,Chisedi Quezada,2011,exact
29147,angelmerlo,EWWAPXN9Z2YS,account_deGyZc4zP5TBJGSibkGU3a,Angel Merlo,2007,exact
29113,kerryblack,9D5S9ZD2FCBV,account_qncL4zimMnsq5ViZrpNXr1,Kerry Black,1974,exact
29110,cliffordjennings,5GC7M65H9VGT,account_rknD3GcgUkTd8XB7rZZjND,Clifford Jennings,1971,exact
29098,carlakimberling,SS4KRJYKTV7B,account_2hJ8frAhU5LDVpaoSH27Tx,Carla Kimberling,1959,exact
29088,richardconklin,WEUNG5H3K8BB,account_7aEgkmhMKwDVqsus5Ln8Bd,RICHARD CONKLIN,1949,exact
29089,leandrabeshea,7V8BZU4G83VF,account_doS7HkZWacVd5r8nWxqDFZ,Leandra Beshea,1950,exact
29067,robertfinch,D7FMTT455GBB,account_jJoU6UPYd2ckqbUhynBx7B,Robert Finch,1928,exact
29058,ericcoffman,MMEBXQYLZWNU,account_4mZAzz4GiGtMU8CJBWs1cQ,Eric Coffman,1919,exact
29050,nathanaelfinch,JVNC68FV7N6B,account_t87r46C6jaw8rjJYurmHrY,Nathanael Finch,1911,exact
29047,gabrielasereno,KCH82WCRA2YR,account_99RoiQcJpubSAqajPZjxFf,Gabriela Sereno,1908,exact
29045,georginacovarrubias,8QT57NUV8C7R,account_gERfDmYXtJj5PLCY9ELLS7,Georgina Covarrubias,1906,exact
29035,thomascline,E523BKVU5MJP,account_g8yP63NFmbioePagUH1kQS,Thomas Cline,1894,exact
29034,susanlewis,R7TZFZEGLNRW,account_mqMu7hR16hGpAesQ3ba83B,Susan Lewis,1893,exact
29025,tammywelborn,RMC482WNBS7,account_rjvhtkZxuk442ghJaKsdtG,Tammy Welborn,1878,exact
29023,haleymuela,BS7X3DTKW8TH,account_hmnByz3wfiafSujapZBbi3,HALEY MUELA,1884,exact
29018,christelles,WL2TKLFG8G2H,account_uGdvd35CHuBkiBamMnB8Bj,Chris Telles,1879,exact
29017,mauricioantonio,6QJW7RAPDBWB,account_tX9wv8qaDb4bCFBxKPsMDe,Mauricio Antonio,1877,exact
29013,vincentlimon,YDDXKFABUZY7,account_eZ6nJGAsmsPwR8sYvtXBRw,Vincent Limon,1873,exact
29634,jeffreyharder,YUV565D67RT,account_gWD9gTNmR8RbzQkJmjpRYy,Jeffrey Harder,10073,exact
29635,pablomartinez,2766581BER,account_g2THEPVu4zgnGsTeFh6MpF,Pablo Martinez,10074,exact
29636,krystalmendez,6FTCKU6S56,account_hiH7WbsYQm1HRkpGA3jJ2v,Krystal Mendez,10080,exact
29637,davidvandagriff,UTRC53865F9,account_kAa1VyPLqz5fecRMXpV7d4,David Vandagriff,10082,exact
29638,carolynwalker,trjxtyx75!,account_wsZpXzz2mhYMxMLmiVGNvF,Carolyn Walker,10084,exact
28979,claygilbert,3E6XFDHXCAG7,account_eVxdo9RAAoBbisk1EYCKnc,CLAY GILBERT,1839,exact
28978,jmichaelculverhouse,2147099521,account_q4nxMiXUoEj2DkH3JUspLP,J'michael Culverhouse,1838,exact
28971,diegosereno,2CE9F5YSPQHX,account_vvPvzpGka7xmJQnqE3gMxq,Diego Sereno,1830,exact
28970,ashleysmith,F8D3FVCZB7PD,account_5nSRJ7EkJpz5dpB2QGRYfz,Ashley Smith,1829,exact
28954,dougstowe,5D35LFD6GEAZ,account_3hQJhhHf48sszYWBaMTjpR,Doug Stowe,1807,exact
28936,timbronschidle,3ZVLCWSAE3EW1,account_o6gDBbdWzQYeUbAQL2Zp1s,Tim Bronschidle,1788,exact
28928,cynthiajones,KC7ESYHEZGNR,account_hJ5wonckmH2jkaykKH2g4Q,Cynthia Jones,1778,exact
28867,bobbymack,UDUBTCXXF8VV,account_nNUBZtEptfAD6TpDteqjfM,Bobby Mack,1712,exact
28848,americatrejo,3B63TLTT9WE4,account_gjzpfGyVuYMaQwdYA2Gufy,America Trejo,1693,exact
28832,tjbanschbach,8WTWTXACGG5N,account_hU5voij99AKqckcGpok7Jx,T.J. Banschbach,1676,exact
28811,luiscabrera,RWUQ94XT9A9Z,account_mrPPtWesWBzmcD4Lfvp2hA,luis Cabrera,1650,exact
28810,rosahernandez,JSGUXKWEMSVN,account_qYeNtZSW1nYgukr9kYy5qn,Rosa Hernandez,1649,exact
28807,carlosgomez,J22RS7F3LGG2,account_jNj9rpkTwq7wF3vJbMuAkF,Carlos Gomez,1645,exact
28763,jeridrutledge,GJ3CZTRJJW2M,account_v6odXP1nVxoVvTMUrUn3SS,Jerid Rutledge,1601,exact
28748,timthomas,NY223RQJD7PN,account_m5ZTZYHZbzpVPeNfRhroxn,Tim Thomas,1585,exact
28715,carolbates,J3UBUCMYDX64,account_97wbzzkGPxCPMyfUs5uTTf,Carol Bates,1543,exact
28710,shirleyelmore,D8Y64N9L4R9W,account_gfPawVk3gSuvufuC4zVjx9,Shirley Elmore,1538,exact
28698,swedlund,swedlund494,account_u9nP4wDufL6eQGx3Jnoyn6,Brian Swedlund,357,exact
28695,wendysanders,RYG9L43NWDZV,account_4KPsmHxqdcnsRNXuWB8s2Y,Wendy Sanders,1524,exact
28693,robbymccollom,RMC482WNBS7,account_xfht7KCxBpasoRqtkTojjj,Robby McCollom,297,exact
28664,heatherbrister2,KK37V8W9ZD9Z,account_m1hacYUopGGJrn2UyJvzBu,Heather Brister 2,1495,exact
28663,ryanlloyd,HWEMKNWX55RF,account_pFHZ8RMuvdETPpHr4vyDWi,Ryan Lloyd,1494,exact
28661,amyrodriguez,WX3QRT2U6L4X,account_w1ywM8kUBcURQHGjHQqirn,Amy Rodriguez,1492,exact
28660,thecountrycutter,YXpScFIneePm,account_1uu6CnSCUuCfBabMwfikLz,The Country Cutter,396,exact
28659,bradwilson,ZFAXA9AK63AH,account_ujyp8hfbksvc8EZALQ5gTL,Brad Wilson,1491,exact
28654,edrater,FRY4823N,account_fNtXkw98GQKVvcjcfbeb9F,Ed Rater,126,exact
28637,vikyacosta,Z4SMV49SAH72,account_mma7TzxJUZKUzXYr6YDUmt,Viky Acosta,1470,exact
28633,terrimack,B9HNTSN7ASQ4,account_wU2qgfTxqjaLSfhwWx4Cxm,Terri Mack,1467,exact
28617,luisarellano,GVNWBQMM3S7U,account_t9DUQUCtBAWojjSYtZQusE,Luis Arellano,1450,exact
28609,dorisavalos,DLKMJS88AF2W,account_pMdxeChdNqnVRQs1rSAKBE,Doris Avalos,1441,exact
28597,jackierollier,TSFNN8DVDSPR,account_w5t1gDksfLnZqU35RFxZES,Jackie Rollier,1429,exact
28588,charlesmccreary,88J6XS6TXRMA,account_dQ3cvFTw98ByhcBzDGMsNQ,Charles McCreary,1418,exact
28548,jamessmith,LRVU6RVG5SP8,account_ayvJr4cDCtK3zNPcoVo5vy,James Smith,1373,exact
28546,davidwhitefoot,PYHTZMHJU8R8-,account_sWngcEfTk4Ed83ARWYtZga,David Whitefoot,1371,exact
28536,patriciavasquez,P88256487V,account_6wngyAj64jCwvn3W8T3sdT,Patricia Vasquez,1128,exact
28535,adrianmcdaniel,U4TGDH2PSAB4,account_aScSfG25Wwo7cD4xbNhja1,ADRIAN MCDANIEL,1362,exact
28526,justinvoga,URHVCX7SFSFU,account_cwmXrKm93bSteHQfm3SJtd,Justin Voga,1351,exact
28518,jenniferlittle,8EAUUDQPXSQX,account_oSA465twDMxZedY5zbPLT1,Jennifer Little,1343,exact
28510,bradslate,AF4KVQ2P44M7,account_nhYxp4RV7gpLxWnLL9oWs6,Brad & Sherry Slate,1334,exact
28494,irisdelira,L6XGQA583DG6,account_mVgspPEmufZG8KD83Cnyjh,Iris Delira,1316,exact
28491,richardbarragan,WJBS86MMJBFH,account_hJ3iUD77LRADfhaxwwsqLj,richard barragan,1314,exact
28476,jensenhall,F8XM9U8SC6LL,account_m3356qm8wGGJDqNZCgXRev,Jensen Hall,979,exact
28466,paulcomella,YTZDMBZCMPX4,account_rRKbPsCejKqMnAb29xPPFx,Paul Comella,967,exact
28449,amberkrings,5NWBR3JLV6N8,account_eB2k88UWVdAFKYgcg7iK4h,amber krings,1273,exact
28438,mauriciosoto,DJ7YP7W7AA8M,account_uyp7QDJmZyCeq9BTGnvoZ3,Mauricio Soto,891,exact
28435,jimcrank,PLA7XNX2F4Y3,account_p9AaVzWfCwR74RVK3k6kPa,Jim Crank,1257,exact
28425,summerbeazley,D4S4YRB8Y3KA,account_9AkWnUdPzs2AE9vfqmbz6i,Summer Beazley,1244,exact
28426,davidarmstrong,MPEZGG53KQSZ,account_p1wD82RWMCTi1hRoLHdjHL,David Armstrong,1245,exact
28395,stevenspurgers,YS5Q785J2HGZ,account_nxruUEiwq3NpZTexeEo8jd,Steven Spurgers,1211,exact
28388,connercoleman,6108RANDOMREDDOG,account_6VLrBr4sZCAJwMzzcEkhSE,Conner Coleman,1202,exact
28367,judithfierro,FUZ33LB6SFFC,account_aFYkyi9DGrJBRgQ4jSud96,Judith Fierro,1177,exact
28359,tonyasipes,N5VEZR8Q5M86,account_rKLQuRuTgybF3YMnNt7VY9,Tonya Sipes,1169,exact
28347,kathysmith,SJ53PSYCZLPR,account_3zbkHwfVAegXCPUhc4XmYV,Kathy Smith,1156,exact
28344,cherieeshelman,BRUV99PN6Y7L,account_nwbZPHiXLbR9o6LZppPSoY,Cherie Eshelman,1153,exact
28289,ronberger,Ronb499H7ALW,account_q8QjfTJ9rtgoow3eHLpVYQ,Ron Berger,301,exact
28267,sandracrouch,V4N3764R5J,account_tCddLw595sDcQEYCrRCmFq,Sandra Crouch,311,exact
28263,abbieandrews,YQSJ499H7ALW,account_vHWu6NZDh4Ajh6YqWEXPb4,Abbie Andrews,1081,exact
28253,perlachavez,ZWR9PXVFFYBS,account_tTLx1Zn6YTykzm8quV6ykg,Perla Chavez,1068,exact
28246,susanking,N2KUYLPDNXJP,account_xqwwg1ZAd6SKt4LyrcAebY,Susan King,1058,exact
28241,terrybachran,NG9VMH25LCTC,account_s6ZC2GALaCFUamcQafLyy7,Terry Bachran,1053,exact
28240,lanaygaunce,MJCNZAJHK9H3,account_pUesJC8A4ENdQ5XZTFGdZt,Lanay Gaunce,1052,exact
28190,heatherbrister,6XPLC545G6HW,account_wkn6qdAVF72b7TxwZxvW6D,Heather Brister,997,exact
28184,jenniferdunaway,343V5CT2R7HZ,account_bTsNpp5j2YHU9dKipBm3iP,Jennifer Dunaway,990,exact
28159,elviraquezada,HXAS29VRX33V,account_q5J8BYNnSG5TtFnikCTEmm,Elvira Quezada,960,exact
28154,jordainpatton,J8P4TPF7QS2A,account_p5VqKAXXC8t3tAFSzzr8cC,Jordain Patton,955,exact
28140,mattkosarek,RJ4BQUZJY,account_7eCHrmvA6X16gn1zr5wCo9,Matt Kosarek,932,exact
28117,shannonclark,F5XESMCJ,account_8YqGj24SS5fxoZhPUYBED9,Shannon Clark,908,exact
28098,kevincostanzo,244VGMVMH66D,account_9DuddEuiaH59KtVGReTwJs,Kevin Costanzo,881,exact
28093,kenpatrick,V7U3XBAPTSV3,account_omX4bSM3rXDShahsdc4aHd,Ken Patrick,875,exact
28083,briangallimore,39V285NX,account_k8GTscTwPuAvgtyCbT6vyG,Brian Gallimore,55,exact
28072,amberprater,RP42B7AYDLAP,account_fXqciwzW348S1eYSKbbi8B,Amber Prater,855,exact
28068,marilynfowler,MarilynVG72P8H,account_v9vCsfc4uMAk7LgXyRR9EC,Marilyn Fowler,851,exact
28065,douglaswilson,HP97HG9XPYC3,account_bmXh2S5K97hPyUkkBRj1ub,Douglas Wilson,848,exact
28034,terrygortney,GQUGR3VJUXBR,account_jPuzvdHGq1BXiDALjrVaR7,Terry Gortney,815,exact
28031,gizellebutler,HMZTX66RTPVX,account_fipAsvnLVUkrSmuJ9GYdEb,Gizelle Butler,811,exact
28029,bradgurney,7A6LRZWT82DX,account_aZMRveyhHvJTHTQCT7yYdf,Brad Gurney,809,exact
28024,darrylchavis,TDGUUNXGMYRC,account_oP5ATcm8WwBDBv8t8mh4vU,Darryl Chavis,803,exact
28025,kathymazzola,5MVS8VUF9QL2,account_g5RpJWMv32tFk6c4yy5F5c,Kathy Mazzola,804,exact
27994,austinwatkins,S7M4UD8QUTDP,account_htyg2uThjYmLbHsX5a3RCj,Austin Watkins,769,exact
27988,oscarrodriguez,TGPCF3Y2ABXS,account_fNLTDkPjpbzZ7Eta2ofer4,Oscar Rodriguez,762,exact
27986,erinthompson,69TQZ797,account_eGe4Qkw13xjjwabHBu51F3,Erin Thompson,394,exact
27983,brianhardesty,8D4P34N8,account_7zjMVqCfCV4jZT68myj6p1,Brian Hardesty,20,exact
27976,cathyday,FAXMKENV8FCT,account_aZmcKqobMWGKhxagDdLCFA,Cathy Day,755,exact
27956,fredheckel,HUVZSE8RGD65,account_mx1SSXHEiptCZaz2Gx6MTm,FRED HECKEL,735,exact
27949,charlesboone,Ccbn78wdfksB,account_iNnHAwxaWRUXte6w7ijGV3,Charles Boone,62,exact
27948,rachelguzzardi,R87wgerf8bsG,account_tEZyHoiA8DvmisscRkYy7U,Rachel Guzzardi,287,exact
27936,juanalonzo,745GF0JK5!W,account_itAy96jDYGcvWjcHsy6XcR,Juan Alonzo,198,exact
27930,kristinamurphy,oPXFrylqKf,account_qx5oYHwkMNxoKEXME6zu6w,Kristina Murphy,725,exact
27926,johnnygoble,J829fhf7G,account_1m7oDB9vYUYJQffFHvxQaz,Johnny Goble,416,exact
27923,keithbarber,K73bvd54tB,account_bKdyNcZqZ1h8qaQAcJzTRK,Keith Barber,208,exact
27917,doreengrubb,DOR32SVT1234,account_5n5qjvMULWVYcGGmF7uiER,Doreen Grubb,122,exact
27916,steveraia,DRR648DFTMSE4,account_g7EjAfDhcTH9oxWZGJ4CTH,Steve Raia,291,exact
27896,twcowley,TW486TW,account_h2bYtWBz1nbRRVBiYvi8wQ,T W Cowley,710,exact
27888,joepatton,L7luYzSKSu,account_hWsePAjG5pq8FnnDQm75GD,Joe Patton,701,exact
27873,timfisher,Thd73gdf5eF,account_hMQqEcyWpzLTEtUnCJmKsi,Tim Fisher,345,exact
27847,kileigheven,K9872gdf8E,account_4aPqi935STSGBSMxsLNwSm,Kileigh Even,215,exact
27833,deannecook,Dc872cobnC,account_hqsfU7P5wK2uDjpfVo8zxb,Deanne Cook,110,exact
27830,kacyharrison,7Ozr7el6K1,account_t9Bb9uXEB9NjGoTDvCThi4,Kacy Harrison,643,exact
27822,milliganwater,8NjGMNsTXd,account_7FafmmaEuKaNeaHmFFLSaf,Milligan Water,636,exact
27813,michaelnewby,TM5M3HU877,account_1moqtbSRAJxiVR2pTtH3f9,Michael Newby,623,exact
27808,ladonnaclark,YQReVH9men,account_vPgQBpVJ4y2gkpRgWTksvi,Ladonna Clark,612,exact
27800,jamesmooney,QNUqD2tSzq,account_5C4WjLaQS5tGDxZm4NCZds,James Mooney,603,exact
27783,tamythomas,TT4699520858tt,account_4gF4CqD6W7iS3UesE6js3g,Tamy Thomas,587,exact
27780,josephcruz,Z06oxJXrNd,account_nYfEqA9HUY6R2xi1PvCQVs,Joseph Cruz,583,exact
27761,franceskirby,FK8179391152fk,account_v2eMTZjQzV5Q3B2vLxGCpa,Frances Kirby,568,exact
27759,chadwhitsell,79374B75phGy,account_swiMEEPxqsGukTQMUEP7DR,Chad Whitsell,61,exact
27754,ruthfengler,8q8MX6NQP3b6,account_2ycJgX1XpXSqDiDgNisZbA,Ruth Fengler,304,exact
27751,bobbyfisher,salvagejuggle,account_iuZZQ7KJWWSihZh9mzi2B8,Bobby Fisher,391,exact
27748,eddieyarbrough,89298ChZWzMy,account_6KuhoNq2CptmBHyHVS33cS,Eddie Yarbrough,125,exact
27744,chrissyeagle,9vkX97426Sgt,account_3uu7k4euycmKnZii9DQAj2,Chrissy Eagle,74,exact
27739,victoriaobier,M57B288P9,account_cPSKw37hjbGmmpHzzHUum1,Victoria Obier,371,exact
27736,bernardheer,BH33682019BER,account_fcUL4WRuUX7LrvPhjfQZQt,Bernard Heer,46,exact
27734,garycable,M2w6z5Y73,account_6JkDjyFzfp7pPcvn6tsd9f,Gary W Cable,144,exact
27735,coyungemach,MhS569N84e7a,account_f5ygK53FvcVakZs4YwNVo5,Coy Ungemach,84,exact
27733,michaelmccauley,CPORADIO98,account_ecewjvtr229nzEjFgSEhf5,Michael McCauley,259,exact
27731,lessruble,9U35SXxM2H8y,account_6M1cjjzHDyrtJWimoD6Tgp,Less Ruble,226,exact
27730,marknunnelee,833j2Ymm4yfV,account_mBYcBCnbFTgVxsDNCUrq9z,Mark Nunnelee,243,exact
27729,elizabethchristian,Webg22nj,account_3Xpb7LLq4bEFZxohgYvjUS,Elizabeth Christian,128,exact
27724,mikebell,BHF538HSEBDLY4,account_4vuDbTF964RUD2YKA5hmhQ,Mike Bell,556,exact
27713,krystabates,KB2145445824kb,account_crxqAidS35hAeKLFDHKmiy,Krysta Bates,218,exact
27708,chrisfrancis,DR45QC74M,account_pcYVJfTDAUK9ALviS7XBjW,Chris Francis,388,exact
27701,markfisher,32fd8P&rw8s7,account_hxsSqNHZMtFbXDA8DtvRso,Mark Fisher,242,exact
27696,kirkvanmeter,J2sR9g^83M33,account_bH9zyBQuBcgkiU9EZPYxa5,Kirk Vanmeter,216,exact
27694,dananance,23SP$e!ug232,account_s9ZW1FpS3wkDoh31CjU1Zw,Dana Nance,89,exact
27690,keithcrank,2zt3T5w72T3A,account_iSuzVkNmem52NdXXh1Savu,Keith Crank,209,exact
27687,scottarmstrong,2SV58M55,account_dCvreFY2K3nJW8ZkCPaeyR,Scott Armstrong,315,exact
27683,floydday,Tfdd3254tfD,account_rFM2cz415W3Rqn1McU5gpW,Floyd Day,141,exact
27680,mariacortes,MC2145858276mc,account_t4Qnzxsq3c2WryFDXTSWba,Maria Cortes,435,exact
27668,christianlummus,K6dbr631ddL,account_fWd5egSy6XovRRzTgi5xZk,Christian Lummus,205,exact
27666,marklindsey,ML9727346434ml,account_ayztehQeAayBTQkizozi2B,Mark Lindsey,531,exact
27658,joeywhitfield,BV758qXne446,account_8bxr54hmvSEFRyDLfvCgie,Joey Whitfield,187,exact
27654,taekim,SAN11837TCK,account_wbsKtfE9W1SyPkqk9hFBRp,Tae Kim,408,exact
27652,teresarobinson,T645trdf87fR,account_2SA7BzkR6cqsS2jSQ14nRh,Teresa Robinson,376,exact
27651,nathanmctee,N32462uu8rM,account_2CZKhrkB2TxZDQTWxuJxVz,Nathan McTee,273,exact
27646,rebekahmoore,R756346tfg4M,account_k8gD8YGUQzwWCE7JT9EfYc,Rebekah Moore,386,exact
27643,monicatrevino,MT2145877143mt,account_d3CqUjkDwdiB1pwR6t66XL,Monica Trevino,524,exact
27640,rachelfuller,R8364hbst,account_t7qMv9PTBxtj1rmr4WmEqV,Rachel Fuller,286,exact
27638,shannonbrown,S9725298223b,account_nZ8roCqzpsY5Wv86p8i6Sq,Shannon Brown,318,exact
27637,allentaylor,A6346gge8T1,account_44F55EHwRrLm33EvyuCPgG,Allen Taylor,26,exact
27632,kristenaustin,KA8017916972ka,account_qunWwkhDGN8jxGDMfB9FzA,Kristen Austin,522,exact
27630,colinhopper,TYVU65445IUB,account_jaGX2dnVZVs463ok5kANR5,Colin Hopper,245,exact
27619,srireddy,ycHAvetmF0,account_jS3WVFZmrQu5EoM7UJ4L1N,Sri Reddy,514,exact
27615,almaacosta,ENKC5DGV,account_rv1k2oTH5Dn7sDTJiTxADa,Alma Acosta,28,exact
27614,chrispassonno,C8766thf96P,account_8UMvyYLatGW2qwSqH62KbM,Chris Passonno,73,exact
27613,seanbaxter,SB4696679337sb,account_vFwXqaiaquwHXzgMkdg1mc,Sean Baxter,512,exact
27612,collinhartman,B886tg98jt9L,account_kvBUopRRFzz8B5PgbdjLGG,Collin Hartman,402,exact
27610,michaelhughes,1RKk32MyTK,account_6QT3zjSA88VLdbBJtAt44j,Michael Hughes,510,exact
27608,michaeltalbot,Mjj1276rt99T,account_4bK2RJU3FRwyH4SPmnik1q,Michael Talbot,262,exact
27606,anagonzalez,WNAHDMwWoE,account_3jfw5hMXURMmVuGyafdSPj,Ana Gonzalez,507,exact
27604,leonardlewis,L1877ef32L,account_3TVj7ZpGiTYYt28q8dh9VR,Leonard Lewis,224,exact
27598,jerryjohnson,J177895aa98J,account_fa8emiMvGC9ZkdA6uuwMpF,JERRY L JOHNSON,177,exact
27594,pamtoler,NfxcgYQUbj,account_mnxwgFcvbjvDjnBBdGQzek,Pam Toler,501,exact
27589,jodymccluskey,JM4696883349jm,account_35QGS4ZHr9tSpCVKNHE1ZT,Jody McCluskey,497,exact
27577,chrisvannatta,!4694502484!,account_dyfr7aKWGD5zJ1pdeEyyna,Chris Vannatta,426,exact
27575,mariomerlo,!2149579585!,account_3yQbySMiwrU5kFJRA8itm6,Mario Merlo,239,exact
27572,williamarmstrong,!2145510334!,account_5izwKtCxbvkCupVQJ1KBBE,William Armstrong,362,exact
27571,trecedaschmid,!4692075942!,account_iR4oM82C6m8zkrFDLkSbYZ,Treceda Schmid,355,exact
27570,ronlewis,JDkf1AL942,account_5Y7chdsykaLwBg3s2rqvMD,Ron Lewis,302,exact
27567,sherryerichardson,DR6528BCD3S2,account_oadyDfiynqanaMffoSVM2W,Sherrye Richardson,321,exact
27566,joannarodriguez,394X3YR9,account_gCVrkxTWdSWaYUNtyk1rqr,Joanna Rodriguez,185,exact
27561,dannycampbell,!9726589541!,account_4ww28eUsFRv8Yh5RkWe9cz,Danny Campbell,92,exact
27560,howardcomstock,!3365494230!,account_kZMVM4zfKdgEAyCCvVREU8,Howard Comstock,156,exact
27555,terrymiesen,!4697420828!,account_bXRRk1Yz4nKkCPsdzDJyYa,Terry Miesen,339,exact
27550,michaelray,primitivedefinitionstoredinner,account_8Rh7edwTjNBYw9FJSq4WRT,Michael Ray,379,exact
27545,terrybates,!2145786644!,account_d6m5HYMAczoeH2ovA6vqxj,Terry Bates,490,exact
27538,gregmcintire,v9368$E9mx9m,account_fyH3ZiVVLx9h56vNYVndbK,Greg McIntire,380,exact
27543,beverlyerwin,MOOLAH,account_9kYh3vsMa7mG8JrGaQtF2V,Beverly Erwin,48,exact
27518,helenlumpkin,Heehhejd0N,account_ts53K9Gc61HkXi7uqcaiN6,Helen Lumpkin,477,exact
27508,stevechristiaens,zY272m258*vJ,account_3HSd4Thc3iXfcidraN6ZSV,Steve Christiaens,329,exact
27505,judydevine,OD4697669725od,account_e8ZUaWmpXqA5NTVfBHaYfq,Judy Devine,277,exact
27500,rhondabolton,RB2145786030rb,account_x8xohdUBJBWyd87jTCtP3r,Rhonda Bolton,295,exact
27492,rickbeckham,tVj7ZI5QA0,account_f861cgYw7zxSiyUh3ngUYk,Rick Beckham,464,exact
27490,vancepeltonen,V66rdf43aaP,account_9arNHyqn6bwuUyPm4uWfrx,Vance Peltonen,356,exact
27486,jacobwilliams,jnlka9994jkfldsa,account_bFr4VtSNkJpdZv3K67mP5y,Jacob Williams,159,exact
27478,classicshear,KARENALDERSON,account_tui6mE1CTprghAF31hKWLp,Classic Shear,400,exact
27477,davekiowski,FNW63KXSAPT7,account_xr9LPLBvR6SHF2xmNdrNfN,Dave Kiowski,100,exact
27458,paulsammataro,P2n74V7P8g4S,account_cwYTP5p9ubFdXioqnmuDsy,Paul Sammataro,283,exact
27460,janetkern,j42252^Bhf44,account_skzmJw1vJfndNPQNWsJjRL,Janet Kern,449,exact
27457,carlossalaises,28E358y$JFB9,account_5mD5W5PczTzo9H7it3qxLX,Carlos Salaises,448,exact
27453,obedsandoval,*H78z9xy84C&,account_k6qzzjxrJtH4UT7BerwBzk,Obed Sandoval,275,exact
27455,dianewiley,Diane03t4&adTL,account_n9yXaEBkPmAm5SdrbTwsTw,Diane Wiley,442,exact
27444,tammylove,f65NS4Ct6$29,account_5WZN3aLBoWBUHeEjHYySgw,Tammy Love,335,exact
27438,floydallen,V3864VB2,account_9oLnGJZwNxJTrJRrQgCX59,Floyd Allen,140,exact
27432,hortenciaalvarez,WHTABORG3593JKS,account_oNBXVRFamuurD1rz4bugiE,Hortencia Alvarez,155,exact
27424,timbagert,CF18n42cY7,account_2U67Q5pWZCgbWGX1tLXbXD,Tim Bagert,343,exact
27425,davidbaker,DB764g1d87rtB,account_okpeaMgrsz9nNs1cSMjQv3,David Baker,102,exact
27421,janicealexander,AG57TSH89L9,account_pdQ864pRJ5AbJP4YHtv1S5,Janice Alexander,165,exact
27423,timgilbert,73xi0r600Sz%,account_ezDVFeaw9XwLiVvM9iEDwJ,Tim Gilbert,346,exact
27413,jesusrios,8Di6QMs7%7m1,account_5RA5zrZJXyuBEj4CmuodCh,Jesus Rios,181,exact
27409,veronabaptist,DAY753LIGHT253,account_j9cuBn7ojxwRH3BfRWmDhN,Verona Baptist,382,exact
27406,whiteywhite,45z6r*H4418n,account_32CUFBTH16juiHGzrKPd7m,Whitey White,303,exact
27402,louislafon,RFTRE4825CVKL,account_9ayF1oF9iawPMpCdQSiSqX,Louis Lafon,445,exact
27397,ausencioluna,ABN765MNL,account_7Gj6TBTXHmpu49iZGZBYT4,Ausencio Luna,427,exact
27374,davidvillanueva,DV5489CRPMWBQ,account_d5hcuzxZBgMErVfhrXzQAJ,David Villanueva,109,exact
29621,coryrees,yt5c6ujhvu,account_uRC7LnETNNZdRmSEQeQbwX,cory rees,10049,exact
29622,brianamartinez,ktydr6428,account_gazJNXCsEwG1UJBmYyQ6xB,Briana Martinez,10051,exact
29623,khushbooagarwal,UUTFVUCTC446,account_5xtd1ApTppgpHnRpvmQHmV,Khushboo Agarwal,10052,exact
29624,elisasanders,OYTCu3276,account_hZD7ECmwCWd5vG73hEYDUN,Elisa Sanders,10054,exact
29625,josemedrano,UYVITRUR24,account_3froYZwHV4X7v9wW6TVu8N,Jose Medrano,10055,exact
29627,nicholasterry,VUTYVL746,account_wSzw7Xd3wb871Ug9aaqUcH,Nicholas Terry,10059,exact
29628,leslieperalta,RYC66747,account_dQfUEUmdqUu6rGALsoowVQ,Leslie Peralta,10061,exact
29629,khushbooagarwal2,iytf65gkj,account_j2iH6cW5tFhsSKxjLUvQBb,Khushboo Agarwal 2,10062,exact
29631,royrogers,TYDGVBNC,account_nCAKAgLxtPx5H68AJZMNUA,Roy Rogers,10065,exact
29632,arturoprieto,1653APVN877,account_pR6sTHc4xvLqPfAZhJxrxd,Arturo Prieto,10069,exact
29633,ericamorris,E16488VN387,account_gG5uDHsz8FPiuknRCkpbHQ,Erica Morris,10071,exact
29617,belindamillener,Uiyu7280,account_mXx2EoT7dcv2am6asSEJFr,Belinda Millener,10032,exact
29618,kailynnbarnfield,HAkuguuvoy1,account_hVh2CveE2tBqctZhpLX5Nf,Kailynn Barnfield,10037,exact
29619,thomasgraham,YUiygiiu4,account_fETWiC2bAqbb7hkQ5UvKjj,Thomas Graham,10040,exact
27315,debravega,RCG8618BDUMSR,account_fbgziTjDnsrjH4aA2hMykS,Debra Vega,112,exact
29582,davidlanman,2BMMRYQ2PHEA,account_u9P7hnf3FR5yqm7FFyCnrP,David Lanman,2486,exact
29583,anthonylongo,9MLG8HVME4J3,account_eq2Po3eJPoEyv6RvhhyBMA,Anthony Longo,2487,exact
29584,konradwoelffer,BCQ8NRFVNA3R,account_7B7CchNkRbNa6LLxRBform,Konrad Woelffer,2488,exact
29585,marthaflores,KG2QEG36348V,account_cdbWPX28Kv3YSaYUDf7zj1,Martha Flores,2489,exact
29586,connorspicer,6U2BWJKGCX8G,account_pV5Vstq2xr42fru5zVB28F,Connor Spicer,2490,exact
29588,korybiggsshop,JA4SXBGKHFUL,account_irdUq6W33yA7J9vpQ5t85q,Kory Biggs Shop,2492,exact
29589,ednagriffin,AXA8EZEAN7DJ,account_nnSEFSX86bVogx8KsqANUP,Edna Griffin,2493,exact
29590,richardrosson,9J5L6MEH4Y7Y,account_jL9tR5FgswV7FZwxH3U5Ex,Richard Rosson,2526,exact
29603,allentaylor2,NCJW87FW8723,account_sEcueqC1ysh7Ne2pE9qbg2,Allen Taylor #2,10009,exact
29604,alextovias,7VSgrVO1WX,account_1BvWee8L5krmWQsfVUqCdk,Alex Tovias,476,exact
29605,mikecurrence,MKH23FG79C,account_gbzPYU8zHeCSV7E5c5eksc,Mike Currence,10011,exact
29594,terryrector,QUPP83ZUUSGL,account_bHx8uSNfzCrYkD8JriK1jV,Terry Rector,2529,exact
29595,donnacampbell,KYCNTWS3929N,account_4y4ZEDFYqQhjW99pPJyLnf,Donna Campbell,2530,exact
29600,billmctee,Bdge6388M,account_qdzi3bYpEjEx7crLFY4xCw,Bill McTee,373,exact
29531,daycor,U45YBPSARH7N,account_aZmcKqobMWGKhxagDdLCFA,Cathy Day,755,fuzzy(manual_override)
29521,tiffanyyarley-olenak,98HTZ9Q8FARG,account_cwmjmXkrYeba8h8RGqLm6E,Tiffany Yarley-Olenak,2393,fuzzy(sequence: 0.95)
29395,vanessagomez1,J5JEW3HEBVAF,account_gb82EhqTwbNWWEovPu4oKZ,Vanessa Gomez,2264,fuzzy(last+first: gomez+vanessa(fn=1.00))
29611,brittannieredden1,TYFTUEZ7429,account_bB6RDmNpkzndVbSh9CvgaL,Brittannie Redden,10017,fuzzy(last+first: redden+brittannie(fn=1.00))
29614,samanthatheis,kf6u8djigk,account_x46hMNq15qHmEvP6fNFdfm,Samatha Theis,10023,fuzzy(last+first: theis+samatha(fn=0.70))
29216,wanxiaxong,BER4NEK9M7NA,account_tiy5YHD34KpwPzUTXyRVFJ,Wanxia Song,2077,fuzzy(sequence: 0.86)
29215,bryangoulart,DTNQFE4CXSFX,account_9sksoKmcJGxqhz4M4aD169,Brian Goulart,2076,fuzzy(last+first: goulart+brian(fn=0.70))
29602,tech,brussels-crucial-smashing,account_jUFnZ52urgAMNgp9XhT4bs,Graham McIntire,1,fuzzy(manual_override)
28935,chrislewis1,V9DS4E4Y7UTT,account_osB3NhyGdoWJRtpL5X2bfD,chris lewis,1787,fuzzy(last+first: lewis+chris(fn=1.00))
28917,crankkeith,kcrank10615,account_iSuzVkNmem52NdXXh1Savu,Keith Crank,209,fuzzy(last+first: crank+keith(fn=1.00))
28703,grahammcintire2,U5Y3hA3g*14L,account_jUFnZ52urgAMNgp9XhT4bs,Graham McIntire,1,fuzzy(last+first: mcintire+graham(fn=1.00))
28702,chrissyeagle2,NJPCGPBNFB,account_3uu7k4euycmKnZii9DQAj2,Chrissy Eagle,74,fuzzy(last+first: eagle+chrissy(fn=1.00))
28671,melodymccarty,U3WLEGEB,account_18HG9hdwwtZM83J9kyGMs6,Melody McCarthy,1397,fuzzy(sequence: 0.93)
28642,barbihardin,ZENETJ75TEVQ,account_bb7iRuPixxutKZxnURfK5U,James Hardin,1475,fuzzy(manual_override)
28628,jackworthy,2VUR7AAFWZZQ,account_hgoipVYXydD1yVSXTD9QGP,John Worthy,1461,fuzzy(last+first: worthy+john(fn=0.70))
28542,michaelray2,jjPgDcotL6rtY,account_8Rh7edwTjNBYw9FJSq4WRT,Michael Ray,379,fuzzy(last+first: ray+michael(fn=1.00))
28458,kennethcampbell2,V5TKW8L6RXFF,account_jF69QnP5j9FtfLgdAiUKfE,Kenneth D. Campbell,213,fuzzy(last+first: campbell+kenneth d(fn=1.00))
28296,cinthiapereira1,9WSGXM9LBXGZ,account_57mDq22QWRV8tdPvk74Uvj,Cinthia Pereira,1102,fuzzy(last+first: pereira+cinthia(fn=1.00))
28107,jacquelinewilder,HEY36LUGYCUQ,account_hHa9Z64B9okZ3yccQP9WpR,Jacquelin Wilder,892,fuzzy(last+first: wilder+jacquelin(fn=1.00))
27939,reynamoore,R92nbskba7iM,account_k8gD8YGUQzwWCE7JT9EfYc,Rebekah Moore,386,fuzzy(last+first: moore+rebekah(fn=0.70))
27912,blancadiaz,B548gdu55D,account_ktdNSpKRV3ja7RMi7pZRBB,Balnca Diaz,433,fuzzy(last+first: diaz+balnca(fn=0.70))
27838,consueloluna1830,C8736gfd0,account_jw16PcKtprHpPkSAEhvsaN,Consuelo Luna,81,fuzzy(last+first: luna+consuelo(fn=1.00))
27781,pambanschbach,Q2p68#*9vwF2,account_6hGXtJ7RV7mqZYfd5AyE1f,Pam Bansbach,383,fuzzy(sequence: 0.88)
27639,choonpang,CP2143855677cp,account_o7aKk2FGKJAcmrXxMfUCpX,Geok Choon Pang,146,fuzzy(last+first: pang+geok choon(fn=1.00))
27618,tammieventris,DR45QC74M,account_fMQ6qiwkPFWP8a86exFw4A,Terry Ventris,340,fuzzy(last+first: ventris+terry(fn=0.70))
27579,donnance,9727346814,account_s9ZW1FpS3wkDoh31CjU1Zw,Dana Nance,89,fuzzy(last+first: nance+dana(fn=0.70))
27501,jonnytaylor,Zb6677&4!yky,account_1fDruGS6CFTBUCGuzVZYTG,Johnny Taylor,190,fuzzy(last+first: taylor+johnny(fn=0.70))
27436,clayrobertson2,C2147288546R,account_jgtX7TysTqcobVVBtSZEc2,Clay Robertson,77,fuzzy(last+first: robertson+clay(fn=1.00))
27417,donniecampbell,6a2671bc0c1,account_4ww28eUsFRv8Yh5RkWe9cz,Danny Campbell,92,fuzzy(last+first: campbell+danny(fn=0.70))
27418,alanwyatt,i1412r53MYr,account_2cnVCpSsYocCYpMEwwBBrY,Allen Wyatt,27,fuzzy(last+first: wyatt+allen(fn=0.70))
29620,sayedahaque,YR4Sytbu,account_4hoTCjwTatthCg7zp2Cnfe,Syeda Haque,10048,fuzzy(last+first: haque+syeda(fn=0.70))
29554,marcuspatton,GEUXX27VLZXK,account_dxY6AZnLdFUWup8Sf6XER1,Marcus Patton,2458,ambiguous_resolved(lowest_id)
29606,mariacastanon,iu297gkid9,account_qh8bFPvWzyAqHqwHMDkThW,Maria Castanon,10007,ambiguous_resolved(lowest_id)
28800,kennethcampbell,!9727342446!,account_jF69QnP5j9FtfLgdAiUKfE,Kenneth D. Campbell,213,ambiguous_resolved(lowest_id)
28358,stephenday,MSVGQ8X38YLN,account_j1UFX8wqzorqSetUje5dTJ,Stephen Day,1168,ambiguous_resolved(lowest_id)
27404,grahammcintire,U5Y3hA3g*14L,account_jUFnZ52urgAMNgp9XhT4bs,Graham McIntire,1,ambiguous_resolved(lowest_id)
29630,sriahobilamuth,RTCESW24,account_7PgSr8dVuafybJdrXND3V9,Sri Ahobilamuth,113,ambiguous_resolved(lowest_id)
29524,chandparvathaneni,TK4QA3XM5XK5,,,,"still_unmatched(best: , score: 0.0)"
29358,radiantlifeministries,12FDIUNF5L7vM,,,,"still_unmatched(best: , score: 0.0)"
29296,midvalleyconcrete,BD58JYT2PJ6A,,,,"still_unmatched(best: , score: 0.0)"
29244,janiscable,JHC7U554JBTR,,,,"still_unmatched(best: last+weakfirst: cable+gary w(fn=0.11), score: 0.611)"
29150,donwilhelmus,2V4NEL87RM39,,,,"still_unmatched(best: , score: 0.0)"
29135,kavalleriefarm,8DFNNTPBPZGY,,,,"still_unmatched(best: , score: 0.0)"
29077,kellygarza,4696591662,,,,"still_unmatched(best: last+weakfirst: garza+esparanza(fn=0.42), score: 0.642)"
29070,1stchoiceliving,YVWJWRPEVYNH,,,,"still_unmatched(best: , score: 0.0)"
28879,kimberlyrichards,7PQA6J8RPZ9Z,,,,"still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)"
28599,ryanrodriguez,9DFYWJ47YWUP,,,,"still_unmatched(best: last+weakfirst: rodriguez+mike(fn=0.19), score: 0.619)"
28460,saidaacosta,V29897X4,,,,"still_unmatched(best: last+weakfirst: acosta+alma(fn=0.21), score: 0.621)"
28422,jackiehendricks,UQR5HGFCJ4XX,,,,"still_unmatched(best: last+weakfirst: hendricks+sue(fn=0.09), score: 0.609)"
28400,bonniestone,BH8BV44TTWUU,,,,"still_unmatched(best: , score: 0.0)"
28336,buddyswan,N7XUYUD7J9SD,,,,"still_unmatched(best: , score: 0.0)"
28268,alberto,TICOGUILLO,,,,"still_unmatched(best: , score: 0.0)"
28213,krisdougherty,RWEH75UJ38,,,,"still_unmatched(best: , score: 0.0)"
27955,evlynrichter,FRQD738BGRC,,,,"still_unmatched(best: last+weakfirst: richter+brent(fn=0.19), score: 0.619)"
27793,branchrvpark,XfKxVZIKmm,,,,"still_unmatched(best: , score: 0.0)"
29601,kimberlyrichards2,K84f7r10GT108,,,,"still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)"
27755,gordonhamilton,c3495Rpuq5Sh,,,,"still_unmatched(best: last+weakfirst: hamilton+sheryl f(fn=0.16), score: 0.616)"
27709,sensibleheatsystems,SHQWERY23,,,,"still_unmatched(best: , score: 0.0)"
27704,lambandlion,LL9727363567ll,,,,"still_unmatched(best: , score: 0.0)"
27625,mattbud,kjklfda79sd,,,,"still_unmatched(best: , score: 0.0)"
27621,richardhill,RH9729788124rh,,,,no_match
27489,jenniferreed,683P7V7R,,,,"still_unmatched(best: , score: 0.0)"
27482,irvinbarojas,yDeEJzYY08,,,,"still_unmatched(best: , score: 0.0)"
27463,dennispereyra,82s62kd9XHhd,,,,"still_unmatched(best: , score: 0.0)"
27441,samanthasmith,22jmR9X#93j5,,,,"still_unmatched(best: last+weakfirst: smith+kathy(fn=0.27), score: 0.627)"
27408,penneywarner,b4h53N6c#5wo,,,,"still_unmatched(best: , score: 0.0)"
29626,karengreen,UTKCYI674,,,,"still_unmatched(best: last+weakfirst: green+dominique(fn=0.17), score: 0.617)"
1 radius_id username password gaiia_id gaiia_name gaiia_readable_id match_type
2 29581 joseortiz J2WEYT7782UJ account_4jKF2XxS1tjfAiTJLsFcGJ Jose Ortiz 2485 exact
3 29578 perlachavez2 U4BBTBMCJZST account_sSL9o2BAvbEHXAkxWtPXxV Perla Chavez 2 2482 exact
4 29575 thomashernandez X6BZLA7SC3XM account_wL9tsaty83QCJpNGNXeSzZ Thomas Hernandez 2479 exact
5 29572 williamarmstead SAQ4XEKXDC58 account_5Eqdv89tv7SUwTjGqQWJdh William Armstead 2476 exact
6 29570 julierose MLM8R2WAGY52 account_9hvebUFe6UDfaZb9omVkB8 Julie Rose 2474 exact
7 29571 derrickmccausland ETNLKZHACGF7 account_89xduFTiVvRoEQpENiaVmT Derrick Mccausland 2475 exact
8 29567 sherrifredrick 7P52BVMTSNN6 account_3eF55epzgGSPhDU1DFBm53 Sherri Fredrick 2471 exact
9 29565 shelbyburris 3YAL9DWFZUPT account_9GNShE9ayAX3fkjwSryGq7 Shelby Burris 2469 exact
10 29564 priscillacrenshaw AXBDLXSGXMLR account_2MK9ykEGzWyxvg1SZMmagK Priscilla Crenshaw 2468 exact
11 29561 korybiggs 4FJC7DXAW5RV account_ftDbZQGSaf8dowpdjC44sU Kory Biggs 2465 exact
12 29563 kristinjimenez 9FAYXMRDU69T account_bdpYxupJ3cfQ5bmhNroy8i Kristin Jimenez 2467 exact
13 29558 jessicaweddle VV96G36AEEDA account_khW6HArsQ677gL18hcQnEN Jessica Weddle 2462 exact
14 29557 yolandamedrano R948UL4KZRA4 account_4tZA9NMygTcq3fidSxDz5h Yolanda Medrano 2461 exact
15 29555 williambowland K7HYNWGE25XX account_6fhZdgAsyd9zJ2FiXURCJ2 William Bowland 2459 exact
16 29553 shacaciabillings 8AZLJCDFYFYV account_pDDXT9Lh8QsE2roPyeVfS8 Shacacia Billings 2457 exact
17 29552 guadalupelugo DQG8XCMFMVH5 account_haPf3cXEdK4TQMzqZi5LYV Guadalupe Lugo 2456 exact
18 29551 brandonhardesty KMRHVPEXBEZ8 account_izj2Jd1nYA49rAcvNu6TfZ Brandon Hardesty 2455 exact
19 29550 nancyavellaneda CXATYV7NGZN2 account_99jecysxoYGBHUhd97ZYmt Nancy Avellaneda 2454 exact
20 29549 matthewgoodwin VSPM7N9UNU6Y account_ocezHaQ4JPvu1EbRMk4hu5 Matthew Goodwin 2453 exact
21 29545 donmckinney C6AQ6UVD6R7R account_viJJswZvVXEbxLBikRjGJz Don Mckinney 2449 exact
22 29544 natashaelmore 8G5QJ8R7MK8Q account_bGYACsz7a5kdyDBDecBTZY Natasha Elmore 2448 exact
23 29543 shamsbashir WLBGY4JMN4VA account_vtGoVXJRkxWw3ZEYDKi9Bu Shams Bashir 2415 exact
24 29541 douglasglover 9Y6H9X3HCG3D account_hzZvaSXgCuhMUTXjpaCQjA Douglas Glover 2413 exact
25 29538 carolstrickland 547P6AQ8RCD4 account_u8W2U31Z3LJgzNcD2qNfcX Carol Strickland 2410 exact
26 29537 carmenlopez TPLND3H2FWTQ account_75ZP8Rhk7DBvwEA83HaWBB Carmen Lopez 2409 exact
27 29534 johnlooper ZPL8LRY2TGRH account_iijdXYXJAhg1mDm1FRuUrc John Looper 2406 exact
28 29535 davidroesch ZHB2BS968DTU account_bHVKB5E3eJFmbqnDwXAxFw David Roesch 2407 exact
29 29530 derekrodriguez AP7Z594DMR7E account_8MmcYW5A3KpMsyBJjiQMfE Derek Rodriguez 2402 exact
30 29522 joshuasoliz PE7MHRZWUEQM account_fPwqeAaDrYSWtRyVjBk8HD Joshua Soliz 2394 exact
31 29514 ericbarrett 5S2CV9FEFMKG account_fhkdvYwigwTL9hR4Stx8Tf Eric Barrett 2386 exact
32 29513 marysmelser ZS74NS5BBPX3 account_sN8UFBKJy6HHSCXSQp8oTB Mary Smelser 2385 exact
33 29506 nicolemaenn B7KS27WL5WSN account_pJFWGZXwDitJCT55tcuqeE Nicole Maenn 2378 exact
34 29503 yolandajudge 6V8RLNK75LM8 account_cWR4qvSM1SepG3DuoD8rUz Yolanda Judge 2375 exact
35 29501 craighovde AVZXWBX8GQTC account_q9j2WgU1236uDnx5rrEx8U Craig Hovde 2373 exact
36 29498 joselucas ZHMW9TE8QHRX account_izByTUMCPK7UT8k6Zncc4e Jose Lucas 2370 exact
37 29488 douggarber WXP9J5SRJWEJ account_vyxx47PALJmyUZZe3m495N Doug Garber 2360 exact
38 29482 carmencleghorn VH3CBTJZAJ2Y account_cRZQwgdsQLcok1dzXy6U5c Carmen Cleghorn 2354 exact
39 29472 rubenreyez 66384Y7B4WR4 account_aC929RXGkB1nsEHu95kg5v Ruben Reyez 2344 exact
40 29464 tiffanystewart 3EGVCS6RMF3Z account_h3oACsArSoq5fZ6vRyH9Br Tiffany Stewart 2335 exact
41 29449 dejadodson FLR4DJHPX6TG account_57jxCEcSpkMPUEVxDoZddc Deja Dodson 2320 exact
42 29447 coreyball 3JAUWZNAR9MB account_8fvkQS2hERUdtSF7DXrzpw Corey Ball 2318 exact
43 29442 toniyoung RJ24VTRM7JA9 account_mXgG1ZhfDfrnkU6usym3kG Toni Young 2312 exact
44 29434 melanieweddle XQ5L7PUM9RP6 account_piQoRw8BbqZpckbweTGdDu Melanie Weddle 2304 exact
45 29431 zackknuckey VP5CCX8LTE47 account_uh8Yfj5mdjaQjofM19p9MU Zack Knuckey 2301 exact
46 29425 christinehamparyan WEYX9R5P7J5L account_aJuvQYQACJ5Ms48bAR4hWY Christine Hamparyan 2295 exact
47 29422 joespeciale UH6ZCM9WLGRQ account_2BwbPKVKpj7QnFFpdZVqn3 Joe Speciale 2292 exact
48 29420 rogerbatt KGDJZ7V66QQK account_2gaAXfFo32nXEh7qA2yX8M Roger Batt 2290 exact
49 29419 kalebjames KMQDGPD5XB6Z account_67qbP4FpqwKmqSNjSmtCBv Kaleb James 2289 exact
50 29416 stevemolina BLLHWPY9RCTC account_c5VgNVVcBTRkHjbzFDEpHk Steve Molina 2286 exact
51 29414 mariatrejo EYPWCQKKWYZH account_ahaKfCrUTPA2mx7oM2i7Xu Maria Trejo 2284 exact
52 29410 kenhall LX897BGJNMK5 account_gZPLTs7y5We8wW5MZyZjkX Ken Hall 2280 exact
53 29407 deewilliams R8AGX3E8WAHN account_kjSroty8KmGKCTEArNC9Pa Dee Williams 2276 exact
54 29406 johnvayo DJJNUEEHGAW5 account_7hUzHAkpHagRdjPWYoRP9R John Vayo 2275 exact
55 29398 anthonythompson WB4E4MPJVHTB account_4CLddQNDWC76dtRwmYRdeZ Anthony Thompson 2267 exact
56 29608 stevecombest STVN23552Wt account_aM6WQ3aysaRPx4zpUh3zWD Steve Combest 10015 exact
57 29607 luisvelira uygo875491! account_3wT7DTZj4An5jaitLTTWTA Luis Velira 10013 exact
58 29609 keithtucker 14FYJSNUU48 account_7Wjw5Lru3JmVxq4s6cRqvn Keith Tucker 10016 exact
59 29610 angelatrevino KJGU39565 account_4yPBWjMpwT4tVHJyVTDiYX Angela Trevino 10014 exact
60 29612 amymontelongo ig76f7fuvu account_o7rGD6dKJP27Sf9AUMK2e2 Amy Montelongo 10020 exact
61 29613 michaelasmith MS21353GHY account_t23HG5uEXET828jSbivozo Michaela Smith 10022 exact
62 29615 janisstewart JD834HCUW1C account_fztV7NVRixwgnD6n392ZfX Janis Stewart 10024 exact
63 29616 irenegaribay uky75dkc account_ctACFtZH9mDX1E9tVgkpzy Irene Garibay 10027 exact
64 29383 leonardocoronel TXPC494 account_bLMBbmXsvurdQQaSokRzTf Leonardo Coronel 2248 exact
65 29374 alejandrolimon MM4BSQS7FRTD account_gqFbPtxAGRTSizXXLFB9j3 Alejandro Limon 2239 exact
66 29368 crystalharney A7W26H834WLJ account_bzPJ8YAjKNt5Bn7zhYf8zw Crystal Harney 2233 exact
67 29355 olgagutierrez WERWRMUDBZ85 account_hswERjNGcJtQnPWCkMnEQs Olga Gutierrez 2220 exact
68 29353 greghummel JV84HSVAK6XR account_myUaLbfFVED7hqEBBY1Pe4 Greg Hummel 2218 exact
69 29349 bettymitchell 8CHHCNBWQLRV account_t4wwpPyK98WBZUqLnwXKNU Betty Mitchell 2214 exact
70 29341 wandadavis JXQXXZ9MKFX6 account_iy49cEMsdawi18rTXarw73 Wanda Davis 2206 exact
71 29338 scottanderson RJ49A43SG3RA account_sDPK3iLsxwrLzndTwh2uxb Scott Anderson 2203 exact
72 29335 jenniferboon 28ESU8ELVELP account_3aCW8JeazPNe4UxjtGXzrp Jennifer Boon 2200 exact
73 29331 marialopez P3FNSPT7MEYM account_mjnZnogDMDws9ueMDxVzaP Maria Lopez 2196 exact
74 29329 cynthiasandlin MCDTLMVFQXAV account_sFzFdm2TTpyRaur4JfF3bv Cynthia Sandlin 2194 exact
75 29328 bethanybell 3UUC5X27PETS account_wnvmzBJwB3r7pG6a6WNANx Bethany Bell 2193 exact
76 29323 garrettojeda U5PRQYZE2MM3 account_chsr6oKpnEYuEKkMXn7CKF Garrett Ojeda 2188 exact
77 29325 rayschaak XSNLP62EK6VZ account_qhFf5wpTsv3FySr63biaRp Ray Schaak 2190 exact
78 29327 carlaswearingen E6DQ4B2T9JZD account_rzEn7BHU6Dmd6PkRwRpNXB Carla Swearingen 2192 exact
79 29320 lesliebewley 55KFSJMEDYLK account_jeuzxdVtC5u4W8utfKf5vc Leslie Bewley 2185 exact
80 29317 delainawaite EM7TWHUS4SYU account_kvePVE9fNZCokbkkvpBvLK Delaina Waite 2182 exact
81 29307 esparanzagarza VLL9ZNPRSTG5 account_dXq5FqPpKhAwE96NT5D9Jk Esparanza Garza 2172 exact
82 29273 glendabeauchamp RKA3J8WSPYJP account_fJVr5CSPdsgWtLNRWkidro Glenda Beauchamp 2133 exact
83 29256 glindarichter VXMLMDAFLNJ3 account_2ngDjpr4VqDCPsv2SNMHvS Glinda Richter 2117 exact
84 29251 nhumorrison EC32VDFKB9CB account_hZpk1bujwvFdyctrbfJtjS Nhu Morrison 2111 exact
85 29245 brittanyworley NBKYERRBSBH4 account_r39MCaQ811VXnw6QbhSG4b Brittany Worley 2106 exact
86 29243 dustynfisher XASFU3DBPZ5M account_sAYv3pn9D1KCwWr1TzGyeu Dustyn Fisher 2104 exact
87 29237 stephenbeegle RU5GZVTVACRC account_7RPLicRiJMb3Y1x3C4tEQJ Stephen Beegle 2098 exact
88 29229 roseanndellavalle 5GAF6JHUWWYP account_wTXj1c59SU9iRzeRmgv2dT Roseann Dellavalle 2090 exact
89 29231 duanewright VXK4FBLVFE7F account_3Hwm7znAWKLPMhmY2avtnG Duane Wright 2092 exact
90 29222 pablohernandez P3YB6A4JGEDF account_vkMiPuMKu6XmQt1G9CP1op Pablo Hernandez 2083 exact
91 29217 pavelavanesov KP8ZSGC2DG2U account_kNWT9ewUppfxMtxpcgScKZ Pavel Avanesov 2078 exact
92 29204 sandrahoughton 5XR4QMPBXVWY account_sDBLs44VLbWy9ZRhuDAkpL Sandra Houghton 2065 exact
93 29203 adamstaeben GP7RANJ8UCYP account_u39DmoPasPkSoYJ3bTG8kz Adam Staeben 2064 exact
94 29190 russmott J8ZN888UQSLR account_7QghTwmH6MwtZdTrvur8uB Russ Mott 2051 exact
95 29189 karenstewart JSKTFAJ2JYDX account_wSUPq2ZjUF1Va7S3vap91M Karen Stewart 2050 exact
96 29185 heatherwomack 89W7S5Q7UAHP account_kSE9uAW6WPbt4msXe4mzHs HEATHER WOMACK 2046 exact
97 29183 wyndijames CPSDNRQU95HJ account_hJwbN2EUqGCtNj45JCyQda Wyndi James 2044 exact
98 29182 angelicacuayahuitl VK8PZW3LK5F3 account_sooioQ729DC21LJa1zKMAN Angelica Cuayahuitl 2043 exact
99 29181 chasewilliams W6AMPQEPY3BC account_iQqfYFDTb4UxtHcgrGt8EP Chase Williams 2042 exact
100 29176 kevinarana JAJYSEG75UZS account_oR5nKGJTvRLhkCyUGG1B1L Kevin Arana 2037 exact
101 29166 chrisclayton BFJ462PLHJFV account_aamYj92hC8r7nxvbAfiHCE Chris Clayton 2027 exact
102 29162 billyhorton MPTX573ZUYYG account_4DuPXfEqx5pRPftTGKkbhS Billy Horton 2023 exact
103 29151 chisediquezada RJBYQ2NUB829 account_1KjfxaKx8MvMEwb7uU3eSC Chisedi Quezada 2011 exact
104 29147 angelmerlo EWWAPXN9Z2YS account_deGyZc4zP5TBJGSibkGU3a Angel Merlo 2007 exact
105 29113 kerryblack 9D5S9ZD2FCBV account_qncL4zimMnsq5ViZrpNXr1 Kerry Black 1974 exact
106 29110 cliffordjennings 5GC7M65H9VGT account_rknD3GcgUkTd8XB7rZZjND Clifford Jennings 1971 exact
107 29098 carlakimberling SS4KRJYKTV7B account_2hJ8frAhU5LDVpaoSH27Tx Carla Kimberling 1959 exact
108 29088 richardconklin WEUNG5H3K8BB account_7aEgkmhMKwDVqsus5Ln8Bd RICHARD CONKLIN 1949 exact
109 29089 leandrabeshea 7V8BZU4G83VF account_doS7HkZWacVd5r8nWxqDFZ Leandra Beshea 1950 exact
110 29067 robertfinch D7FMTT455GBB account_jJoU6UPYd2ckqbUhynBx7B Robert Finch 1928 exact
111 29058 ericcoffman MMEBXQYLZWNU account_4mZAzz4GiGtMU8CJBWs1cQ Eric Coffman 1919 exact
112 29050 nathanaelfinch JVNC68FV7N6B account_t87r46C6jaw8rjJYurmHrY Nathanael Finch 1911 exact
113 29047 gabrielasereno KCH82WCRA2YR account_99RoiQcJpubSAqajPZjxFf Gabriela Sereno 1908 exact
114 29045 georginacovarrubias 8QT57NUV8C7R account_gERfDmYXtJj5PLCY9ELLS7 Georgina Covarrubias 1906 exact
115 29035 thomascline E523BKVU5MJP account_g8yP63NFmbioePagUH1kQS Thomas Cline 1894 exact
116 29034 susanlewis R7TZFZEGLNRW account_mqMu7hR16hGpAesQ3ba83B Susan Lewis 1893 exact
117 29025 tammywelborn RMC482WNBS7 account_rjvhtkZxuk442ghJaKsdtG Tammy Welborn 1878 exact
118 29023 haleymuela BS7X3DTKW8TH account_hmnByz3wfiafSujapZBbi3 HALEY MUELA 1884 exact
119 29018 christelles WL2TKLFG8G2H account_uGdvd35CHuBkiBamMnB8Bj Chris Telles 1879 exact
120 29017 mauricioantonio 6QJW7RAPDBWB account_tX9wv8qaDb4bCFBxKPsMDe Mauricio Antonio 1877 exact
121 29013 vincentlimon YDDXKFABUZY7 account_eZ6nJGAsmsPwR8sYvtXBRw Vincent Limon 1873 exact
122 29634 jeffreyharder YUV565D67RT account_gWD9gTNmR8RbzQkJmjpRYy Jeffrey Harder 10073 exact
123 29635 pablomartinez 2766581BER account_g2THEPVu4zgnGsTeFh6MpF Pablo Martinez 10074 exact
124 29636 krystalmendez 6FTCKU6S56 account_hiH7WbsYQm1HRkpGA3jJ2v Krystal Mendez 10080 exact
125 29637 davidvandagriff UTRC53865F9 account_kAa1VyPLqz5fecRMXpV7d4 David Vandagriff 10082 exact
126 29638 carolynwalker trjxtyx75! account_wsZpXzz2mhYMxMLmiVGNvF Carolyn Walker 10084 exact
127 28979 claygilbert 3E6XFDHXCAG7 account_eVxdo9RAAoBbisk1EYCKnc CLAY GILBERT 1839 exact
128 28978 jmichaelculverhouse 2147099521 account_q4nxMiXUoEj2DkH3JUspLP J'michael Culverhouse 1838 exact
129 28971 diegosereno 2CE9F5YSPQHX account_vvPvzpGka7xmJQnqE3gMxq Diego Sereno 1830 exact
130 28970 ashleysmith F8D3FVCZB7PD account_5nSRJ7EkJpz5dpB2QGRYfz Ashley Smith 1829 exact
131 28954 dougstowe 5D35LFD6GEAZ account_3hQJhhHf48sszYWBaMTjpR Doug Stowe 1807 exact
132 28936 timbronschidle 3ZVLCWSAE3EW1 account_o6gDBbdWzQYeUbAQL2Zp1s Tim Bronschidle 1788 exact
133 28928 cynthiajones KC7ESYHEZGNR account_hJ5wonckmH2jkaykKH2g4Q Cynthia Jones 1778 exact
134 28867 bobbymack UDUBTCXXF8VV account_nNUBZtEptfAD6TpDteqjfM Bobby Mack 1712 exact
135 28848 americatrejo 3B63TLTT9WE4 account_gjzpfGyVuYMaQwdYA2Gufy America Trejo 1693 exact
136 28832 tjbanschbach 8WTWTXACGG5N account_hU5voij99AKqckcGpok7Jx T.J. Banschbach 1676 exact
137 28811 luiscabrera RWUQ94XT9A9Z account_mrPPtWesWBzmcD4Lfvp2hA luis Cabrera 1650 exact
138 28810 rosahernandez JSGUXKWEMSVN account_qYeNtZSW1nYgukr9kYy5qn Rosa Hernandez 1649 exact
139 28807 carlosgomez J22RS7F3LGG2 account_jNj9rpkTwq7wF3vJbMuAkF Carlos Gomez 1645 exact
140 28763 jeridrutledge GJ3CZTRJJW2M account_v6odXP1nVxoVvTMUrUn3SS Jerid Rutledge 1601 exact
141 28748 timthomas NY223RQJD7PN account_m5ZTZYHZbzpVPeNfRhroxn Tim Thomas 1585 exact
142 28715 carolbates J3UBUCMYDX64 account_97wbzzkGPxCPMyfUs5uTTf Carol Bates 1543 exact
143 28710 shirleyelmore D8Y64N9L4R9W account_gfPawVk3gSuvufuC4zVjx9 Shirley Elmore 1538 exact
144 28698 swedlund swedlund494 account_u9nP4wDufL6eQGx3Jnoyn6 Brian Swedlund 357 exact
145 28695 wendysanders RYG9L43NWDZV account_4KPsmHxqdcnsRNXuWB8s2Y Wendy Sanders 1524 exact
146 28693 robbymccollom RMC482WNBS7 account_xfht7KCxBpasoRqtkTojjj Robby McCollom 297 exact
147 28664 heatherbrister2 KK37V8W9ZD9Z account_m1hacYUopGGJrn2UyJvzBu Heather Brister 2 1495 exact
148 28663 ryanlloyd HWEMKNWX55RF account_pFHZ8RMuvdETPpHr4vyDWi Ryan Lloyd 1494 exact
149 28661 amyrodriguez WX3QRT2U6L4X account_w1ywM8kUBcURQHGjHQqirn Amy Rodriguez 1492 exact
150 28660 thecountrycutter YXpScFIneePm account_1uu6CnSCUuCfBabMwfikLz The Country Cutter 396 exact
151 28659 bradwilson ZFAXA9AK63AH account_ujyp8hfbksvc8EZALQ5gTL Brad Wilson 1491 exact
152 28654 edrater FRY4823N account_fNtXkw98GQKVvcjcfbeb9F Ed Rater 126 exact
153 28637 vikyacosta Z4SMV49SAH72 account_mma7TzxJUZKUzXYr6YDUmt Viky Acosta 1470 exact
154 28633 terrimack B9HNTSN7ASQ4 account_wU2qgfTxqjaLSfhwWx4Cxm Terri Mack 1467 exact
155 28617 luisarellano GVNWBQMM3S7U account_t9DUQUCtBAWojjSYtZQusE Luis Arellano 1450 exact
156 28609 dorisavalos DLKMJS88AF2W account_pMdxeChdNqnVRQs1rSAKBE Doris Avalos 1441 exact
157 28597 jackierollier TSFNN8DVDSPR account_w5t1gDksfLnZqU35RFxZES Jackie Rollier 1429 exact
158 28588 charlesmccreary 88J6XS6TXRMA account_dQ3cvFTw98ByhcBzDGMsNQ Charles McCreary 1418 exact
159 28548 jamessmith LRVU6RVG5SP8 account_ayvJr4cDCtK3zNPcoVo5vy James Smith 1373 exact
160 28546 davidwhitefoot PYHTZMHJU8R8- account_sWngcEfTk4Ed83ARWYtZga David Whitefoot 1371 exact
161 28536 patriciavasquez P88256487V account_6wngyAj64jCwvn3W8T3sdT Patricia Vasquez 1128 exact
162 28535 adrianmcdaniel U4TGDH2PSAB4 account_aScSfG25Wwo7cD4xbNhja1 ADRIAN MCDANIEL 1362 exact
163 28526 justinvoga URHVCX7SFSFU account_cwmXrKm93bSteHQfm3SJtd Justin Voga 1351 exact
164 28518 jenniferlittle 8EAUUDQPXSQX account_oSA465twDMxZedY5zbPLT1 Jennifer Little 1343 exact
165 28510 bradslate AF4KVQ2P44M7 account_nhYxp4RV7gpLxWnLL9oWs6 Brad & Sherry Slate 1334 exact
166 28494 irisdelira L6XGQA583DG6 account_mVgspPEmufZG8KD83Cnyjh Iris Delira 1316 exact
167 28491 richardbarragan WJBS86MMJBFH account_hJ3iUD77LRADfhaxwwsqLj richard barragan 1314 exact
168 28476 jensenhall F8XM9U8SC6LL account_m3356qm8wGGJDqNZCgXRev Jensen Hall 979 exact
169 28466 paulcomella YTZDMBZCMPX4 account_rRKbPsCejKqMnAb29xPPFx Paul Comella 967 exact
170 28449 amberkrings 5NWBR3JLV6N8 account_eB2k88UWVdAFKYgcg7iK4h amber krings 1273 exact
171 28438 mauriciosoto DJ7YP7W7AA8M account_uyp7QDJmZyCeq9BTGnvoZ3 Mauricio Soto 891 exact
172 28435 jimcrank PLA7XNX2F4Y3 account_p9AaVzWfCwR74RVK3k6kPa Jim Crank 1257 exact
173 28425 summerbeazley D4S4YRB8Y3KA account_9AkWnUdPzs2AE9vfqmbz6i Summer Beazley 1244 exact
174 28426 davidarmstrong MPEZGG53KQSZ account_p1wD82RWMCTi1hRoLHdjHL David Armstrong 1245 exact
175 28395 stevenspurgers YS5Q785J2HGZ account_nxruUEiwq3NpZTexeEo8jd Steven Spurgers 1211 exact
176 28388 connercoleman 6108RANDOMREDDOG account_6VLrBr4sZCAJwMzzcEkhSE Conner Coleman 1202 exact
177 28367 judithfierro FUZ33LB6SFFC account_aFYkyi9DGrJBRgQ4jSud96 Judith Fierro 1177 exact
178 28359 tonyasipes N5VEZR8Q5M86 account_rKLQuRuTgybF3YMnNt7VY9 Tonya Sipes 1169 exact
179 28347 kathysmith SJ53PSYCZLPR account_3zbkHwfVAegXCPUhc4XmYV Kathy Smith 1156 exact
180 28344 cherieeshelman BRUV99PN6Y7L account_nwbZPHiXLbR9o6LZppPSoY Cherie Eshelman 1153 exact
181 28289 ronberger Ronb499H7ALW account_q8QjfTJ9rtgoow3eHLpVYQ Ron Berger 301 exact
182 28267 sandracrouch V4N3764R5J account_tCddLw595sDcQEYCrRCmFq Sandra Crouch 311 exact
183 28263 abbieandrews YQSJ499H7ALW account_vHWu6NZDh4Ajh6YqWEXPb4 Abbie Andrews 1081 exact
184 28253 perlachavez ZWR9PXVFFYBS account_tTLx1Zn6YTykzm8quV6ykg Perla Chavez 1068 exact
185 28246 susanking N2KUYLPDNXJP account_xqwwg1ZAd6SKt4LyrcAebY Susan King 1058 exact
186 28241 terrybachran NG9VMH25LCTC account_s6ZC2GALaCFUamcQafLyy7 Terry Bachran 1053 exact
187 28240 lanaygaunce MJCNZAJHK9H3 account_pUesJC8A4ENdQ5XZTFGdZt Lanay Gaunce 1052 exact
188 28190 heatherbrister 6XPLC545G6HW account_wkn6qdAVF72b7TxwZxvW6D Heather Brister 997 exact
189 28184 jenniferdunaway 343V5CT2R7HZ account_bTsNpp5j2YHU9dKipBm3iP Jennifer Dunaway 990 exact
190 28159 elviraquezada HXAS29VRX33V account_q5J8BYNnSG5TtFnikCTEmm Elvira Quezada 960 exact
191 28154 jordainpatton J8P4TPF7QS2A account_p5VqKAXXC8t3tAFSzzr8cC Jordain Patton 955 exact
192 28140 mattkosarek RJ4BQUZJY account_7eCHrmvA6X16gn1zr5wCo9 Matt Kosarek 932 exact
193 28117 shannonclark F5XESMCJ account_8YqGj24SS5fxoZhPUYBED9 Shannon Clark 908 exact
194 28098 kevincostanzo 244VGMVMH66D account_9DuddEuiaH59KtVGReTwJs Kevin Costanzo 881 exact
195 28093 kenpatrick V7U3XBAPTSV3 account_omX4bSM3rXDShahsdc4aHd Ken Patrick 875 exact
196 28083 briangallimore 39V285NX account_k8GTscTwPuAvgtyCbT6vyG Brian Gallimore 55 exact
197 28072 amberprater RP42B7AYDLAP account_fXqciwzW348S1eYSKbbi8B Amber Prater 855 exact
198 28068 marilynfowler MarilynVG72P8H account_v9vCsfc4uMAk7LgXyRR9EC Marilyn Fowler 851 exact
199 28065 douglaswilson HP97HG9XPYC3 account_bmXh2S5K97hPyUkkBRj1ub Douglas Wilson 848 exact
200 28034 terrygortney GQUGR3VJUXBR account_jPuzvdHGq1BXiDALjrVaR7 Terry Gortney 815 exact
201 28031 gizellebutler HMZTX66RTPVX account_fipAsvnLVUkrSmuJ9GYdEb Gizelle Butler 811 exact
202 28029 bradgurney 7A6LRZWT82DX account_aZMRveyhHvJTHTQCT7yYdf Brad Gurney 809 exact
203 28024 darrylchavis TDGUUNXGMYRC account_oP5ATcm8WwBDBv8t8mh4vU Darryl Chavis 803 exact
204 28025 kathymazzola 5MVS8VUF9QL2 account_g5RpJWMv32tFk6c4yy5F5c Kathy Mazzola 804 exact
205 27994 austinwatkins S7M4UD8QUTDP account_htyg2uThjYmLbHsX5a3RCj Austin Watkins 769 exact
206 27988 oscarrodriguez TGPCF3Y2ABXS account_fNLTDkPjpbzZ7Eta2ofer4 Oscar Rodriguez 762 exact
207 27986 erinthompson 69TQZ797 account_eGe4Qkw13xjjwabHBu51F3 Erin Thompson 394 exact
208 27983 brianhardesty 8D4P34N8 account_7zjMVqCfCV4jZT68myj6p1 Brian Hardesty 20 exact
209 27976 cathyday FAXMKENV8FCT account_aZmcKqobMWGKhxagDdLCFA Cathy Day 755 exact
210 27956 fredheckel HUVZSE8RGD65 account_mx1SSXHEiptCZaz2Gx6MTm FRED HECKEL 735 exact
211 27949 charlesboone Ccbn78wdfksB account_iNnHAwxaWRUXte6w7ijGV3 Charles Boone 62 exact
212 27948 rachelguzzardi R87wgerf8bsG account_tEZyHoiA8DvmisscRkYy7U Rachel Guzzardi 287 exact
213 27936 juanalonzo 745GF0JK5!W account_itAy96jDYGcvWjcHsy6XcR Juan Alonzo 198 exact
214 27930 kristinamurphy oPXFrylqKf account_qx5oYHwkMNxoKEXME6zu6w Kristina Murphy 725 exact
215 27926 johnnygoble J829fhf7G account_1m7oDB9vYUYJQffFHvxQaz Johnny Goble 416 exact
216 27923 keithbarber K73bvd54tB account_bKdyNcZqZ1h8qaQAcJzTRK Keith Barber 208 exact
217 27917 doreengrubb DOR32SVT1234 account_5n5qjvMULWVYcGGmF7uiER Doreen Grubb 122 exact
218 27916 steveraia DRR648DFTMSE4 account_g7EjAfDhcTH9oxWZGJ4CTH Steve Raia 291 exact
219 27896 twcowley TW486TW account_h2bYtWBz1nbRRVBiYvi8wQ T W Cowley 710 exact
220 27888 joepatton L7luYzSKSu account_hWsePAjG5pq8FnnDQm75GD Joe Patton 701 exact
221 27873 timfisher Thd73gdf5eF account_hMQqEcyWpzLTEtUnCJmKsi Tim Fisher 345 exact
222 27847 kileigheven K9872gdf8E account_4aPqi935STSGBSMxsLNwSm Kileigh Even 215 exact
223 27833 deannecook Dc872cobnC account_hqsfU7P5wK2uDjpfVo8zxb Deanne Cook 110 exact
224 27830 kacyharrison 7Ozr7el6K1 account_t9Bb9uXEB9NjGoTDvCThi4 Kacy Harrison 643 exact
225 27822 milliganwater 8NjGMNsTXd account_7FafmmaEuKaNeaHmFFLSaf Milligan Water 636 exact
226 27813 michaelnewby TM5M3HU877 account_1moqtbSRAJxiVR2pTtH3f9 Michael Newby 623 exact
227 27808 ladonnaclark YQReVH9men account_vPgQBpVJ4y2gkpRgWTksvi Ladonna Clark 612 exact
228 27800 jamesmooney QNUqD2tSzq account_5C4WjLaQS5tGDxZm4NCZds James Mooney 603 exact
229 27783 tamythomas TT4699520858tt account_4gF4CqD6W7iS3UesE6js3g Tamy Thomas 587 exact
230 27780 josephcruz Z06oxJXrNd account_nYfEqA9HUY6R2xi1PvCQVs Joseph Cruz 583 exact
231 27761 franceskirby FK8179391152fk account_v2eMTZjQzV5Q3B2vLxGCpa Frances Kirby 568 exact
232 27759 chadwhitsell 79374B75phGy account_swiMEEPxqsGukTQMUEP7DR Chad Whitsell 61 exact
233 27754 ruthfengler 8q8MX6NQP3b6 account_2ycJgX1XpXSqDiDgNisZbA Ruth Fengler 304 exact
234 27751 bobbyfisher salvagejuggle account_iuZZQ7KJWWSihZh9mzi2B8 Bobby Fisher 391 exact
235 27748 eddieyarbrough 89298ChZWzMy account_6KuhoNq2CptmBHyHVS33cS Eddie Yarbrough 125 exact
236 27744 chrissyeagle 9vkX97426Sgt account_3uu7k4euycmKnZii9DQAj2 Chrissy Eagle 74 exact
237 27739 victoriaobier M57B288P9 account_cPSKw37hjbGmmpHzzHUum1 Victoria Obier 371 exact
238 27736 bernardheer BH33682019BER account_fcUL4WRuUX7LrvPhjfQZQt Bernard Heer 46 exact
239 27734 garycable M2w6z5Y73 account_6JkDjyFzfp7pPcvn6tsd9f Gary W Cable 144 exact
240 27735 coyungemach MhS569N84e7a account_f5ygK53FvcVakZs4YwNVo5 Coy Ungemach 84 exact
241 27733 michaelmccauley CPORADIO98 account_ecewjvtr229nzEjFgSEhf5 Michael McCauley 259 exact
242 27731 lessruble 9U35SXxM2H8y account_6M1cjjzHDyrtJWimoD6Tgp Less Ruble 226 exact
243 27730 marknunnelee 833j2Ymm4yfV account_mBYcBCnbFTgVxsDNCUrq9z Mark Nunnelee 243 exact
244 27729 elizabethchristian Webg22nj account_3Xpb7LLq4bEFZxohgYvjUS Elizabeth Christian 128 exact
245 27724 mikebell BHF538HSEBDLY4 account_4vuDbTF964RUD2YKA5hmhQ Mike Bell 556 exact
246 27713 krystabates KB2145445824kb account_crxqAidS35hAeKLFDHKmiy Krysta Bates 218 exact
247 27708 chrisfrancis DR45QC74M account_pcYVJfTDAUK9ALviS7XBjW Chris Francis 388 exact
248 27701 markfisher 32fd8P&rw8s7 account_hxsSqNHZMtFbXDA8DtvRso Mark Fisher 242 exact
249 27696 kirkvanmeter J2sR9g^83M33 account_bH9zyBQuBcgkiU9EZPYxa5 Kirk Vanmeter 216 exact
250 27694 dananance 23SP$e!ug232 account_s9ZW1FpS3wkDoh31CjU1Zw Dana Nance 89 exact
251 27690 keithcrank 2zt3T5w72T3A account_iSuzVkNmem52NdXXh1Savu Keith Crank 209 exact
252 27687 scottarmstrong 2SV58M55 account_dCvreFY2K3nJW8ZkCPaeyR Scott Armstrong 315 exact
253 27683 floydday Tfdd3254tfD account_rFM2cz415W3Rqn1McU5gpW Floyd Day 141 exact
254 27680 mariacortes MC2145858276mc account_t4Qnzxsq3c2WryFDXTSWba Maria Cortes 435 exact
255 27668 christianlummus K6dbr631ddL account_fWd5egSy6XovRRzTgi5xZk Christian Lummus 205 exact
256 27666 marklindsey ML9727346434ml account_ayztehQeAayBTQkizozi2B Mark Lindsey 531 exact
257 27658 joeywhitfield BV758qXne446 account_8bxr54hmvSEFRyDLfvCgie Joey Whitfield 187 exact
258 27654 taekim SAN11837TCK account_wbsKtfE9W1SyPkqk9hFBRp Tae Kim 408 exact
259 27652 teresarobinson T645trdf87fR account_2SA7BzkR6cqsS2jSQ14nRh Teresa Robinson 376 exact
260 27651 nathanmctee N32462uu8rM account_2CZKhrkB2TxZDQTWxuJxVz Nathan McTee 273 exact
261 27646 rebekahmoore R756346tfg4M account_k8gD8YGUQzwWCE7JT9EfYc Rebekah Moore 386 exact
262 27643 monicatrevino MT2145877143mt account_d3CqUjkDwdiB1pwR6t66XL Monica Trevino 524 exact
263 27640 rachelfuller R8364hbst account_t7qMv9PTBxtj1rmr4WmEqV Rachel Fuller 286 exact
264 27638 shannonbrown S9725298223b account_nZ8roCqzpsY5Wv86p8i6Sq Shannon Brown 318 exact
265 27637 allentaylor A6346gge8T1 account_44F55EHwRrLm33EvyuCPgG Allen Taylor 26 exact
266 27632 kristenaustin KA8017916972ka account_qunWwkhDGN8jxGDMfB9FzA Kristen Austin 522 exact
267 27630 colinhopper TYVU65445IUB account_jaGX2dnVZVs463ok5kANR5 Colin Hopper 245 exact
268 27619 srireddy ycHAvetmF0 account_jS3WVFZmrQu5EoM7UJ4L1N Sri Reddy 514 exact
269 27615 almaacosta ENKC5DGV account_rv1k2oTH5Dn7sDTJiTxADa Alma Acosta 28 exact
270 27614 chrispassonno C8766thf96P account_8UMvyYLatGW2qwSqH62KbM Chris Passonno 73 exact
271 27613 seanbaxter SB4696679337sb account_vFwXqaiaquwHXzgMkdg1mc Sean Baxter 512 exact
272 27612 collinhartman B886tg98jt9L account_kvBUopRRFzz8B5PgbdjLGG Collin Hartman 402 exact
273 27610 michaelhughes 1RKk32MyTK account_6QT3zjSA88VLdbBJtAt44j Michael Hughes 510 exact
274 27608 michaeltalbot Mjj1276rt99T account_4bK2RJU3FRwyH4SPmnik1q Michael Talbot 262 exact
275 27606 anagonzalez WNAHDMwWoE account_3jfw5hMXURMmVuGyafdSPj Ana Gonzalez 507 exact
276 27604 leonardlewis L1877ef32L account_3TVj7ZpGiTYYt28q8dh9VR Leonard Lewis 224 exact
277 27598 jerryjohnson J177895aa98J account_fa8emiMvGC9ZkdA6uuwMpF JERRY L JOHNSON 177 exact
278 27594 pamtoler NfxcgYQUbj account_mnxwgFcvbjvDjnBBdGQzek Pam Toler 501 exact
279 27589 jodymccluskey JM4696883349jm account_35QGS4ZHr9tSpCVKNHE1ZT Jody McCluskey 497 exact
280 27577 chrisvannatta !4694502484! account_dyfr7aKWGD5zJ1pdeEyyna Chris Vannatta 426 exact
281 27575 mariomerlo !2149579585! account_3yQbySMiwrU5kFJRA8itm6 Mario Merlo 239 exact
282 27572 williamarmstrong !2145510334! account_5izwKtCxbvkCupVQJ1KBBE William Armstrong 362 exact
283 27571 trecedaschmid !4692075942! account_iR4oM82C6m8zkrFDLkSbYZ Treceda Schmid 355 exact
284 27570 ronlewis JDkf1AL942 account_5Y7chdsykaLwBg3s2rqvMD Ron Lewis 302 exact
285 27567 sherryerichardson DR6528BCD3S2 account_oadyDfiynqanaMffoSVM2W Sherrye Richardson 321 exact
286 27566 joannarodriguez 394X3YR9 account_gCVrkxTWdSWaYUNtyk1rqr Joanna Rodriguez 185 exact
287 27561 dannycampbell !9726589541! account_4ww28eUsFRv8Yh5RkWe9cz Danny Campbell 92 exact
288 27560 howardcomstock !3365494230! account_kZMVM4zfKdgEAyCCvVREU8 Howard Comstock 156 exact
289 27555 terrymiesen !4697420828! account_bXRRk1Yz4nKkCPsdzDJyYa Terry Miesen 339 exact
290 27550 michaelray primitivedefinitionstoredinner account_8Rh7edwTjNBYw9FJSq4WRT Michael Ray 379 exact
291 27545 terrybates !2145786644! account_d6m5HYMAczoeH2ovA6vqxj Terry Bates 490 exact
292 27538 gregmcintire v9368$E9mx9m account_fyH3ZiVVLx9h56vNYVndbK Greg McIntire 380 exact
293 27543 beverlyerwin MOOLAH account_9kYh3vsMa7mG8JrGaQtF2V Beverly Erwin 48 exact
294 27518 helenlumpkin Heehhejd0N account_ts53K9Gc61HkXi7uqcaiN6 Helen Lumpkin 477 exact
295 27508 stevechristiaens zY272m258*vJ account_3HSd4Thc3iXfcidraN6ZSV Steve Christiaens 329 exact
296 27505 judydevine OD4697669725od account_e8ZUaWmpXqA5NTVfBHaYfq Judy Devine 277 exact
297 27500 rhondabolton RB2145786030rb account_x8xohdUBJBWyd87jTCtP3r Rhonda Bolton 295 exact
298 27492 rickbeckham tVj7ZI5QA0 account_f861cgYw7zxSiyUh3ngUYk Rick Beckham 464 exact
299 27490 vancepeltonen V66rdf43aaP account_9arNHyqn6bwuUyPm4uWfrx Vance Peltonen 356 exact
300 27486 jacobwilliams jnlka9994jkfldsa account_bFr4VtSNkJpdZv3K67mP5y Jacob Williams 159 exact
301 27478 classicshear KARENALDERSON account_tui6mE1CTprghAF31hKWLp Classic Shear 400 exact
302 27477 davekiowski FNW63KXSAPT7 account_xr9LPLBvR6SHF2xmNdrNfN Dave Kiowski 100 exact
303 27458 paulsammataro P2n74V7P8g4S account_cwYTP5p9ubFdXioqnmuDsy Paul Sammataro 283 exact
304 27460 janetkern j42252^Bhf44 account_skzmJw1vJfndNPQNWsJjRL Janet Kern 449 exact
305 27457 carlossalaises 28E358y$JFB9 account_5mD5W5PczTzo9H7it3qxLX Carlos Salaises 448 exact
306 27453 obedsandoval *H78z9xy84C& account_k6qzzjxrJtH4UT7BerwBzk Obed Sandoval 275 exact
307 27455 dianewiley Diane03t4&adTL account_n9yXaEBkPmAm5SdrbTwsTw Diane Wiley 442 exact
308 27444 tammylove f65NS4Ct6$29 account_5WZN3aLBoWBUHeEjHYySgw Tammy Love 335 exact
309 27438 floydallen V3864VB2 account_9oLnGJZwNxJTrJRrQgCX59 Floyd Allen 140 exact
310 27432 hortenciaalvarez WHTABORG3593JKS account_oNBXVRFamuurD1rz4bugiE Hortencia Alvarez 155 exact
311 27424 timbagert CF18n42cY7 account_2U67Q5pWZCgbWGX1tLXbXD Tim Bagert 343 exact
312 27425 davidbaker DB764g1d87rtB account_okpeaMgrsz9nNs1cSMjQv3 David Baker 102 exact
313 27421 janicealexander AG57TSH89L9 account_pdQ864pRJ5AbJP4YHtv1S5 Janice Alexander 165 exact
314 27423 timgilbert 73xi0r600Sz% account_ezDVFeaw9XwLiVvM9iEDwJ Tim Gilbert 346 exact
315 27413 jesusrios 8Di6QMs7%7m1 account_5RA5zrZJXyuBEj4CmuodCh Jesus Rios 181 exact
316 27409 veronabaptist DAY753LIGHT253 account_j9cuBn7ojxwRH3BfRWmDhN Verona Baptist 382 exact
317 27406 whiteywhite 45z6r*H4418n account_32CUFBTH16juiHGzrKPd7m Whitey White 303 exact
318 27402 louislafon RFTRE4825CVKL account_9ayF1oF9iawPMpCdQSiSqX Louis Lafon 445 exact
319 27397 ausencioluna ABN765MNL account_7Gj6TBTXHmpu49iZGZBYT4 Ausencio Luna 427 exact
320 27374 davidvillanueva DV5489CRPMWBQ account_d5hcuzxZBgMErVfhrXzQAJ David Villanueva 109 exact
321 29621 coryrees yt5c6ujhvu account_uRC7LnETNNZdRmSEQeQbwX cory rees 10049 exact
322 29622 brianamartinez ktydr6428 account_gazJNXCsEwG1UJBmYyQ6xB Briana Martinez 10051 exact
323 29623 khushbooagarwal UUTFVUCTC446 account_5xtd1ApTppgpHnRpvmQHmV Khushboo Agarwal 10052 exact
324 29624 elisasanders OYTCu3276 account_hZD7ECmwCWd5vG73hEYDUN Elisa Sanders 10054 exact
325 29625 josemedrano UYVITRUR24 account_3froYZwHV4X7v9wW6TVu8N Jose Medrano 10055 exact
326 29627 nicholasterry VUTYVL746 account_wSzw7Xd3wb871Ug9aaqUcH Nicholas Terry 10059 exact
327 29628 leslieperalta RYC66747 account_dQfUEUmdqUu6rGALsoowVQ Leslie Peralta 10061 exact
328 29629 khushbooagarwal2 iytf65gkj account_j2iH6cW5tFhsSKxjLUvQBb Khushboo Agarwal 2 10062 exact
329 29631 royrogers TYDGVBNC account_nCAKAgLxtPx5H68AJZMNUA Roy Rogers 10065 exact
330 29632 arturoprieto 1653APVN877 account_pR6sTHc4xvLqPfAZhJxrxd Arturo Prieto 10069 exact
331 29633 ericamorris E16488VN387 account_gG5uDHsz8FPiuknRCkpbHQ Erica Morris 10071 exact
332 29617 belindamillener Uiyu7280 account_mXx2EoT7dcv2am6asSEJFr Belinda Millener 10032 exact
333 29618 kailynnbarnfield HAkuguuvoy1 account_hVh2CveE2tBqctZhpLX5Nf Kailynn Barnfield 10037 exact
334 29619 thomasgraham YUiygiiu4 account_fETWiC2bAqbb7hkQ5UvKjj Thomas Graham 10040 exact
335 27315 debravega RCG8618BDUMSR account_fbgziTjDnsrjH4aA2hMykS Debra Vega 112 exact
336 29582 davidlanman 2BMMRYQ2PHEA account_u9P7hnf3FR5yqm7FFyCnrP David Lanman 2486 exact
337 29583 anthonylongo 9MLG8HVME4J3 account_eq2Po3eJPoEyv6RvhhyBMA Anthony Longo 2487 exact
338 29584 konradwoelffer BCQ8NRFVNA3R account_7B7CchNkRbNa6LLxRBform Konrad Woelffer 2488 exact
339 29585 marthaflores KG2QEG36348V account_cdbWPX28Kv3YSaYUDf7zj1 Martha Flores 2489 exact
340 29586 connorspicer 6U2BWJKGCX8G account_pV5Vstq2xr42fru5zVB28F Connor Spicer 2490 exact
341 29588 korybiggsshop JA4SXBGKHFUL account_irdUq6W33yA7J9vpQ5t85q Kory Biggs Shop 2492 exact
342 29589 ednagriffin AXA8EZEAN7DJ account_nnSEFSX86bVogx8KsqANUP Edna Griffin 2493 exact
343 29590 richardrosson 9J5L6MEH4Y7Y account_jL9tR5FgswV7FZwxH3U5Ex Richard Rosson 2526 exact
344 29603 allentaylor2 NCJW87FW8723 account_sEcueqC1ysh7Ne2pE9qbg2 Allen Taylor #2 10009 exact
345 29604 alextovias 7VSgrVO1WX account_1BvWee8L5krmWQsfVUqCdk Alex Tovias 476 exact
346 29605 mikecurrence MKH23FG79C account_gbzPYU8zHeCSV7E5c5eksc Mike Currence 10011 exact
347 29594 terryrector QUPP83ZUUSGL account_bHx8uSNfzCrYkD8JriK1jV Terry Rector 2529 exact
348 29595 donnacampbell KYCNTWS3929N account_4y4ZEDFYqQhjW99pPJyLnf Donna Campbell 2530 exact
349 29600 billmctee Bdge6388M account_qdzi3bYpEjEx7crLFY4xCw Bill McTee 373 exact
350 29531 daycor U45YBPSARH7N account_aZmcKqobMWGKhxagDdLCFA Cathy Day 755 fuzzy(manual_override)
351 29521 tiffanyyarley-olenak 98HTZ9Q8FARG account_cwmjmXkrYeba8h8RGqLm6E Tiffany Yarley-Olenak 2393 fuzzy(sequence: 0.95)
352 29395 vanessagomez1 J5JEW3HEBVAF account_gb82EhqTwbNWWEovPu4oKZ Vanessa Gomez 2264 fuzzy(last+first: gomez+vanessa(fn=1.00))
353 29611 brittannieredden1 TYFTUEZ7429 account_bB6RDmNpkzndVbSh9CvgaL Brittannie Redden 10017 fuzzy(last+first: redden+brittannie(fn=1.00))
354 29614 samanthatheis kf6u8djigk account_x46hMNq15qHmEvP6fNFdfm Samatha Theis 10023 fuzzy(last+first: theis+samatha(fn=0.70))
355 29216 wanxiaxong BER4NEK9M7NA account_tiy5YHD34KpwPzUTXyRVFJ Wanxia Song 2077 fuzzy(sequence: 0.86)
356 29215 bryangoulart DTNQFE4CXSFX account_9sksoKmcJGxqhz4M4aD169 Brian Goulart 2076 fuzzy(last+first: goulart+brian(fn=0.70))
357 29602 tech brussels-crucial-smashing account_jUFnZ52urgAMNgp9XhT4bs Graham McIntire 1 fuzzy(manual_override)
358 28935 chrislewis1 V9DS4E4Y7UTT account_osB3NhyGdoWJRtpL5X2bfD chris lewis 1787 fuzzy(last+first: lewis+chris(fn=1.00))
359 28917 crankkeith kcrank10615 account_iSuzVkNmem52NdXXh1Savu Keith Crank 209 fuzzy(last+first: crank+keith(fn=1.00))
360 28703 grahammcintire2 U5Y3hA3g*14L account_jUFnZ52urgAMNgp9XhT4bs Graham McIntire 1 fuzzy(last+first: mcintire+graham(fn=1.00))
361 28702 chrissyeagle2 NJPCGPBNFB account_3uu7k4euycmKnZii9DQAj2 Chrissy Eagle 74 fuzzy(last+first: eagle+chrissy(fn=1.00))
362 28671 melodymccarty U3WLEGEB account_18HG9hdwwtZM83J9kyGMs6 Melody McCarthy 1397 fuzzy(sequence: 0.93)
363 28642 barbihardin ZENETJ75TEVQ account_bb7iRuPixxutKZxnURfK5U James Hardin 1475 fuzzy(manual_override)
364 28628 jackworthy 2VUR7AAFWZZQ account_hgoipVYXydD1yVSXTD9QGP John Worthy 1461 fuzzy(last+first: worthy+john(fn=0.70))
365 28542 michaelray2 jjPgDcotL6rtY account_8Rh7edwTjNBYw9FJSq4WRT Michael Ray 379 fuzzy(last+first: ray+michael(fn=1.00))
366 28458 kennethcampbell2 V5TKW8L6RXFF account_jF69QnP5j9FtfLgdAiUKfE Kenneth D. Campbell 213 fuzzy(last+first: campbell+kenneth d(fn=1.00))
367 28296 cinthiapereira1 9WSGXM9LBXGZ account_57mDq22QWRV8tdPvk74Uvj Cinthia Pereira 1102 fuzzy(last+first: pereira+cinthia(fn=1.00))
368 28107 jacquelinewilder HEY36LUGYCUQ account_hHa9Z64B9okZ3yccQP9WpR Jacquelin Wilder 892 fuzzy(last+first: wilder+jacquelin(fn=1.00))
369 27939 reynamoore R92nbskba7iM account_k8gD8YGUQzwWCE7JT9EfYc Rebekah Moore 386 fuzzy(last+first: moore+rebekah(fn=0.70))
370 27912 blancadiaz B548gdu55D account_ktdNSpKRV3ja7RMi7pZRBB Balnca Diaz 433 fuzzy(last+first: diaz+balnca(fn=0.70))
371 27838 consueloluna1830 C8736gfd0 account_jw16PcKtprHpPkSAEhvsaN Consuelo Luna 81 fuzzy(last+first: luna+consuelo(fn=1.00))
372 27781 pambanschbach Q2p68#*9vwF2 account_6hGXtJ7RV7mqZYfd5AyE1f Pam Bansbach 383 fuzzy(sequence: 0.88)
373 27639 choonpang CP2143855677cp account_o7aKk2FGKJAcmrXxMfUCpX Geok Choon Pang 146 fuzzy(last+first: pang+geok choon(fn=1.00))
374 27618 tammieventris DR45QC74M account_fMQ6qiwkPFWP8a86exFw4A Terry Ventris 340 fuzzy(last+first: ventris+terry(fn=0.70))
375 27579 donnance 9727346814 account_s9ZW1FpS3wkDoh31CjU1Zw Dana Nance 89 fuzzy(last+first: nance+dana(fn=0.70))
376 27501 jonnytaylor Zb6677&4!yky account_1fDruGS6CFTBUCGuzVZYTG Johnny Taylor 190 fuzzy(last+first: taylor+johnny(fn=0.70))
377 27436 clayrobertson2 C2147288546R account_jgtX7TysTqcobVVBtSZEc2 Clay Robertson 77 fuzzy(last+first: robertson+clay(fn=1.00))
378 27417 donniecampbell 6a2671bc0c1 account_4ww28eUsFRv8Yh5RkWe9cz Danny Campbell 92 fuzzy(last+first: campbell+danny(fn=0.70))
379 27418 alanwyatt i1412r53MYr account_2cnVCpSsYocCYpMEwwBBrY Allen Wyatt 27 fuzzy(last+first: wyatt+allen(fn=0.70))
380 29620 sayedahaque YR4Sytbu account_4hoTCjwTatthCg7zp2Cnfe Syeda Haque 10048 fuzzy(last+first: haque+syeda(fn=0.70))
381 29554 marcuspatton GEUXX27VLZXK account_dxY6AZnLdFUWup8Sf6XER1 Marcus Patton 2458 ambiguous_resolved(lowest_id)
382 29606 mariacastanon iu297gkid9 account_qh8bFPvWzyAqHqwHMDkThW Maria Castanon 10007 ambiguous_resolved(lowest_id)
383 28800 kennethcampbell !9727342446! account_jF69QnP5j9FtfLgdAiUKfE Kenneth D. Campbell 213 ambiguous_resolved(lowest_id)
384 28358 stephenday MSVGQ8X38YLN account_j1UFX8wqzorqSetUje5dTJ Stephen Day 1168 ambiguous_resolved(lowest_id)
385 27404 grahammcintire U5Y3hA3g*14L account_jUFnZ52urgAMNgp9XhT4bs Graham McIntire 1 ambiguous_resolved(lowest_id)
386 29630 sriahobilamuth RTCESW24 account_7PgSr8dVuafybJdrXND3V9 Sri Ahobilamuth 113 ambiguous_resolved(lowest_id)
387 29524 chandparvathaneni TK4QA3XM5XK5 still_unmatched(best: , score: 0.0)
388 29358 radiantlifeministries 12FDIUNF5L7vM still_unmatched(best: , score: 0.0)
389 29296 midvalleyconcrete BD58JYT2PJ6A still_unmatched(best: , score: 0.0)
390 29244 janiscable JHC7U554JBTR still_unmatched(best: last+weakfirst: cable+gary w(fn=0.11), score: 0.611)
391 29150 donwilhelmus 2V4NEL87RM39 still_unmatched(best: , score: 0.0)
392 29135 kavalleriefarm 8DFNNTPBPZGY still_unmatched(best: , score: 0.0)
393 29077 kellygarza 4696591662 still_unmatched(best: last+weakfirst: garza+esparanza(fn=0.42), score: 0.642)
394 29070 1stchoiceliving YVWJWRPEVYNH still_unmatched(best: , score: 0.0)
395 28879 kimberlyrichards 7PQA6J8RPZ9Z still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)
396 28599 ryanrodriguez 9DFYWJ47YWUP still_unmatched(best: last+weakfirst: rodriguez+mike(fn=0.19), score: 0.619)
397 28460 saidaacosta V29897X4 still_unmatched(best: last+weakfirst: acosta+alma(fn=0.21), score: 0.621)
398 28422 jackiehendricks UQR5HGFCJ4XX still_unmatched(best: last+weakfirst: hendricks+sue(fn=0.09), score: 0.609)
399 28400 bonniestone BH8BV44TTWUU still_unmatched(best: , score: 0.0)
400 28336 buddyswan N7XUYUD7J9SD still_unmatched(best: , score: 0.0)
401 28268 alberto TICOGUILLO still_unmatched(best: , score: 0.0)
402 28213 krisdougherty RWEH75UJ38 still_unmatched(best: , score: 0.0)
403 27955 evlynrichter FRQD738BGRC still_unmatched(best: last+weakfirst: richter+brent(fn=0.19), score: 0.619)
404 27793 branchrvpark XfKxVZIKmm still_unmatched(best: , score: 0.0)
405 29601 kimberlyrichards2 K84f7r10GT108 still_unmatched(best: last+weakfirst: kim+tae(fn=0.08), score: 0.608)
406 27755 gordonhamilton c3495Rpuq5Sh still_unmatched(best: last+weakfirst: hamilton+sheryl f(fn=0.16), score: 0.616)
407 27709 sensibleheatsystems SHQWERY23 still_unmatched(best: , score: 0.0)
408 27704 lambandlion LL9727363567ll still_unmatched(best: , score: 0.0)
409 27625 mattbud kjklfda79sd still_unmatched(best: , score: 0.0)
410 27621 richardhill RH9729788124rh no_match
411 27489 jenniferreed 683P7V7R still_unmatched(best: , score: 0.0)
412 27482 irvinbarojas yDeEJzYY08 still_unmatched(best: , score: 0.0)
413 27463 dennispereyra 82s62kd9XHhd still_unmatched(best: , score: 0.0)
414 27441 samanthasmith 22jmR9X#93j5 still_unmatched(best: last+weakfirst: smith+kathy(fn=0.27), score: 0.627)
415 27408 penneywarner b4h53N6c#5wo still_unmatched(best: , score: 0.0)
416 29626 karengreen UTKCYI674 still_unmatched(best: last+weakfirst: green+dominique(fn=0.17), score: 0.617)

View file

@ -0,0 +1,68 @@
radius_id,username,password,gaiia_id,gaiia_name,gaiia_readable_id,match_type
29554,marcuspatton,GEUXX27VLZXK,,"AMBIGUOUS: Marcus Patton, Marcus Patton",,ambiguous
29524,chandparvathaneni,TK4QA3XM5XK5,,,,no_match
29521,tiffanyyarley-olenak,98HTZ9Q8FARG,,,,no_match
29606,mariacastanon,iu297gkid9,,"AMBIGUOUS: Maria Castanon, Maria Castanon",,ambiguous
29395,vanessagomez1,J5JEW3HEBVAF,,,,no_match
29611,brittannieredden1,TYFTUEZ7429,,,,no_match
29614,samanthatheis,kf6u8djigk,,,,no_match
29358,radiantlifeministries,12FDIUNF5L7vM,,,,no_match
29296,midvalleyconcrete,BD58JYT2PJ6A,,,,no_match
29244,janiscable,JHC7U554JBTR,,,,no_match
29216,wanxiaxong,BER4NEK9M7NA,,,,no_match
29215,bryangoulart,DTNQFE4CXSFX,,,,no_match
29150,donwilhelmus,2V4NEL87RM39,,,,no_match
29135,kavalleriefarm,8DFNNTPBPZGY,,,,no_match
29077,kellygarza,4696591662,,,,no_match
29070,1stchoiceliving,YVWJWRPEVYNH,,,,no_match
29602,tech,brussels-crucial-smashing,,,,no_match
28935,chrislewis1,V9DS4E4Y7UTT,,,,no_match
28917,crankkeith,kcrank10615,,,,no_match
28879,kimberlyrichards,7PQA6J8RPZ9Z,,,,no_match
28800,kennethcampbell,!9727342446!,,"AMBIGUOUS: Kenneth D. Campbell, Kenneth Campbell",,ambiguous
28703,grahammcintire2,U5Y3hA3g*14L,,,,no_match
28702,chrissyeagle2,NJPCGPBNFB,,,,no_match
28671,melodymccarty,U3WLEGEB,,,,no_match
28642,barbihardin,ZENETJ75TEVQ,,,,no_match
28628,jackworthy,2VUR7AAFWZZQ,,,,no_match
28599,ryanrodriguez,9DFYWJ47YWUP,,,,no_match
28542,michaelray2,jjPgDcotL6rtY,,,,no_match
28460,saidaacosta,V29897X4,,,,no_match
28458,kennethcampbell2,V5TKW8L6RXFF,,,,no_match
28422,jackiehendricks,UQR5HGFCJ4XX,,,,no_match
28400,bonniestone,BH8BV44TTWUU,,,,no_match
28358,stephenday,MSVGQ8X38YLN,,"AMBIGUOUS: Stephen Day, Stephen Day",,ambiguous
28336,buddyswan,N7XUYUD7J9SD,,,,no_match
28296,cinthiapereira1,9WSGXM9LBXGZ,,,,no_match
28268,alberto,TICOGUILLO,,,,no_match
29531,daycor,U45YBPSARH7N,,,,no_match
29630,sriahobilamuth,RTCESW24,,"AMBIGUOUS: Sri Ahobilamuth, Sri Ahobilamuth",,ambiguous
27408,penneywarner,b4h53N6c#5wo,,,,no_match
29620,sayedahaque,YR4Sytbu,,,,no_match
29626,karengreen,UTKCYI674,,,,no_match
27404,grahammcintire,U5Y3hA3g*14L,,"AMBIGUOUS: Graham McIntire, Graham McIntire, Graham McIntire",,ambiguous
27417,donniecampbell,6a2671bc0c1,,,,no_match
27418,alanwyatt,i1412r53MYr,,,,no_match
27441,samanthasmith,22jmR9X#93j5,,,,no_match
27436,clayrobertson2,C2147288546R,,,,no_match
27489,jenniferreed,683P7V7R,,,,no_match
27501,jonnytaylor,Zb6677&4!yky,,,,no_match
27482,irvinbarojas,yDeEJzYY08,,,,no_match
27463,dennispereyra,82s62kd9XHhd,,,,no_match
27579,donnance,9727346814,,,,no_match
27838,consueloluna1830,C8736gfd0,,,,no_match
27912,blancadiaz,B548gdu55D,,,,no_match
27709,sensibleheatsystems,SHQWERY23,,,,no_match
27704,lambandlion,LL9727363567ll,,,,no_match
27625,mattbud,kjklfda79sd,,,,no_match
27639,choonpang,CP2143855677cp,,,,no_match
127621,richardhill,RH9729788124rh,,,,no_match
27618,tammieventris,DR45QC74M,,,,no_match
27955,evlynrichter,FRQD738BGRC,,,,no_match
27939,reynamoore,R92nbskba7iM,,,,no_match
27755,gordonhamilton,c3495Rpuq5Sh,,,,no_match
27793,branchrvpark,XfKxVZIKmm,,,,no_match
27781,pambanschbach,Q2p68#*9vwF2,,,,no_match
29601,kimberlyrichards2,K84f7r10GT108,,,,no_match
28213,krisdougherty,RWEH75UJ38,,,,no_match
28107,jacquelinewilder,HEY36LUGYCUQ,,,,no_match
1 radius_id username password gaiia_id gaiia_name gaiia_readable_id match_type
2 29554 marcuspatton GEUXX27VLZXK AMBIGUOUS: Marcus Patton, Marcus Patton ambiguous
3 29524 chandparvathaneni TK4QA3XM5XK5 no_match
4 29521 tiffanyyarley-olenak 98HTZ9Q8FARG no_match
5 29606 mariacastanon iu297gkid9 AMBIGUOUS: Maria Castanon, Maria Castanon ambiguous
6 29395 vanessagomez1 J5JEW3HEBVAF no_match
7 29611 brittannieredden1 TYFTUEZ7429 no_match
8 29614 samanthatheis kf6u8djigk no_match
9 29358 radiantlifeministries 12FDIUNF5L7vM no_match
10 29296 midvalleyconcrete BD58JYT2PJ6A no_match
11 29244 janiscable JHC7U554JBTR no_match
12 29216 wanxiaxong BER4NEK9M7NA no_match
13 29215 bryangoulart DTNQFE4CXSFX no_match
14 29150 donwilhelmus 2V4NEL87RM39 no_match
15 29135 kavalleriefarm 8DFNNTPBPZGY no_match
16 29077 kellygarza 4696591662 no_match
17 29070 1stchoiceliving YVWJWRPEVYNH no_match
18 29602 tech brussels-crucial-smashing no_match
19 28935 chrislewis1 V9DS4E4Y7UTT no_match
20 28917 crankkeith kcrank10615 no_match
21 28879 kimberlyrichards 7PQA6J8RPZ9Z no_match
22 28800 kennethcampbell !9727342446! AMBIGUOUS: Kenneth D. Campbell, Kenneth Campbell ambiguous
23 28703 grahammcintire2 U5Y3hA3g*14L no_match
24 28702 chrissyeagle2 NJPCGPBNFB no_match
25 28671 melodymccarty U3WLEGEB no_match
26 28642 barbihardin ZENETJ75TEVQ no_match
27 28628 jackworthy 2VUR7AAFWZZQ no_match
28 28599 ryanrodriguez 9DFYWJ47YWUP no_match
29 28542 michaelray2 jjPgDcotL6rtY no_match
30 28460 saidaacosta V29897X4 no_match
31 28458 kennethcampbell2 V5TKW8L6RXFF no_match
32 28422 jackiehendricks UQR5HGFCJ4XX no_match
33 28400 bonniestone BH8BV44TTWUU no_match
34 28358 stephenday MSVGQ8X38YLN AMBIGUOUS: Stephen Day, Stephen Day ambiguous
35 28336 buddyswan N7XUYUD7J9SD no_match
36 28296 cinthiapereira1 9WSGXM9LBXGZ no_match
37 28268 alberto TICOGUILLO no_match
38 29531 daycor U45YBPSARH7N no_match
39 29630 sriahobilamuth RTCESW24 AMBIGUOUS: Sri Ahobilamuth, Sri Ahobilamuth ambiguous
40 27408 penneywarner b4h53N6c#5wo no_match
41 29620 sayedahaque YR4Sytbu no_match
42 29626 karengreen UTKCYI674 no_match
43 27404 grahammcintire U5Y3hA3g*14L AMBIGUOUS: Graham McIntire, Graham McIntire, Graham McIntire ambiguous
44 27417 donniecampbell 6a2671bc0c1 no_match
45 27418 alanwyatt i1412r53MYr no_match
46 27441 samanthasmith 22jmR9X#93j5 no_match
47 27436 clayrobertson2 C2147288546R no_match
48 27489 jenniferreed 683P7V7R no_match
49 27501 jonnytaylor Zb6677&4!yky no_match
50 27482 irvinbarojas yDeEJzYY08 no_match
51 27463 dennispereyra 82s62kd9XHhd no_match
52 27579 donnance 9727346814 no_match
53 27838 consueloluna1830 C8736gfd0 no_match
54 27912 blancadiaz B548gdu55D no_match
55 27709 sensibleheatsystems SHQWERY23 no_match
56 27704 lambandlion LL9727363567ll no_match
57 27625 mattbud kjklfda79sd no_match
58 27639 choonpang CP2143855677cp no_match
59 127621 richardhill RH9729788124rh no_match
60 27618 tammieventris DR45QC74M no_match
61 27955 evlynrichter FRQD738BGRC no_match
62 27939 reynamoore R92nbskba7iM no_match
63 27755 gordonhamilton c3495Rpuq5Sh no_match
64 27793 branchrvpark XfKxVZIKmm no_match
65 27781 pambanschbach Q2p68#*9vwF2 no_match
66 29601 kimberlyrichards2 K84f7r10GT108 no_match
67 28213 krisdougherty RWEH75UJ38 no_match
68 28107 jacquelinewilder HEY36LUGYCUQ no_match

2687
gaiia/radius_usernames.json Normal file

File diff suppressed because it is too large Load diff

415
gaiia/radius_users.txt Normal file
View file

@ -0,0 +1,415 @@
| 29581 | joseortiz | Cleartext-Password | := | J2WEYT7782UJ |
| 29578 | perlachavez2 | Cleartext-Password | := | U4BBTBMCJZST |
| 29575 | thomashernandez | Cleartext-Password | := | X6BZLA7SC3XM |
| 29572 | williamarmstead | Cleartext-Password | := | SAQ4XEKXDC58 |
| 29570 | julierose | Cleartext-Password | := | MLM8R2WAGY52 |
| 29571 | derrickmccausland | Cleartext-Password | := | ETNLKZHACGF7 |
| 29567 | sherrifredrick | Cleartext-Password | := | 7P52BVMTSNN6 |
| 29565 | shelbyburris | Cleartext-Password | := | 3YAL9DWFZUPT |
| 29564 | priscillacrenshaw | Cleartext-Password | := | AXBDLXSGXMLR |
| 29561 | korybiggs | Cleartext-Password | := | 4FJC7DXAW5RV |
| 29563 | kristinjimenez | Cleartext-Password | := | 9FAYXMRDU69T |
| 29558 | jessicaweddle | Cleartext-Password | := | VV96G36AEEDA |
| 29557 | yolandamedrano | Cleartext-Password | := | R948UL4KZRA4 |
| 29555 | williambowland | Cleartext-Password | := | K7HYNWGE25XX |
| 29554 | marcuspatton | Cleartext-Password | := | GEUXX27VLZXK |
| 29553 | shacaciabillings | Cleartext-Password | := | 8AZLJCDFYFYV |
| 29552 | guadalupelugo | Cleartext-Password | := | DQG8XCMFMVH5 |
| 29551 | brandonhardesty | Cleartext-Password | := | KMRHVPEXBEZ8 |
| 29550 | nancyavellaneda | Cleartext-Password | := | CXATYV7NGZN2 |
| 29549 | matthewgoodwin | Cleartext-Password | := | VSPM7N9UNU6Y |
| 29545 | donmckinney | Cleartext-Password | := | C6AQ6UVD6R7R |
| 29544 | natashaelmore | Cleartext-Password | := | 8G5QJ8R7MK8Q |
| 29543 | shamsbashir | Cleartext-Password | := | WLBGY4JMN4VA |
| 29541 | douglasglover | Cleartext-Password | := | 9Y6H9X3HCG3D |
| 29538 | carolstrickland | Cleartext-Password | := | 547P6AQ8RCD4 |
| 29537 | carmenlopez | Cleartext-Password | := | TPLND3H2FWTQ |
| 29534 | johnlooper | Cleartext-Password | := | ZPL8LRY2TGRH |
| 29535 | davidroesch | Cleartext-Password | := | ZHB2BS968DTU |
| 29531 | daycor | Cleartext-Password | := | U45YBPSARH7N |
| 29530 | derekrodriguez | Cleartext-Password | := | AP7Z594DMR7E |
| 29524 | chandparvathaneni | Cleartext-Password | := | TK4QA3XM5XK5 |
| 29522 | joshuasoliz | Cleartext-Password | := | PE7MHRZWUEQM |
| 29521 | tiffanyyarley-olenak | Cleartext-Password | := | 98HTZ9Q8FARG |
| 29514 | ericbarrett | Cleartext-Password | := | 5S2CV9FEFMKG |
| 29513 | marysmelser | Cleartext-Password | := | ZS74NS5BBPX3 |
| 29506 | nicolemaenn | Cleartext-Password | := | B7KS27WL5WSN |
| 29503 | yolandajudge | Cleartext-Password | := | 6V8RLNK75LM8 |
| 29501 | craighovde | Cleartext-Password | := | AVZXWBX8GQTC |
| 29498 | joselucas | Cleartext-Password | := | ZHMW9TE8QHRX |
| 29488 | douggarber | Cleartext-Password | := | WXP9J5SRJWEJ |
| 29482 | carmencleghorn | Cleartext-Password | := | VH3CBTJZAJ2Y |
| 29472 | rubenreyez | Cleartext-Password | := | 66384Y7B4WR4 |
| 29464 | tiffanystewart | Cleartext-Password | := | 3EGVCS6RMF3Z |
| 29449 | dejadodson | Cleartext-Password | := | FLR4DJHPX6TG |
| 29447 | coreyball | Cleartext-Password | := | 3JAUWZNAR9MB |
| 29442 | toniyoung | Cleartext-Password | := | RJ24VTRM7JA9 |
| 29434 | melanieweddle | Cleartext-Password | := | XQ5L7PUM9RP6 |
| 29431 | zackknuckey | Cleartext-Password | := | VP5CCX8LTE47 |
| 29425 | christinehamparyan | Cleartext-Password | := | WEYX9R5P7J5L |
| 29422 | joespeciale | Cleartext-Password | := | UH6ZCM9WLGRQ |
| 29420 | rogerbatt | Cleartext-Password | := | KGDJZ7V66QQK |
| 29419 | kalebjames | Cleartext-Password | := | KMQDGPD5XB6Z |
| 29416 | stevemolina | Cleartext-Password | := | BLLHWPY9RCTC |
| 29414 | mariatrejo | Cleartext-Password | := | EYPWCQKKWYZH |
| 29410 | kenhall | Cleartext-Password | := | LX897BGJNMK5 |
| 29407 | deewilliams | Cleartext-Password | := | R8AGX3E8WAHN |
| 29406 | johnvayo | Cleartext-Password | := | DJJNUEEHGAW5 |
| 29398 | anthonythompson | Cleartext-Password | := | WB4E4MPJVHTB |
| 29608 | stevecombest | ClearText-Password | := | STVN23552Wt |
| 29606 | mariacastanon | ClearText-Password | := | iu297gkid9 |
| 29395 | vanessagomez1 | Cleartext-Password | := | J5JEW3HEBVAF |
| 29607 | luisvelira | ClearText-Password | := | uygo875491! |
| 29609 | keithtucker | ClearText-Password | := | 14FYJSNUU48 |
| 29610 | angelatrevino | ClearText-Password | := | KJGU39565 |
| 29611 | brittannieredden1 | ClearText-Password | := | TYFTUEZ7429 |
| 29612 | amymontelongo | ClearText-Password | := | ig76f7fuvu |
| 29613 | michaelasmith | ClearText-Password | := | MS21353GHY |
| 29614 | samanthatheis | ClearText-Password | := | kf6u8djigk |
| 29615 | janisstewart | ClearText-Password | := | JD834HCUW1C |
| 29616 | irenegaribay | ClearText-Password | := | uky75dkc |
| 29383 | leonardocoronel | Cleartext-Password | := | TXPC494 |
| 29374 | alejandrolimon | Cleartext-Password | := | MM4BSQS7FRTD |
| 29368 | crystalharney | Cleartext-Password | := | A7W26H834WLJ |
| 29358 | radiantlifeministries | Cleartext-Password | := | 12FDIUNF5L7vM |
| 29355 | olgagutierrez | Cleartext-Password | := | WERWRMUDBZ85 |
| 29353 | greghummel | Cleartext-Password | := | JV84HSVAK6XR |
| 29349 | bettymitchell | Cleartext-Password | := | 8CHHCNBWQLRV |
| 29341 | wandadavis | Cleartext-Password | := | JXQXXZ9MKFX6 |
| 29338 | scottanderson | Cleartext-Password | := | RJ49A43SG3RA |
| 29335 | jenniferboon | Cleartext-Password | := | 28ESU8ELVELP |
| 29331 | marialopez | Cleartext-Password | := | P3FNSPT7MEYM |
| 29329 | cynthiasandlin | Cleartext-Password | := | MCDTLMVFQXAV |
| 29328 | bethanybell | Cleartext-Password | := | 3UUC5X27PETS |
| 29323 | garrettojeda | Cleartext-Password | := | U5PRQYZE2MM3 |
| 29325 | rayschaak | Cleartext-Password | := | XSNLP62EK6VZ |
| 29327 | carlaswearingen | Cleartext-Password | := | E6DQ4B2T9JZD |
| 29320 | lesliebewley | Cleartext-Password | := | 55KFSJMEDYLK |
| 29317 | delainawaite | Cleartext-Password | := | EM7TWHUS4SYU |
| 29307 | esparanzagarza | Cleartext-Password | := | VLL9ZNPRSTG5 |
| 29296 | midvalleyconcrete | Cleartext-Password | := | BD58JYT2PJ6A |
| 29273 | glendabeauchamp | Cleartext-Password | := | RKA3J8WSPYJP |
| 29256 | glindarichter | Cleartext-Password | := | VXMLMDAFLNJ3 |
| 29251 | nhumorrison | Cleartext-Password | := | EC32VDFKB9CB |
| 29245 | brittanyworley | Cleartext-Password | := | NBKYERRBSBH4 |
| 29244 | janiscable | Cleartext-Password | := | JHC7U554JBTR |
| 29243 | dustynfisher | Cleartext-Password | := | XASFU3DBPZ5M |
| 29237 | stephenbeegle | Cleartext-Password | := | RU5GZVTVACRC |
| 29229 | roseanndellavalle | Cleartext-Password | := | 5GAF6JHUWWYP |
| 29231 | duanewright | Cleartext-Password | := | VXK4FBLVFE7F |
| 29222 | pablohernandez | Cleartext-Password | := | P3YB6A4JGEDF |
| 29217 | pavelavanesov | Cleartext-Password | := | KP8ZSGC2DG2U |
| 29216 | wanxiaxong | Cleartext-Password | := | BER4NEK9M7NA |
| 29215 | bryangoulart | Cleartext-Password | := | DTNQFE4CXSFX |
| 29204 | sandrahoughton | Cleartext-Password | := | 5XR4QMPBXVWY |
| 29203 | adamstaeben | Cleartext-Password | := | GP7RANJ8UCYP |
| 29190 | russmott | Cleartext-Password | := | J8ZN888UQSLR |
| 29189 | karenstewart | Cleartext-Password | := | JSKTFAJ2JYDX |
| 29185 | heatherwomack | Cleartext-Password | := | 89W7S5Q7UAHP |
| 29183 | wyndijames | Cleartext-Password | := | CPSDNRQU95HJ |
| 29182 | angelicacuayahuitl | Cleartext-Password | := | VK8PZW3LK5F3 |
| 29181 | chasewilliams | Cleartext-Password | := | W6AMPQEPY3BC |
| 29176 | kevinarana | Cleartext-Password | := | JAJYSEG75UZS |
| 29166 | chrisclayton | Cleartext-Password | := | BFJ462PLHJFV |
| 29162 | billyhorton | Cleartext-Password | := | MPTX573ZUYYG |
| 29151 | chisediquezada | Cleartext-Password | := | RJBYQ2NUB829 |
| 29150 | donwilhelmus | Cleartext-Password | := | 2V4NEL87RM39 |
| 29147 | angelmerlo | Cleartext-Password | := | EWWAPXN9Z2YS |
| 29135 | kavalleriefarm | Cleartext-Password | := | 8DFNNTPBPZGY |
| 29113 | kerryblack | Cleartext-Password | := | 9D5S9ZD2FCBV |
| 29110 | cliffordjennings | Cleartext-Password | := | 5GC7M65H9VGT |
| 29098 | carlakimberling | Cleartext-Password | := | SS4KRJYKTV7B |
| 29088 | richardconklin | Cleartext-Password | := | WEUNG5H3K8BB |
| 29089 | leandrabeshea | Cleartext-Password | := | 7V8BZU4G83VF |
| 29077 | kellygarza | Cleartext-Password | := | 4696591662 |
| 29070 | 1stchoiceliving | Cleartext-Password | := | YVWJWRPEVYNH |
| 29067 | robertfinch | Cleartext-Password | := | D7FMTT455GBB |
| 29058 | ericcoffman | Cleartext-Password | := | MMEBXQYLZWNU |
| 29050 | nathanaelfinch | Cleartext-Password | := | JVNC68FV7N6B |
| 29047 | gabrielasereno | Cleartext-Password | := | KCH82WCRA2YR |
| 29045 | georginacovarrubias | Cleartext-Password | := | 8QT57NUV8C7R |
| 29035 | thomascline | Cleartext-Password | := | E523BKVU5MJP |
| 29034 | susanlewis | Cleartext-Password | := | R7TZFZEGLNRW |
| 29025 | tammywelborn | Cleartext-Password | := | RMC482WNBS7 |
| 29023 | haleymuela | Cleartext-Password | := | BS7X3DTKW8TH |
| 29018 | christelles | Cleartext-Password | := | WL2TKLFG8G2H |
| 29017 | mauricioantonio | Cleartext-Password | := | 6QJW7RAPDBWB |
| 29013 | vincentlimon | Cleartext-Password | := | YDDXKFABUZY7 |
| 29602 | tech | ClearText-Password | := | brussels-crucial-smashing |
| 29634 | jeffreyharder | ClearText-Password | := | YUV565D67RT |
| 29635 | pablomartinez | ClearText-Password | := | 2766581BER |
| 29636 | krystalmendez | ClearText-Password | := | 6FTCKU6S56 |
| 29637 | davidvandagriff | Cleartext-Password | := | UTRC53865F9 |
| 29638 | carolynwalker | Cleartext-Password | := | trjxtyx75! |
| 28979 | claygilbert | Cleartext-Password | := | 3E6XFDHXCAG7 |
| 28978 | jmichaelculverhouse | Cleartext-Password | := | 2147099521 |
| 28971 | diegosereno | Cleartext-Password | := | 2CE9F5YSPQHX |
| 28970 | ashleysmith | Cleartext-Password | := | F8D3FVCZB7PD |
| 28954 | dougstowe | Cleartext-Password | := | 5D35LFD6GEAZ |
| 28936 | timbronschidle | ClearText-Password | := | 3ZVLCWSAE3EW1 |
| 28935 | chrislewis1 | Cleartext-Password | := | V9DS4E4Y7UTT |
| 28928 | cynthiajones | Cleartext-Password | := | KC7ESYHEZGNR |
| 28917 | crankkeith | Cleartext-Password | := | kcrank10615 |
| 28879 | kimberlyrichards | Cleartext-Password | := | 7PQA6J8RPZ9Z |
| 28867 | bobbymack | Cleartext-Password | := | UDUBTCXXF8VV |
| 28848 | americatrejo | Cleartext-Password | := | 3B63TLTT9WE4 |
| 28832 | tjbanschbach | Cleartext-Password | := | 8WTWTXACGG5N |
| 28811 | luiscabrera | Cleartext-Password | := | RWUQ94XT9A9Z |
| 28810 | rosahernandez | Cleartext-Password | := | JSGUXKWEMSVN |
| 28807 | carlosgomez | Cleartext-Password | := | J22RS7F3LGG2 |
| 28800 | kennethcampbell | Cleartext-Password | := | !9727342446! |
| 28763 | jeridrutledge | Cleartext-Password | := | GJ3CZTRJJW2M |
| 28748 | timthomas | Cleartext-Password | := | NY223RQJD7PN |
| 28715 | carolbates | Cleartext-Password | := | J3UBUCMYDX64 |
| 28710 | shirleyelmore | Cleartext-Password | := | D8Y64N9L4R9W |
| 28703 | grahammcintire2 | Cleartext-Password | := | U5Y3hA3g*14L |
| 28702 | chrissyeagle2 | Cleartext-Password | := | NJPCGPBNFB |
| 28698 | swedlund | Cleartext-Password | := | swedlund494 |
| 28695 | wendysanders | Cleartext-Password | := | RYG9L43NWDZV |
| 28693 | robbymccollom | Cleartext-Password | := | RMC482WNBS7 |
| 28671 | melodymccarty | Cleartext-Password | := | U3WLEGEB |
| 28664 | heatherbrister2 | Cleartext-Password | := | KK37V8W9ZD9Z |
| 28663 | ryanlloyd | Cleartext-Password | := | HWEMKNWX55RF |
| 28661 | amyrodriguez | Cleartext-Password | := | WX3QRT2U6L4X |
| 28660 | thecountrycutter | Cleartext-Password | := | YXpScFIneePm |
| 28659 | bradwilson | Cleartext-Password | := | ZFAXA9AK63AH |
| 28654 | edrater | Cleartext-Password | := | FRY4823N |
| 28642 | barbihardin | Cleartext-Password | := | ZENETJ75TEVQ |
| 28637 | vikyacosta | Cleartext-Password | := | Z4SMV49SAH72 |
| 28633 | terrimack | Cleartext-Password | := | B9HNTSN7ASQ4 |
| 28628 | jackworthy | Cleartext-Password | := | 2VUR7AAFWZZQ |
| 28617 | luisarellano | Cleartext-Password | := | GVNWBQMM3S7U |
| 28609 | dorisavalos | Cleartext-Password | := | DLKMJS88AF2W |
| 28599 | ryanrodriguez | Cleartext-Password | := | 9DFYWJ47YWUP |
| 28597 | jackierollier | Cleartext-Password | := | TSFNN8DVDSPR |
| 28588 | charlesmccreary | Cleartext-Password | := | 88J6XS6TXRMA |
| 28548 | jamessmith | Cleartext-Password | := | LRVU6RVG5SP8 |
| 28546 | davidwhitefoot | Cleartext-Password | := | PYHTZMHJU8R8- |
| 28542 | michaelray2 | Cleartext-Password | := | jjPgDcotL6rtY |
| 28536 | patriciavasquez | Cleartext-Password | := | P88256487V |
| 28535 | adrianmcdaniel | Cleartext-Password | := | U4TGDH2PSAB4 |
| 28526 | justinvoga | Cleartext-Password | := | URHVCX7SFSFU |
| 28518 | jenniferlittle | Cleartext-Password | := | 8EAUUDQPXSQX |
| 28510 | bradslate | Cleartext-Password | := | AF4KVQ2P44M7 |
| 28494 | irisdelira | Cleartext-Password | := | L6XGQA583DG6 |
| 28491 | richardbarragan | Cleartext-Password | := | WJBS86MMJBFH |
| 28476 | jensenhall | Cleartext-Password | := | F8XM9U8SC6LL |
| 28466 | paulcomella | Cleartext-Password | := | YTZDMBZCMPX4 |
| 28460 | saidaacosta | Cleartext-Password | := | V29897X4 |
| 28458 | kennethcampbell2 | Cleartext-Password | := | V5TKW8L6RXFF |
| 28449 | amberkrings | Cleartext-Password | := | 5NWBR3JLV6N8 |
| 28438 | mauriciosoto | Cleartext-Password | := | DJ7YP7W7AA8M |
| 28435 | jimcrank | Cleartext-Password | := | PLA7XNX2F4Y3 |
| 28425 | summerbeazley | Cleartext-Password | := | D4S4YRB8Y3KA |
| 28426 | davidarmstrong | Cleartext-Password | := | MPEZGG53KQSZ |
| 28422 | jackiehendricks | Cleartext-Password | := | UQR5HGFCJ4XX |
| 28400 | bonniestone | Cleartext-Password | := | BH8BV44TTWUU |
| 28395 | stevenspurgers | Cleartext-Password | := | YS5Q785J2HGZ |
| 28388 | connercoleman | Cleartext-Password | := | 6108RANDOMREDDOG |
| 28367 | judithfierro | Cleartext-Password | := | FUZ33LB6SFFC |
| 28359 | tonyasipes | Cleartext-Password | := | N5VEZR8Q5M86 |
| 28358 | stephenday | Cleartext-Password | := | MSVGQ8X38YLN |
| 28347 | kathysmith | Cleartext-Password | := | SJ53PSYCZLPR |
| 28344 | cherieeshelman | Cleartext-Password | := | BRUV99PN6Y7L |
| 28336 | buddyswan | Cleartext-Password | := | N7XUYUD7J9SD |
| 28296 | cinthiapereira1 | Cleartext-Password | := | 9WSGXM9LBXGZ |
| 28289 | ronberger | Cleartext-Password | := | Ronb499H7ALW |
| 28268 | alberto | Cleartext-Password | := | TICOGUILLO |
| 28267 | sandracrouch | Cleartext-Password | := | V4N3764R5J |
| 28263 | abbieandrews | Cleartext-Password | := | YQSJ499H7ALW |
| 28253 | perlachavez | Cleartext-Password | := | ZWR9PXVFFYBS |
| 28246 | susanking | Cleartext-Password | := | N2KUYLPDNXJP |
| 28241 | terrybachran | Cleartext-Password | := | NG9VMH25LCTC |
| 28240 | lanaygaunce | Cleartext-Password | := | MJCNZAJHK9H3 |
| 28213 | krisdougherty | Cleartext-Password | := | RWEH75UJ38 |
| 28190 | heatherbrister | Cleartext-Password | := | 6XPLC545G6HW |
| 28184 | jenniferdunaway | Cleartext-Password | := | 343V5CT2R7HZ |
| 28159 | elviraquezada | Cleartext-Password | := | HXAS29VRX33V |
| 28154 | jordainpatton | Cleartext-Password | := | J8P4TPF7QS2A |
| 28140 | mattkosarek | Cleartext-Password | := | RJ4BQUZJY |
| 28117 | shannonclark | Cleartext-Password | := | F5XESMCJ |
| 28107 | jacquelinewilder | Cleartext-Password | := | HEY36LUGYCUQ |
| 28098 | kevincostanzo | Cleartext-Password | := | 244VGMVMH66D |
| 28093 | kenpatrick | Cleartext-Password | := | V7U3XBAPTSV3 |
| 28083 | briangallimore | Cleartext-Password | := | 39V285NX |
| 28072 | amberprater | Cleartext-Password | := | RP42B7AYDLAP |
| 28068 | marilynfowler | Cleartext-Password | := | MarilynVG72P8H |
| 28065 | douglaswilson | Cleartext-Password | := | HP97HG9XPYC3 |
| 28034 | terrygortney | Cleartext-Password | := | GQUGR3VJUXBR |
| 28031 | gizellebutler | Cleartext-Password | := | HMZTX66RTPVX |
| 28029 | bradgurney | Cleartext-Password | := | 7A6LRZWT82DX |
| 28024 | darrylchavis | Cleartext-Password | := | TDGUUNXGMYRC |
| 28025 | kathymazzola | Cleartext-Password | := | 5MVS8VUF9QL2 |
| 27994 | austinwatkins | Cleartext-Password | := | S7M4UD8QUTDP |
| 27988 | oscarrodriguez | Cleartext-Password | := | TGPCF3Y2ABXS |
| 27986 | erinthompson | Cleartext-Password | := | 69TQZ797 |
| 27983 | brianhardesty | Cleartext-Password | := | 8D4P34N8 |
| 27976 | cathyday | Cleartext-Password | := | FAXMKENV8FCT |
| 27956 | fredheckel | Cleartext-Password | := | HUVZSE8RGD65 |
| 27955 | evlynrichter | Cleartext-Password | := | FRQD738BGRC |
| 27949 | charlesboone | Cleartext-Password | := | Ccbn78wdfksB |
| 27948 | rachelguzzardi | Cleartext-Password | := | R87wgerf8bsG |
| 27939 | reynamoore | Cleartext-Password | := | R92nbskba7iM |
| 27936 | juanalonzo | Cleartext-Password | := | 745GF0JK5!W |
| 27930 | kristinamurphy | Cleartext-Password | := | oPXFrylqKf |
| 27926 | johnnygoble | Cleartext-Password | := | J829fhf7G |
| 27923 | keithbarber | Cleartext-Password | := | K73bvd54tB |
| 27917 | doreengrubb | Cleartext-Password | := | DOR32SVT1234 |
| 27916 | steveraia | Cleartext-Password | := | DRR648DFTMSE4 |
| 27912 | blancadiaz | Cleartext-Password | := | B548gdu55D |
| 27896 | twcowley | Cleartext-Password | := | TW486TW |
| 27888 | joepatton | Cleartext-Password | := | L7luYzSKSu |
| 27873 | timfisher | Cleartext-Password | := | Thd73gdf5eF |
| 27847 | kileigheven | Cleartext-Password | := | K9872gdf8E |
| 27838 | consueloluna1830 | Cleartext-Password | := | C8736gfd0 |
| 27833 | deannecook | Cleartext-Password | := | Dc872cobnC |
| 27830 | kacyharrison | Cleartext-Password | := | 7Ozr7el6K1 |
| 27822 | milliganwater | Cleartext-Password | := | 8NjGMNsTXd |
| 27813 | michaelnewby | Cleartext-Password | := | TM5M3HU877 |
| 27808 | ladonnaclark | Cleartext-Password | := | YQReVH9men |
| 27800 | jamesmooney | Cleartext-Password | := | QNUqD2tSzq |
| 27793 | branchrvpark | Cleartext-Password | := | XfKxVZIKmm |
| 27783 | tamythomas | Cleartext-Password | := | TT4699520858tt |
| 27781 | pambanschbach | Cleartext-Password | := | Q2p68#*9vwF2 |
| 27780 | josephcruz | Cleartext-Password | := | Z06oxJXrNd |
| 29601 | kimberlyrichards2 | ClearText-Password | := | K84f7r10GT108 |
| 27761 | franceskirby | Cleartext-Password | := | FK8179391152fk |
| 27759 | chadwhitsell | Cleartext-Password | := | 79374B75phGy |
| 27754 | ruthfengler | Cleartext-Password | := | 8q8MX6NQP3b6 |
| 27755 | gordonhamilton | Cleartext-Password | := | c3495Rpuq5Sh |
| 27751 | bobbyfisher | Cleartext-Password | := | salvagejuggle |
| 27748 | eddieyarbrough | Cleartext-Password | := | 89298ChZWzMy |
| 27744 | chrissyeagle | Cleartext-Password | := | 9vkX97426Sgt |
| 27739 | victoriaobier | Cleartext-Password | := | M57B288P9 |
| 27736 | bernardheer | Cleartext-Password | := | BH33682019BER |
| 27734 | garycable | Cleartext-Password | := | M2w6z5Y73 |
| 27735 | coyungemach | Cleartext-Password | := | MhS569N84e7a |
| 27733 | michaelmccauley | Cleartext-Password | := | CPORADIO98 |
| 27731 | lessruble | Cleartext-Password | := | 9U35SXxM2H8y |
| 27730 | marknunnelee | Cleartext-Password | := | 833j2Ymm4yfV |
| 27729 | elizabethchristian | Cleartext-Password | := | Webg22nj |
| 27724 | mikebell | Cleartext-Password | := | BHF538HSEBDLY4 |
| 27713 | krystabates | Cleartext-Password | := | KB2145445824kb |
| 27709 | sensibleheatsystems | Cleartext-Password | := | SHQWERY23 |
| 27708 | chrisfrancis | Cleartext-Password | := | DR45QC74M |
| 27704 | lambandlion | Cleartext-Password | := | LL9727363567ll |
| 27701 | markfisher | Cleartext-Password | := | 32fd8P&rw8s7 |
| 27696 | kirkvanmeter | Cleartext-Password | := | J2sR9g^83M33 |
| 27694 | dananance | Cleartext-Password | := | 23SP$e!ug232 |
| 27690 | keithcrank | Cleartext-Password | := | 2zt3T5w72T3A |
| 27687 | scottarmstrong | Cleartext-Password | := | 2SV58M55 |
| 27683 | floydday | Cleartext-Password | := | Tfdd3254tfD |
| 27680 | mariacortes | Cleartext-Password | := | MC2145858276mc |
| 27668 | christianlummus | ClearText-Password | := | K6dbr631ddL |
| 27666 | marklindsey | Cleartext-Password | := | ML9727346434ml |
| 27658 | joeywhitfield | Cleartext-Password | := | BV758qXne446 |
| 27654 | taekim | Cleartext-Password | := | SAN11837TCK |
| 27652 | teresarobinson | Cleartext-Password | := | T645trdf87fR |
| 27651 | nathanmctee | Cleartext-Password | := | N32462uu8rM |
| 27646 | rebekahmoore | Cleartext-Password | := | R756346tfg4M |
| 27643 | monicatrevino | Cleartext-Password | := | MT2145877143mt |
| 27640 | rachelfuller | Cleartext-Password | := | R8364hbst |
| 27639 | choonpang | Cleartext-Password | := | CP2143855677cp |
| 27638 | shannonbrown | Cleartext-Password | := | S9725298223b |
| 27637 | allentaylor | Cleartext-Password | := | A6346gge8T1 |
| 27632 | kristenaustin | Cleartext-Password | := | KA8017916972ka |
| 27630 | colinhopper | Cleartext-Password | := | TYVU65445IUB |
| 27625 | mattbud | Cleartext-Password | := | kjklfda79sd |
| 27621 | richardhill | Cleartext-Password | := | RH9729788124rh |
| 27619 | srireddy | Cleartext-Password | := | ycHAvetmF0 |
| 27618 | tammieventris | Cleartext-Password | := | DR45QC74M |
| 27615 | almaacosta | Cleartext-Password | := | ENKC5DGV |
| 27614 | chrispassonno | Cleartext-Password | := | C8766thf96P |
| 27613 | seanbaxter | Cleartext-Password | := | SB4696679337sb |
| 27612 | collinhartman | Cleartext-Password | := | B886tg98jt9L |
| 27610 | michaelhughes | Cleartext-Password | := | 1RKk32MyTK |
| 27608 | michaeltalbot | Cleartext-Password | := | Mjj1276rt99T |
| 27606 | anagonzalez | Cleartext-Password | := | WNAHDMwWoE |
| 27604 | leonardlewis | Cleartext-Password | := | L1877ef32L |
| 27598 | jerryjohnson | Cleartext-Password | := | J177895aa98J |
| 27594 | pamtoler | Cleartext-Password | := | NfxcgYQUbj |
| 27589 | jodymccluskey | Cleartext-Password | := | JM4696883349jm |
| 27579 | donnance | Cleartext-Password | := | 9727346814 |
| 27577 | chrisvannatta | Cleartext-Password | := | !4694502484! |
| 27575 | mariomerlo | Cleartext-Password | := | !2149579585! |
| 27572 | williamarmstrong | Cleartext-Password | := | !2145510334! |
| 27571 | trecedaschmid | Cleartext-Password | := | !4692075942! |
| 27570 | ronlewis | Cleartext-Password | := | JDkf1AL942 |
| 27567 | sherryerichardson | Cleartext-Password | := | DR6528BCD3S2 |
| 27566 | joannarodriguez | Cleartext-Password | := | 394X3YR9 |
| 27561 | dannycampbell | Cleartext-Password | := | !9726589541! |
| 27560 | howardcomstock | Cleartext-Password | := | !3365494230! |
| 27555 | terrymiesen | Cleartext-Password | := | !4697420828! |
| 27550 | michaelray | Cleartext-Password | := | primitivedefinitionstoredinner |
| 27545 | terrybates | Cleartext-Password | := | !2145786644! |
| 27538 | gregmcintire | Cleartext-Password | := | v9368$E9mx9m |
| 27543 | beverlyerwin | Cleartext-Password | := | MOOLAH |
| 27518 | helenlumpkin | Cleartext-Password | := | Heehhejd0N |
| 27508 | stevechristiaens | Cleartext-Password | := | zY272m258*vJ |
| 27505 | judydevine | Cleartext-Password | := | OD4697669725od |
| 27501 | jonnytaylor | Cleartext-Password | := | Zb6677&4!yky |
| 27500 | rhondabolton | Cleartext-Password | := | RB2145786030rb |
| 27492 | rickbeckham | Cleartext-Password | := | tVj7ZI5QA0 |
| 27489 | jenniferreed | Cleartext-Password | := | 683P7V7R |
| 27490 | vancepeltonen | Cleartext-Password | := | V66rdf43aaP |
| 27486 | jacobwilliams | Cleartext-Password | := | jnlka9994jkfldsa |
| 27482 | irvinbarojas | Cleartext-Password | := | yDeEJzYY08 |
| 27478 | classicshear | Cleartext-Password | := | KARENALDERSON |
| 27477 | davekiowski | Cleartext-Password | := | FNW63KXSAPT7 |
| 27463 | dennispereyra | Cleartext-Password | := | 82s62kd9XHhd |
| 27458 | paulsammataro | Cleartext-Password | := | P2n74V7P8g4S |
| 27460 | janetkern | Cleartext-Password | := | j42252^Bhf44 |
| 27457 | carlossalaises | Cleartext-Password | := | 28E358y$JFB9 |
| 27453 | obedsandoval | Cleartext-Password | := | *H78z9xy84C& |
| 27455 | dianewiley | Cleartext-Password | := | Diane03t4&adTL |
| 27444 | tammylove | Cleartext-Password | := | f65NS4Ct6$29 |
| 27441 | samanthasmith | Cleartext-Password | := | 22jmR9X#93j5 |
| 27438 | floydallen | Cleartext-Password | := | V3864VB2 |
| 27436 | clayrobertson2 | Cleartext-Password | := | C2147288546R |
| 27432 | hortenciaalvarez | Cleartext-Password | := | WHTABORG3593JKS |
| 27424 | timbagert | Cleartext-Password | := | CF18n42cY7 |
| 27425 | davidbaker | Cleartext-Password | := | DB764g1d87rtB |
| 27421 | janicealexander | Cleartext-Password | := | AG57TSH89L9 |
| 27423 | timgilbert | Cleartext-Password | := | 73xi0r600Sz% |
| 27417 | donniecampbell | Cleartext-Password | := | 6a2671bc0c1 |
| 27418 | alanwyatt | Cleartext-Password | := | i1412r53MYr |
| 27413 | jesusrios | Cleartext-Password | := | 8Di6QMs7%7m1 |
| 27408 | penneywarner | Cleartext-Password | := | b4h53N6c#5wo |
| 27409 | veronabaptist | Cleartext-Password | := | DAY753LIGHT253 |
| 27406 | whiteywhite | Cleartext-Password | := | 45z6r*H4418n |
| 27404 | grahammcintire | Cleartext-Password | := | U5Y3hA3g*14L |
| 27402 | louislafon | Cleartext-Password | := | RFTRE4825CVKL |
| 27397 | ausencioluna | Cleartext-Password | := | ABN765MNL |
| 27374 | davidvillanueva | Cleartext-Password | := | DV5489CRPMWBQ |
| 29620 | sayedahaque | ClearText-Password | := | YR4Sytbu |
| 29621 | coryrees | ClearText-Password | := | yt5c6ujhvu |
| 29622 | brianamartinez | ClearText-Password | := | ktydr6428 |
| 29623 | khushbooagarwal | ClearText-Password | := | UUTFVUCTC446 |
| 29624 | elisasanders | ClearText-Password | := | OYTCu3276 |
| 29625 | josemedrano | ClearText-Password | := | UYVITRUR24 |
| 29626 | karengreen | ClearText-Password | := | UTKCYI674 |
| 29627 | nicholasterry | ClearText-Password | := | VUTYVL746 |
| 29628 | leslieperalta | ClearText-Password | := | RYC66747 |
| 29629 | khushbooagarwal2 | ClearText-Password | := | iytf65gkj |
| 29630 | sriahobilamuth | ClearText-Password | := | RTCESW24 |
| 29631 | royrogers | ClearText-Password | := | TYDGVBNC |
| 29632 | arturoprieto | ClearText-Password | := | 1653APVN877 |
| 29633 | ericamorris | ClearText-Password | := | E16488VN387 |
| 29617 | belindamillener | ClearText-Password | := | Uiyu7280 |
| 29618 | kailynnbarnfield | ClearText-Password | := | HAkuguuvoy1 |
| 29619 | thomasgraham | ClearText-Password | := | YUiygiiu4 |
| 27315 | debravega | Cleartext-Password | := | RCG8618BDUMSR |
| 29582 | davidlanman | Cleartext-Password | := | 2BMMRYQ2PHEA |
| 29583 | anthonylongo | Cleartext-Password | := | 9MLG8HVME4J3 |
| 29584 | konradwoelffer | Cleartext-Password | := | BCQ8NRFVNA3R |
| 29585 | marthaflores | Cleartext-Password | := | KG2QEG36348V |
| 29586 | connorspicer | Cleartext-Password | := | 6U2BWJKGCX8G |
| 29588 | korybiggsshop | Cleartext-Password | := | JA4SXBGKHFUL |
| 29589 | ednagriffin | Cleartext-Password | := | AXA8EZEAN7DJ |
| 29590 | richardrosson | Cleartext-Password | := | 9J5L6MEH4Y7Y |
| 29603 | allentaylor2 | ClearText-Password | := | NCJW87FW8723 |
| 29604 | alextovias | ClearText-Password | := | 7VSgrVO1WX |
| 29605 | mikecurrence | ClearText-Password | := | MKH23FG79C |
| 29594 | terryrector | Cleartext-Password | := | QUPP83ZUUSGL |
| 29595 | donnacampbell | Cleartext-Password | := | KYCNTWS3929N |
| 29600 | billmctee | ClearText-Password | := | Bdge6388M |

View file

@ -0,0 +1,41 @@
"""gaiia GraphQL API client.
Quick start:
from gaiia import GaiiaClient
with GaiiaClient(api_key="gaiia_sk_live_...") as g:
result = g.query("{ technicians(first: 10) { nodes { id firstName lastName } } }")
"""
from gaiia.client import AsyncGaiiaClient, GaiiaClient
from gaiia.errors import (
GaiiaAPIError,
GaiiaError,
GaiiaMutationError,
GaiiaRateLimitedError,
GaiiaTransportError,
)
from gaiia.globalid import decode_global_id, encode_global_id
from gaiia.ratelimit import RateLimitInfo
from gaiia.webhooks import (
WebhookSignatureError,
verify_webhook_signature,
)
__all__ = [
"AsyncGaiiaClient",
"GaiiaAPIError",
"GaiiaClient",
"GaiiaError",
"GaiiaMutationError",
"GaiiaRateLimitedError",
"GaiiaTransportError",
"RateLimitInfo",
"WebhookSignatureError",
"decode_global_id",
"encode_global_id",
"verify_webhook_signature",
]
__version__ = "0.1.0"

392
gaiia/src/gaiia/client.py Normal file
View file

@ -0,0 +1,392 @@
"""Sync and async GraphQL clients for the gaiia API."""
from __future__ import annotations
import os
import time
from types import TracebackType
from typing import Any, AsyncIterator, Iterator
import httpx
from gaiia.errors import (
GaiiaAPIError,
GaiiaMutationError,
GaiiaRateLimitedError,
GaiiaTransportError,
GraphQLError,
)
from gaiia.pagination import aiter_nodes, iter_nodes
from gaiia.ratelimit import RateLimitInfo
DEFAULT_BASE_URL = "https://api.gaiia.com/api/v1"
DEFAULT_TIMEOUT = 30.0
API_KEY_HEADER = "X-Gaiia-Api-Key"
TIMEZONE_HEADER = "x-timezone"
USER_AGENT = "gaiia-py/0.1.0"
def _build_headers(
api_key: str, timezone: str | None, extra: dict[str, str] | None
) -> dict[str, str]:
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": USER_AGENT,
API_KEY_HEADER: api_key,
}
if timezone:
headers[TIMEZONE_HEADER] = timezone
if extra:
headers.update(extra)
return headers
def _classify_errors(raw_errors: list[dict[str, Any]]) -> tuple[bool, list[GraphQLError]]:
"""Return ``(rate_limited, parsed_errors)`` for top-level GraphQL errors."""
errors = [GraphQLError.from_dict(e) for e in raw_errors]
rate_limited = any(e.code == "RATE_LIMITED" for e in errors)
return rate_limited, errors
def _raise_for_payload(payload: dict[str, Any]) -> None:
raw_errors = payload.get("errors")
if not raw_errors:
return
rate_limited, errors = _classify_errors(raw_errors)
if rate_limited:
ext = errors[0].extensions
retry_at = None
if ext.get("retryAt"):
from datetime import datetime
try:
retry_at = datetime.fromisoformat(str(ext["retryAt"]).replace("Z", "+00:00"))
except ValueError:
retry_at = None
raise GaiiaRateLimitedError(
errors,
cost=ext.get("cost"),
limit=ext.get("limit"),
used=ext.get("used"),
remaining=ext.get("remaining"),
retry_at=retry_at,
)
raise GaiiaAPIError(errors, data=payload.get("data"))
class _Base:
def __init__(
self,
api_key: str | None = None,
*,
base_url: str = DEFAULT_BASE_URL,
timezone: str | None = None,
timeout: float = DEFAULT_TIMEOUT,
default_headers: dict[str, str] | None = None,
max_retries: int = 0,
) -> None:
resolved = api_key if api_key is not None else os.environ.get("GAIIA_KEY")
if not resolved:
raise ValueError(
"api_key is required (pass api_key= or set the GAIIA_KEY env var)"
)
self._api_key = resolved
self._base_url = base_url
self._timezone = timezone
self._timeout = timeout
self._default_headers = default_headers
self._max_retries = max_retries
self.last_rate_limit: RateLimitInfo | None = None
def _headers(self, extra: dict[str, str] | None = None) -> dict[str, str]:
merged = dict(self._default_headers or {})
if extra:
merged.update(extra)
return _build_headers(self._api_key, self._timezone, merged)
class GaiiaClient(_Base):
"""Synchronous gaiia GraphQL client."""
def __init__(
self,
api_key: str | None = None,
*,
base_url: str = DEFAULT_BASE_URL,
timezone: str | None = None,
timeout: float = DEFAULT_TIMEOUT,
default_headers: dict[str, str] | None = None,
max_retries: int = 0,
transport: httpx.BaseTransport | None = None,
) -> None:
super().__init__(
api_key,
base_url=base_url,
timezone=timezone,
timeout=timeout,
default_headers=default_headers,
max_retries=max_retries,
)
self._http = httpx.Client(timeout=timeout, transport=transport)
def __enter__(self) -> GaiiaClient:
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
self.close()
def close(self) -> None:
self._http.close()
def query(
self,
query: str,
variables: dict[str, Any] | None = None,
*,
operation_name: str | None = None,
timezone: str | None = None,
extra_headers: dict[str, str] | None = None,
) -> dict[str, Any]:
"""Run a GraphQL query/mutation and return the ``data`` payload.
Raises :class:`GaiiaAPIError` on top-level errors,
:class:`GaiiaRateLimitedError` on RATE_LIMITED, and
:class:`GaiiaTransportError` on network failures.
"""
body: dict[str, Any] = {"query": query}
if variables is not None:
body["variables"] = variables
if operation_name is not None:
body["operationName"] = operation_name
per_call_headers = dict(extra_headers or {})
if timezone is not None:
per_call_headers[TIMEZONE_HEADER] = timezone
headers = self._headers(per_call_headers)
attempt = 0
while True:
attempt += 1
try:
resp = self._http.post(self._base_url, json=body, headers=headers)
except httpx.HTTPError as e:
raise GaiiaTransportError(str(e)) from e
self.last_rate_limit = RateLimitInfo.from_headers(resp.headers)
try:
payload = resp.json()
except ValueError as e:
raise GaiiaTransportError(
f"non-JSON response from gaiia (HTTP {resp.status_code})"
) from e
try:
_raise_for_payload(payload)
except GaiiaRateLimitedError as e:
if attempt > self._max_retries:
raise
_sleep_until(e.retry_at)
continue
if resp.status_code >= 500:
raise GaiiaTransportError(
f"gaiia returned HTTP {resp.status_code}: {resp.text[:200]}"
)
return payload.get("data") or {}
def mutate(
self,
query: str,
mutation_name: str,
variables: dict[str, Any] | None = None,
*,
timezone: str | None = None,
raise_on_payload_errors: bool = True,
) -> dict[str, Any]:
"""Run a mutation and inspect its ``errors`` payload field.
Returns the mutation's result object (e.g. the contents of
``data.createTicket``). If ``raise_on_payload_errors`` is true and the
result contains a non-empty ``errors`` list, :class:`GaiiaMutationError`
is raised.
"""
data = self.query(query, variables, timezone=timezone)
result = data.get(mutation_name)
if result is None:
raise GaiiaAPIError(
[GraphQLError(message=f"mutation {mutation_name!r} not in response", code=None)],
data=data,
)
errors = result.get("errors") or []
if errors and raise_on_payload_errors:
raise GaiiaMutationError(mutation_name, errors, payload=result)
return result
def iter_nodes(
self,
query: str,
connection_path: str,
variables: dict[str, Any] | None = None,
*,
cursor_var: str = "after",
timezone: str | None = None,
) -> Iterator[Any]:
"""Iterate every node across all pages of a Connection.
``query`` must accept the cursor variable (default ``$after``) and use
it as the ``after:`` argument on the paginated field.
"""
base_vars = dict(variables or {})
def fetch_page(cursor: str | None) -> dict[str, Any]:
v = dict(base_vars)
v[cursor_var] = cursor
return self.query(query, v, timezone=timezone)
yield from iter_nodes(fetch_page, connection_path)
class AsyncGaiiaClient(_Base):
"""Asynchronous gaiia GraphQL client."""
def __init__(
self,
api_key: str | None = None,
*,
base_url: str = DEFAULT_BASE_URL,
timezone: str | None = None,
timeout: float = DEFAULT_TIMEOUT,
default_headers: dict[str, str] | None = None,
max_retries: int = 0,
transport: httpx.AsyncBaseTransport | None = None,
) -> None:
super().__init__(
api_key,
base_url=base_url,
timezone=timezone,
timeout=timeout,
default_headers=default_headers,
max_retries=max_retries,
)
self._http = httpx.AsyncClient(timeout=timeout, transport=transport)
async def __aenter__(self) -> AsyncGaiiaClient:
return self
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
await self.aclose()
async def aclose(self) -> None:
await self._http.aclose()
async def query(
self,
query: str,
variables: dict[str, Any] | None = None,
*,
operation_name: str | None = None,
timezone: str | None = None,
extra_headers: dict[str, str] | None = None,
) -> dict[str, Any]:
body: dict[str, Any] = {"query": query}
if variables is not None:
body["variables"] = variables
if operation_name is not None:
body["operationName"] = operation_name
per_call_headers = dict(extra_headers or {})
if timezone is not None:
per_call_headers[TIMEZONE_HEADER] = timezone
headers = self._headers(per_call_headers)
attempt = 0
while True:
attempt += 1
try:
resp = await self._http.post(self._base_url, json=body, headers=headers)
except httpx.HTTPError as e:
raise GaiiaTransportError(str(e)) from e
self.last_rate_limit = RateLimitInfo.from_headers(resp.headers)
try:
payload = resp.json()
except ValueError as e:
raise GaiiaTransportError(
f"non-JSON response from gaiia (HTTP {resp.status_code})"
) from e
try:
_raise_for_payload(payload)
except GaiiaRateLimitedError as e:
if attempt > self._max_retries:
raise
_sleep_until(e.retry_at)
continue
if resp.status_code >= 500:
raise GaiiaTransportError(
f"gaiia returned HTTP {resp.status_code}: {resp.text[:200]}"
)
return payload.get("data") or {}
async def mutate(
self,
query: str,
mutation_name: str,
variables: dict[str, Any] | None = None,
*,
timezone: str | None = None,
raise_on_payload_errors: bool = True,
) -> dict[str, Any]:
data = await self.query(query, variables, timezone=timezone)
result = data.get(mutation_name)
if result is None:
raise GaiiaAPIError(
[GraphQLError(message=f"mutation {mutation_name!r} not in response", code=None)],
data=data,
)
errors = result.get("errors") or []
if errors and raise_on_payload_errors:
raise GaiiaMutationError(mutation_name, errors, payload=result)
return result
async def iter_nodes(
self,
query: str,
connection_path: str,
variables: dict[str, Any] | None = None,
*,
cursor_var: str = "after",
timezone: str | None = None,
) -> AsyncIterator[Any]:
base_vars = dict(variables or {})
async def fetch_page(cursor: str | None) -> dict[str, Any]:
v = dict(base_vars)
v[cursor_var] = cursor
return await self.query(query, v, timezone=timezone)
async for node in aiter_nodes(fetch_page, connection_path):
yield node
def _sleep_until(retry_at: Any) -> None:
if not retry_at:
time.sleep(1.0)
return
delta = retry_at.timestamp() - time.time()
if delta > 0:
time.sleep(min(delta, 60.0))
else:
time.sleep(1.0)

91
gaiia/src/gaiia/errors.py Normal file
View file

@ -0,0 +1,91 @@
"""Exception types raised by the gaiia client."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
class GaiiaError(Exception):
"""Base class for all gaiia client errors."""
class GaiiaTransportError(GaiiaError):
"""Network/transport failure talking to the gaiia API."""
@dataclass
class GraphQLError:
message: str
code: str | None = None
path: list[str | int] | None = None
extensions: dict[str, Any] = field(default_factory=dict)
@classmethod
def from_dict(cls, raw: dict[str, Any]) -> GraphQLError:
extensions = dict(raw.get("extensions") or {})
return cls(
message=raw.get("message", ""),
code=extensions.get("code"),
path=raw.get("path"),
extensions=extensions,
)
class GaiiaAPIError(GaiiaError):
"""One or more top-level GraphQL errors were returned."""
def __init__(self, errors: list[GraphQLError], data: Any | None = None) -> None:
self.errors = errors
self.data = data
msg = "; ".join(e.message for e in errors) or "gaiia API error"
super().__init__(msg)
class GaiiaMutationError(GaiiaError):
"""A mutation returned validation/expected errors under data.<mutation>.errors.
These are the GraphQL "200 OK with errors in the payload" responses that
represent expected failure modes (validation, business rules) rather than
transport or schema issues.
"""
def __init__(
self,
mutation: str,
errors: list[dict[str, Any]],
payload: dict[str, Any] | None = None,
) -> None:
self.mutation = mutation
self.errors = errors
self.payload = payload
codes = [e.get("code") for e in errors if e.get("code")]
msg = (
f"mutation {mutation!r} returned errors: "
+ ", ".join(f"{c}: {e.get('message','')}" for c, e in zip(codes, errors, strict=False))
if codes
else f"mutation {mutation!r} returned errors"
)
super().__init__(msg)
class GaiiaRateLimitedError(GaiiaAPIError):
"""Raised when the API returns a RATE_LIMITED error."""
def __init__(
self,
errors: list[GraphQLError],
*,
cost: int | None = None,
limit: int | None = None,
used: int | None = None,
remaining: int | None = None,
retry_at: datetime | None = None,
) -> None:
super().__init__(errors)
self.cost = cost
self.limit = limit
self.used = used
self.remaining = remaining
self.retry_at = retry_at

View file

@ -0,0 +1,65 @@
"""Encode and decode gaiia GlobalIDs.
A GlobalID looks like ``account_pwE84wz4Mw15LPX5YE8Z3o``: a snake-cased entity
type, an underscore, and a 22-char Flickr Base-58 encoding of the underlying
128-bit UUID. This matches the ``short-uuid`` library that gaiia uses on the
server side.
"""
from __future__ import annotations
import re
from uuid import UUID
_ALPHABET = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
_BASE = len(_ALPHABET)
_TARGET_LEN = 22
_TYPE_RE = re.compile(r"(?<!^)(?=[A-Z])")
def _snake_case(name: str) -> str:
return _TYPE_RE.sub("_", name).lower()
def encode_global_id(type_name: str, uuid_str: str | UUID) -> str:
"""Build a GlobalID from a type name (e.g. ``Account``) and a UUID."""
if isinstance(uuid_str, UUID):
stripped = uuid_str.hex
else:
stripped = str(uuid_str).replace("-", "").lower()
if len(stripped) != 32 or any(c not in "0123456789abcdef" for c in stripped):
raise ValueError(f"not a valid uuid: {uuid_str!r}")
num = int(stripped, 16)
chars: list[str] = []
if num == 0:
chars.append(_ALPHABET[0])
else:
while num > 0:
num, rem = divmod(num, _BASE)
chars.append(_ALPHABET[rem])
chars.reverse()
short = "".join(chars).rjust(_TARGET_LEN, _ALPHABET[0])
return f"{_snake_case(type_name)}_{short}"
def decode_global_id(global_id: str) -> tuple[str, str]:
"""Split a GlobalID into ``(type_snake_case, uuid_str)``."""
if "_" not in global_id:
raise ValueError(f"not a global id: {global_id!r}")
type_part, _, short_id = global_id.rpartition("_")
if not type_part or not short_id:
raise ValueError(f"malformed global id: {global_id!r}")
num = 0
for ch in short_id:
idx = _ALPHABET.find(ch)
if idx < 0:
raise ValueError(f"invalid base58 char {ch!r} in {global_id!r}")
num = num * _BASE + idx
hex_str = format(num, "x").rjust(32, "0")
if len(hex_str) > 32:
raise ValueError(f"global id decodes to oversized uuid: {global_id!r}")
uuid_str = (
f"{hex_str[0:8]}-{hex_str[8:12]}-{hex_str[12:16]}-{hex_str[16:20]}-{hex_str[20:32]}"
)
return type_part, uuid_str

View file

@ -0,0 +1,61 @@
"""Helpers for cursor-based pagination over gaiia Connection types."""
from __future__ import annotations
from typing import Any, AsyncIterator, Iterator
def extract_connection(data: dict[str, Any], path: str) -> dict[str, Any]:
"""Walk a dotted path into a GraphQL ``data`` payload and return the Connection node."""
node: Any = data
for part in path.split("."):
if not isinstance(node, dict):
raise KeyError(f"path {path!r} not found in response")
node = node.get(part)
if node is None:
raise KeyError(f"path {path!r} not found in response (broke at {part!r})")
if not isinstance(node, dict) or "pageInfo" not in node:
raise ValueError(f"path {path!r} did not point to a Connection (no pageInfo)")
return node
def iter_nodes(
fetch_page: "Callable[[str | None], dict[str, Any]]", # noqa: F821
connection_path: str,
) -> Iterator[Any]:
"""Yield nodes one by one across all pages of a Connection.
``fetch_page`` takes the current ``after`` cursor (``None`` on the first
page) and returns the full GraphQL ``data`` payload for that page.
"""
cursor: str | None = None
while True:
data = fetch_page(cursor)
conn = extract_connection(data, connection_path)
for node in conn.get("nodes") or []:
yield node
page_info = conn.get("pageInfo") or {}
if not page_info.get("hasNextPage"):
return
cursor = page_info.get("endCursor")
if not cursor:
return
async def aiter_nodes(
fetch_page: "Callable[[str | None], Awaitable[dict[str, Any]]]", # noqa: F821
connection_path: str,
) -> AsyncIterator[Any]:
"""Async version of :func:`iter_nodes`."""
cursor: str | None = None
while True:
data = await fetch_page(cursor)
conn = extract_connection(data, connection_path)
for node in conn.get("nodes") or []:
yield node
page_info = conn.get("pageInfo") or {}
if not page_info.get("hasNextPage"):
return
cursor = page_info.get("endCursor")
if not cursor:
return

0
gaiia/src/gaiia/py.typed Normal file
View file

View file

@ -0,0 +1,56 @@
"""Parse gaiia rate-limit response headers."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Mapping
@dataclass
class RateLimitInfo:
allowed: bool | None
cost: int | None
limit: int | None
used: int | None
remaining: int | None
retry_at: datetime | None
@classmethod
def from_headers(cls, headers: Mapping[str, str]) -> RateLimitInfo:
def _get(name: str) -> str | None:
for k, v in headers.items():
if k.lower() == name.lower():
return v
return None
def _int(name: str) -> int | None:
v = _get(name)
try:
return int(v) if v is not None else None
except ValueError:
return None
def _bool(name: str) -> bool | None:
v = _get(name)
if v is None:
return None
return v.strip().lower() == "true"
def _dt(name: str) -> datetime | None:
v = _get(name)
if not v:
return None
try:
return datetime.fromisoformat(v.replace("Z", "+00:00"))
except ValueError:
return None
return cls(
allowed=_bool("X-Rate-Limit-Allowed"),
cost=_int("X-Rate-Limit-Cost"),
limit=_int("X-Rate-Limit-Limit"),
used=_int("X-Rate-Limit-Used"),
remaining=_int("X-Rate-Limit-Remaining"),
retry_at=_dt("X-Rate-Limit-Retry-At") or _dt("X-Rate-Limit-Retry-at"),
)

View file

@ -0,0 +1,93 @@
"""Webhook signature verification for incoming gaiia webhooks.
Each delivery includes the header
``X-Gaiia-Webhook-Signature: t=<unix-ts>,v1=<hex-sha256-hmac>``. The signature
is HMAC-SHA256 over ``"${t}.${rawBody}"`` keyed by the endpoint secret. Compare
in constant time and reject if ``t`` is outside your tolerance window.
"""
from __future__ import annotations
import hashlib
import hmac
import time
from typing import Iterable
from gaiia.errors import GaiiaError
SIGNATURE_HEADER = "X-Gaiia-Webhook-Signature"
DEFAULT_TOLERANCE_SECONDS = 5 * 60
class WebhookSignatureError(GaiiaError):
"""Raised when a webhook signature fails verification."""
def _parse_header(header_value: str) -> tuple[str | None, list[str]]:
timestamp: str | None = None
v1_signatures: list[str] = []
for chunk in header_value.split(","):
if "=" not in chunk:
continue
prefix, _, value = chunk.partition("=")
prefix = prefix.strip()
value = value.strip()
if prefix == "t":
timestamp = value
elif prefix == "v1":
v1_signatures.append(value)
return timestamp, v1_signatures
def _compute_signature(secret: str | bytes, timestamp: str, raw_body: str | bytes) -> str:
if isinstance(secret, str):
secret = secret.encode("utf-8")
if isinstance(raw_body, bytes):
body_text = raw_body.decode("utf-8")
else:
body_text = raw_body
payload = f"{timestamp}.{body_text}".encode("utf-8")
return hmac.new(secret, payload, hashlib.sha256).hexdigest()
def verify_webhook_signature(
*,
header_value: str,
raw_body: str | bytes,
secret: str | bytes,
tolerance_seconds: int = DEFAULT_TOLERANCE_SECONDS,
now: float | None = None,
) -> None:
"""Verify a webhook delivery. Raises :class:`WebhookSignatureError` on failure.
``raw_body`` must be the exact bytes/string the server signed do not
re-serialize a parsed JSON body, since whitespace differences will break
the HMAC.
"""
if not header_value:
raise WebhookSignatureError("missing signature header")
timestamp, candidates = _parse_header(header_value)
if timestamp is None:
raise WebhookSignatureError("signature header missing timestamp (t=...)")
if not candidates:
raise WebhookSignatureError("signature header missing v1 signatures")
try:
ts_int = int(timestamp)
except ValueError as e:
raise WebhookSignatureError(f"signature timestamp not an integer: {timestamp!r}") from e
current = time.time() if now is None else now
if abs(current - ts_int) > tolerance_seconds:
raise WebhookSignatureError(
f"signature timestamp outside tolerance "
f"({tolerance_seconds}s): {abs(current - ts_int):.0f}s drift"
)
expected = _compute_signature(secret, timestamp, raw_body)
if not _any_match(expected, candidates):
raise WebhookSignatureError("no signature matched expected HMAC")
def _any_match(expected: str, candidates: Iterable[str]) -> bool:
return any(hmac.compare_digest(expected, c) for c in candidates)

View file

@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""
Run 'Update RADIUS Account Groups' for every account in Gaiia.
Status must be UPPERCASE (ACTIVE, SUSPENDED, etc.)
Usage:
source .envrc
uv run python3 sync_all_radius_groups.py --dry-run (preview)
uv run python3 sync_all_radius_groups.py (execute)
"""
from gaiia import GaiiaClient, GaiiaAPIError, GaiiaRateLimitedError
import json
import sys
import time
WF_ID = "workflow_fLXTNCvridPqwEEXxf3CK1"
def list_accounts(client):
"""Paginate through all accounts, return [{id, name, status, productIds}]."""
accounts = []
after = None
page = 0
while True:
page += 1
data = client.query("""
query Q($after: String) {
accounts(first: 250, after: $after) {
nodes {
id
name
status { name }
billingSubscriptions {
nodes {
productVersion {
product { id name slug }
}
}
}
}
pageInfo { hasNextPage endCursor }
totalCount
}
}
""", {"after": after} if after else {})
result = data['accounts']
for acct in result['nodes']:
status_name = acct['status']['name'] if acct['status'] else ''
products = []
for sub in (acct.get('billingSubscriptions') or {}).get('nodes', []):
pv = sub.get('productVersion') or {}
p = pv.get('product') if pv else None
if p and p['id'] not in products:
products.append(p['id'])
accounts.append({
'id': acct['id'],
'name': acct.get('name', '') or '',
'status': status_name,
'productIds': products,
})
sys.stderr.write(f"\r Page {page}: {len(accounts)} accounts so far...")
sys.stderr.flush()
page_info = result['pageInfo']
if not page_info.get('hasNextPage'):
break
after = page_info['endCursor']
sys.stderr.write("\n")
return accounts
def sync_account(client, acct, dry_run=False):
"""Run the Update RADIUS Account Groups workflow for one account."""
status_upper = acct['status'].upper()
wf_input = {
"accountId": acct['id'],
"status": status_upper,
"productIds": acct['productIds'],
}
if dry_run:
name = acct['name'] or '(no name)'
print(f" [DRY-RUN] {name} status={status_upper} products={len(acct['productIds'])}")
return True
try:
result = client.query("""
mutation Run($input: StartWorkflowExecutionInput!) {
startWorkflowExecution(input: $input) {
workflowExecution { id status }
}
}
""", {"input": {"workflowId": WF_ID, "input": wf_input}})
we = result['startWorkflowExecution']['workflowExecution']
return we is not None
except GaiiaAPIError as e:
print(f" FAILED {acct.get('name','?')}: API error - {e}")
return False
except GaiiaRateLimitedError as e:
print(f" RATE LIMITED - sleeping until {e.retry_at}")
if e.retry_at:
wait = max(0, (e.retry_at.timestamp() - time.time()))
time.sleep(wait + 1)
return False
def main():
dry_run = "--dry-run" in sys.argv
with GaiiaClient(timezone='America/Chicago') as g:
print("Listing all accounts...")
accounts = list_accounts(g)
print(f"\nFound {len(accounts)} accounts")
# Stats
statuses = {}
for a in accounts:
s = a['status'].upper()
statuses[s] = statuses.get(s, 0) + 1
print(f"Statuses: {statuses}")
if dry_run:
print("\n--dry-run mode: previewing --")
else:
print(f"\nSyncing via workflow {WF_ID}...")
succeeded = 0
failed = 0
for i, a in enumerate(accounts):
name = a['name'] or f"(id={a['id']})"
if not dry_run:
sys.stderr.write(f"\r {i+1}/{len(accounts)}: {name[:40]}...")
sys.stderr.flush()
if sync_account(g, a, dry_run):
succeeded += 1
else:
failed += 1
if not dry_run:
time.sleep(0.15) # avoid rate limiting (~7/sec)
if not dry_run:
sys.stderr.write("\n")
print(f"\nDone: {succeeded} started, {failed} failed")
if dry_run:
print("(nothing executed — remove --dry-run to run)")
if __name__ == '__main__':
main()

View file

@ -0,0 +1,154 @@
{
"name": "Sync All RADIUS Groups",
"description": "MANUAL: Syncs RADIUS usergroups for ALL accounts (2 pages x 250 = 500 accounts). Passes actual account status through — ACTIVE, SUSPENDED, INACTIVE, etc.",
"trigger": {
"type": "MANUAL",
"nextSteps": [{ "slug": "fetch-page-1" }]
},
"steps": [
{
"name": "Fetch accounts page 1",
"slug": "fetch-page-1",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const data = output.response.accounts;\n return {\n accounts: data.nodes.map((a) => ({\n accountId: a.id,\n status: (a.status?.name || '').toUpperCase(),\n products: (a.billingSubscriptions?.nodes || [])\n .map((s) => s.productVersion?.product?.id)\n .filter(Boolean)\n .filter((id, i, arr) => arr.indexOf(id) === i)\n })),\n hasNextPage: data.pageInfo.hasNextPage,\n endCursor: data.pageInfo.endCursor\n };\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query {\n accounts(first: 250) {\n nodes {\n id\n status { name }\n billingSubscriptions {\n nodes { productVersion { product { id } } }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ secrets, state, utils, variables }) => ({})"
}
],
"nextSteps": [{ "slug": "sync-page-1" }]
},
{
"name": "Sync page 1",
"slug": "sync-page-1",
"type": "PARALLEL_LOOP",
"nodeSlug": "parallel-loop",
"maxConcurrentIterations": 10,
"inputs": [
{
"key": "iterable",
"type": "FUNCTION",
"value": "({ state }) => state.steps['fetch-page-1'].output.accounts"
}
],
"initialSteps": [{ "slug": "call-update-page1" }],
"nextSteps": [{ "slug": "need-page-2" }]
},
{
"name": "Call Update RADIUS Account Groups",
"slug": "call-update-page1",
"type": "GAIIA_START_WORKFLOW_EXECUTION",
"nodeSlug": "start-workflow-execution",
"parentParallelLoopSlug": "sync-page-1",
"allowSkipExecution": false,
"inputs": [
{
"key": "workflowId",
"type": "LITERAL",
"value": "string:workflow_fLXTNCvridPqwEEXxf3CK1"
},
{
"key": "input",
"type": "FUNCTION",
"value": "({ state }) => {\n const acct = state.currentParallelLoopIterations['sync-page-1'];\n return { accountId: acct.accountId, status: acct.status, productIds: acct.products };\n}"
},
{
"key": "fireAndForget",
"type": "LITERAL",
"value": "boolean:true"
}
],
"nextSteps": [{ "slug": "sync-page-1-end" }]
},
{
"name": "Need page 2?",
"slug": "need-page-2",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n return state.steps['fetch-page-1'].output.hasNextPage;\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "fetch-page-2" }]
},
{
"name": "Fetch accounts page 2",
"slug": "fetch-page-2",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const nodes = output.response.accounts.nodes;\n if (!nodes || nodes.length === 0) return [];\n return nodes.map((a) => ({\n accountId: a.id,\n status: (a.status?.name || '').toUpperCase(),\n products: (a.billingSubscriptions?.nodes || [])\n .map((s) => s.productVersion?.product?.id)\n .filter(Boolean)\n .filter((id, i, arr) => arr.indexOf(id) === i)\n }));\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query($after: String) {\n accounts(first: 250, after: $after) {\n nodes {\n id\n status { name }\n billingSubscriptions {\n nodes { productVersion { product { id } } }\n }\n }\n pageInfo { hasNextPage endCursor }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => {\n const p1 = state.steps['fetch-page-1'].output;\n return p1.hasNextPage ? { after: p1.endCursor } : {};\n}"
}
],
"nextSteps": [{ "slug": "if-page-2-has-data" }]
},
{
"name": "Page 2 has data?",
"slug": "if-page-2-has-data",
"type": "CONDITION",
"nodeSlug": "condition",
"condition": "({ secrets, state, utils, variables }) => {\n const data = state.steps['fetch-page-2'].output;\n return Array.isArray(data) && data.length > 0;\n}",
"elseSteps": [{ "slug": "end" }],
"thenSteps": [{ "slug": "sync-page-2" }]
},
{
"name": "Sync page 2",
"slug": "sync-page-2",
"type": "PARALLEL_LOOP",
"nodeSlug": "parallel-loop",
"maxConcurrentIterations": 10,
"inputs": [
{
"key": "iterable",
"type": "FUNCTION",
"value": "({ state }) => state.steps['fetch-page-2'].output"
}
],
"initialSteps": [{ "slug": "call-update-page2" }],
"nextSteps": [{ "slug": "end" }]
},
{
"name": "Call Update RADIUS Account Groups (page 2)",
"slug": "call-update-page2",
"type": "GAIIA_START_WORKFLOW_EXECUTION",
"nodeSlug": "start-workflow-execution",
"parentParallelLoopSlug": "sync-page-2",
"allowSkipExecution": false,
"inputs": [
{
"key": "workflowId",
"type": "LITERAL",
"value": "string:workflow_fLXTNCvridPqwEEXxf3CK1"
},
{
"key": "input",
"type": "FUNCTION",
"value": "({ state }) => {\n const acct = state.currentParallelLoopIterations['sync-page-2'];\n return { accountId: acct.accountId, status: acct.status, productIds: acct.products };\n}"
},
{
"key": "fireAndForget",
"type": "LITERAL",
"value": "boolean:true"
}
],
"nextSteps": [{ "slug": "sync-page-2-end" }]
}
]
}

0
gaiia/tests/__init__.py Normal file
View file

153
gaiia/tests/test_client.py Normal file
View file

@ -0,0 +1,153 @@
import httpx
import pytest
from gaiia import (
GaiiaAPIError,
GaiiaClient,
GaiiaMutationError,
GaiiaRateLimitedError,
)
from gaiia.client import API_KEY_HEADER, TIMEZONE_HEADER
def _client(handler):
transport = httpx.MockTransport(handler)
return GaiiaClient(
api_key="gaiia_sk_sbox_test",
timezone="America/Chicago",
transport=transport,
)
def test_query_sends_expected_headers_and_body():
captured = {}
def handler(request: httpx.Request) -> httpx.Response:
captured["headers"] = dict(request.headers)
captured["body"] = request.read()
return httpx.Response(200, json={"data": {"x": 1}})
with _client(handler) as g:
out = g.query("{ x }", {"v": 1})
assert out == {"x": 1}
assert captured["headers"][API_KEY_HEADER.lower()] == "gaiia_sk_sbox_test"
assert captured["headers"][TIMEZONE_HEADER] == "America/Chicago"
import json
body = json.loads(captured["body"])
assert body["query"] == "{ x }"
assert body["variables"] == {"v": 1}
def test_top_level_errors_raise_api_error():
def handler(request):
return httpx.Response(
200,
json={"errors": [{"message": "bad", "extensions": {"code": "BAD_USER_INPUT"}}]},
)
with _client(handler) as g, pytest.raises(GaiiaAPIError) as exc:
g.query("{}")
assert exc.value.errors[0].code == "BAD_USER_INPUT"
def test_rate_limited_classifies_separately():
def handler(request):
return httpx.Response(
200,
json={
"errors": [
{
"message": "too many",
"extensions": {
"code": "RATE_LIMITED",
"limit": 500,
"used": 500,
"remaining": 0,
"retryAt": "2026-06-27T19:01:02.000Z",
},
}
]
},
headers={
"X-Rate-Limit-Allowed": "false",
"X-Rate-Limit-Limit": "500",
"X-Rate-Limit-Used": "500",
"X-Rate-Limit-Remaining": "0",
},
)
with _client(handler) as g, pytest.raises(GaiiaRateLimitedError) as exc:
g.query("{}")
assert exc.value.limit == 500
assert g.last_rate_limit is not None
assert g.last_rate_limit.remaining == 0
def test_mutate_raises_on_payload_errors():
def handler(request):
return httpx.Response(
200,
json={
"data": {
"createTicket": {
"ticket": None,
"errors": [{"code": "TITLE_TOO_LONG", "message": "long"}],
}
}
},
)
with _client(handler) as g, pytest.raises(GaiiaMutationError) as exc:
g.mutate("mutation { createTicket { ticket { id } errors { code message } } }", "createTicket")
assert exc.value.errors[0]["code"] == "TITLE_TOO_LONG"
def test_iter_nodes_paginates_via_cursor():
calls = []
def handler(request):
body = request.read().decode()
calls.append(body)
if '"after":null' in body.replace(" ", ""):
return httpx.Response(
200,
json={
"data": {
"things": {
"nodes": [{"id": 1}, {"id": 2}],
"pageInfo": {"hasNextPage": True, "endCursor": "c1"},
}
}
},
)
return httpx.Response(
200,
json={
"data": {
"things": {
"nodes": [{"id": 3}],
"pageInfo": {"hasNextPage": False, "endCursor": None},
}
}
},
)
with _client(handler) as g:
ids = [n["id"] for n in g.iter_nodes(
"query Q($after: String) { things(first: 2, after: $after) { nodes { id } pageInfo { hasNextPage endCursor } } }",
"things",
)]
assert ids == [1, 2, 3]
assert len(calls) == 2
def test_missing_api_key_raises():
import os
saved = os.environ.pop("GAIIA_KEY", None)
try:
with pytest.raises(ValueError):
GaiiaClient()
finally:
if saved is not None:
os.environ["GAIIA_KEY"] = saved

View file

@ -0,0 +1,45 @@
from uuid import UUID
import pytest
from gaiia import decode_global_id, encode_global_id
def test_roundtrip_known_value():
type_name = "Account"
uuid_str = "3c3b1978-6a68-4a13-bdc2-2d51c8ef7519"
gid = encode_global_id(type_name, uuid_str)
snake, decoded = decode_global_id(gid)
assert snake == "account"
assert decoded == uuid_str
def test_camelcase_type_becomes_snake_case():
gid = encode_global_id("InventoryItem", "00000000-0000-0000-0000-000000000001")
snake, _ = decode_global_id(gid)
assert snake == "inventory_item"
def test_accepts_uuid_object():
u = UUID("3c3b1978-6a68-4a13-bdc2-2d51c8ef7519")
gid = encode_global_id("Account", u)
_, decoded = decode_global_id(gid)
assert decoded == str(u)
def test_short_id_always_22_chars():
gid = encode_global_id("Account", "00000000-0000-0000-0000-000000000001")
_, short = gid.split("_", 1)
assert len(short) == 22
def test_decode_rejects_garbage():
with pytest.raises(ValueError):
decode_global_id("no-underscore-here")
with pytest.raises(ValueError):
decode_global_id("account_!!!invalid!!!")
def test_encode_rejects_bad_uuid():
with pytest.raises(ValueError):
encode_global_id("Account", "not-a-uuid")

View file

@ -0,0 +1,43 @@
import pytest
from gaiia.pagination import extract_connection, iter_nodes
def _conn(nodes, has_next=False, end="cur"):
return {
"nodes": nodes,
"pageInfo": {"hasNextPage": has_next, "endCursor": end if has_next else None},
}
def test_extract_connection_walks_dotted_path():
data = {"a": {"b": _conn([1, 2])}}
conn = extract_connection(data, "a.b")
assert conn["nodes"] == [1, 2]
def test_extract_connection_missing_path_raises():
with pytest.raises(KeyError):
extract_connection({"a": {}}, "a.b")
def test_iter_nodes_terminates_on_last_page():
pages = [
{"things": _conn(["a", "b"], has_next=True, end="x")},
{"things": _conn(["c"], has_next=False)},
]
calls = []
def fetch(cursor):
calls.append(cursor)
return pages.pop(0)
assert list(iter_nodes(fetch, "things")) == ["a", "b", "c"]
assert calls == [None, "x"]
def test_iter_nodes_handles_empty():
def fetch(cursor):
return {"things": _conn([])}
assert list(iter_nodes(fetch, "things")) == []

View file

@ -0,0 +1,62 @@
import hashlib
import hmac
import time
import pytest
from gaiia import WebhookSignatureError, verify_webhook_signature
def _make_header(secret: str, timestamp: int, body: bytes) -> str:
sig = hmac.new(
secret.encode(), f"{timestamp}.{body.decode()}".encode(), hashlib.sha256
).hexdigest()
return f"t={timestamp},v1={sig}"
def test_valid_signature_passes():
secret = "whsec_test"
body = b'{"hello":"world"}'
ts = int(time.time())
header = _make_header(secret, ts, body)
verify_webhook_signature(header_value=header, raw_body=body, secret=secret)
def test_tampered_body_fails():
secret = "whsec_test"
body = b'{"hello":"world"}'
ts = int(time.time())
header = _make_header(secret, ts, body)
with pytest.raises(WebhookSignatureError):
verify_webhook_signature(
header_value=header, raw_body=b'{"hello":"WORLD"}', secret=secret
)
def test_old_timestamp_rejected():
secret = "whsec_test"
body = b'{"x":1}'
ts = int(time.time()) - 3600
header = _make_header(secret, ts, body)
with pytest.raises(WebhookSignatureError):
verify_webhook_signature(
header_value=header, raw_body=body, secret=secret, tolerance_seconds=60
)
def test_multiple_signatures_one_match():
secret = "whsec_test"
body = b"abc"
ts = int(time.time())
real = _make_header(secret, ts, body).split("v1=")[1]
header = f"t={ts},v1=ffffffff,v1={real}"
verify_webhook_signature(header_value=header, raw_body=body, secret=secret)
def test_missing_components():
with pytest.raises(WebhookSignatureError):
verify_webhook_signature(header_value="", raw_body=b"", secret="x")
with pytest.raises(WebhookSignatureError):
verify_webhook_signature(header_value="v1=abc", raw_body=b"", secret="x")
with pytest.raises(WebhookSignatureError):
verify_webhook_signature(header_value="t=123", raw_body=b"", secret="x")

View file

@ -0,0 +1,108 @@
{
"steps": [
{
"name": "Find inventory item",
"slug": "find-inventory-item",
"type": "GAIIA_API_GATEWAY_V1_GRAPHQL_REQUEST",
"nodeSlug": "gaiia-public-api-gateway-graphql-request",
"allowSkipExecution": false,
"outputMapper": "({ output }) => {\n const assignee = output.response.inventoryItem?.assignation?.assignee;\n if (!assignee || assignee.__typename !== 'Account') {\n workflow.fail('Inventory item is not assigned to an account.', 'NO_ACCOUNT');\n }\n return { id: assignee.id, gaiiaId: assignee.readableId };\n}",
"inputs": [
{
"key": "query",
"type": "LITERAL",
"value": "string:query IpAssignmentWorkflowInventoryItemQuery($id: ID!) {\n inventoryItem(id: $id) {\n id\n assignation {\n assignee {\n ... on Account { id readableId }\n }\n }\n }\n}"
},
{
"key": "variables",
"type": "FUNCTION",
"value": "({ state }) => ({ id: state.input.objectId })"
}
],
"nextSteps": [
{
"slug": "find-account-cpes"
}
]
},
{
"name": "Find account CPEs",
"slug": "find-account-cpes",
"type": "GAIIA_FIND_ACCOUNT_CPES",
"nodeSlug": "find-account-cpes",
"allowSkipExecution": false,
"inputs": [
{
"key": "accountId",
"type": "FUNCTION",
"value": "({ state }) => state.steps['find-inventory-item'].output.id"
}
],
"nextSteps": [
{
"slug": "find-account-internet-plans"
}
]
},
{
"name": "Find account Internet plans",
"slug": "find-account-internet-plans",
"type": "GAIIA_FIND_ACCOUNT_INTERNET_PLANS",
"nodeSlug": "find-account-internet-plans",
"allowSkipExecution": false,
"inputs": [
{
"key": "accountId",
"type": "FUNCTION",
"value": "({ state }) => state.steps['find-inventory-item'].output.id"
}
],
"nextSteps": [
{
"slug": "preseem-update-service"
}
]
},
{
"name": "Preseem update service",
"slug": "preseem-update-service",
"type": "PRESEEM_REST_REQUEST",
"nodeSlug": "preseem-rest-request",
"allowSkipExecution": false,
"inputs": [
{
"key": "body",
"type": "FUNCTION",
"value": "({ state }) => {\n const { gaiiaId } = state.steps['find-inventory-item'].output;\n const [{ slug }] = state.steps['find-account-internet-plans'].output.internetPlans;\n const cpes = state.steps['find-account-cpes'].output.accountCpe;\n const eventMac = state.input.payload.macAddress;\n const macAddresses = [\n ...new Set([\n ...cpes.map(({ macAddress }) => macAddress),\n eventMac\n ])\n ];\n return {\n id: String(gaiiaId),\n attachments: macAddresses\n .filter(Boolean)\n .map((macAddress) => ({ cpe_mac: macAddress.toLowerCase() })),\n account: String(gaiiaId),\n package: slug\n };\n }"
},
{
"key": "method",
"type": "LITERAL",
"value": "string:PUT"
},
{
"key": "path",
"type": "FUNCTION",
"value": "({ state }) => /services/${state.steps['find-inventory-item'].output.gaiiaId}"
}
],
"nextSteps": [
{
"slug": "end"
}
]
}
],
"trigger": {
"nextSteps": [
{
"slug": "find-inventory-item"
}
],
"type": "EVENT",
"events": [
"inventory_item.ip_address_assigned",
"inventory_item.ip_address_unassigned"
]
}
}

502
gaiia/uv.lock generated Normal file
View file

@ -0,0 +1,502 @@
version = 1
revision = 3
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.15'",
"python_full_version < '3.15'",
]
[[package]]
name = "anyio"
version = "4.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "idna" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
]
[[package]]
name = "ast-serialize"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a9/9d/912fefab0e30aee6a3af8a62bbea4a81b29afa4ba2c973d31170620a26de/ast_serialize-0.3.0.tar.gz", hash = "sha256:1bc3ca09a63a021376527c4e938deedd11d11d675ce850e6f9c7487f5889992b", size = 60689, upload-time = "2026-04-30T23:24:48.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6f/57/a54d4de491d6cdd7a4e4b0952cc3ca9f60dcefa7b5fb48d6d492debe1649/ast_serialize-0.3.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3a867927df59f76a18dc1d874a0b2c079b42c58972dca637905576deb0912e14", size = 1182966, upload-time = "2026-04-30T23:23:57.376Z" },
{ url = "https://files.pythonhosted.org/packages/ee/9e/a5db014bb0f91b209236b57c429389e31290c0093532b8436d577699b2fa/ast_serialize-0.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a6fb063bf040abf8321e7b8113a0554eda445ffc508aa51287f8808886a5ae22", size = 1171316, upload-time = "2026-04-30T23:23:59.63Z" },
{ url = "https://files.pythonhosted.org/packages/15/59/fd55133e478c4326f60a11df02573bf7ccb2ac685810b50f1803d0f68053/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5075cd8482573d743586779e5f9b652a015e37d4e95132d7e5a9bc5c8f483d8f", size = 1232234, upload-time = "2026-04-30T23:24:01.168Z" },
{ url = "https://files.pythonhosted.org/packages/cc/79/0ca1d26357ecb4a697d74d00b73ef3137f24c140424125393a0de820eb09/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:41560b27794f4553b0f77811e9fb325b77db4a2b39018d437e09932275306e66", size = 1233437, upload-time = "2026-04-30T23:24:03.151Z" },
{ url = "https://files.pythonhosted.org/packages/53/3e/7078ec94dd6e124b8e028ac77016a4f13c83fa1c145790f2e68f3816998b/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b967c01ca74909c5d90e0fe4393401e2cc5da5ebd9a6262a19e45ffd3757dec8", size = 1440188, upload-time = "2026-04-30T23:24:04.717Z" },
{ url = "https://files.pythonhosted.org/packages/21/16/cca7195ef55a012f8013c3442afa91d287a0a36dcf88b480b262475135b3/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:424ebb8f46cd993f7cec4009d119312d8433dd90e6b0df0499cd2c91bdcc5af9", size = 1254211, upload-time = "2026-04-30T23:24:06.18Z" },
{ url = "https://files.pythonhosted.org/packages/a0/0f/f3d4dfae67dee6580534361a6343367d34217e7d25cff858bd1d8f03b8ed/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14b1d566b56e2ee70b11fec1de7e0b94ec7cd83717ec7d189967841a361190e", size = 1255973, upload-time = "2026-04-30T23:24:07.772Z" },
{ url = "https://files.pythonhosted.org/packages/14/41/55fbfe02c42f40fbe3e74eda167d977d555ff720ce1abfa08515236efd88/ast_serialize-0.3.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7ba30b18735f047ec11103d1ab92f4789cf1fea1e0dc89b04a2f5a0632fd79de", size = 1298629, upload-time = "2026-04-30T23:24:09.4Z" },
{ url = "https://files.pythonhosted.org/packages/28/36/7d2501cacc7989fb8504aa9da2a2022a174200a59d4e6639de4367a57fdd/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ea0754cb7b0f682ebb005ffb0d18f8d17993490d9c289863cd69cacc4ab8df", size = 1408435, upload-time = "2026-04-30T23:24:11.013Z" },
{ url = "https://files.pythonhosted.org/packages/03/e7/54e3b469c3fa0bf9cd532fa643d1d33b73303f8d70beac3e366b68dd64b7/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:a0c5aa1073a5ba7b2abaa4b54abe8b8d75c4d1e2d54a2ff70b0ca6222fea5728", size = 1508174, upload-time = "2026-04-30T23:24:12.635Z" },
{ url = "https://files.pythonhosted.org/packages/b5/2a/9b9621865b02c60539e26d9b114a312b4fa46aa703e33e79317174bfea21/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4e52650d834c1ea7791969a361de2c54c13b2fb4c519ec79445fa8b9021a147d", size = 1502354, upload-time = "2026-04-30T23:24:14.186Z" },
{ url = "https://files.pythonhosted.org/packages/34/dd/f138bc5c43b0c414fdd12eefe15677839323078b6e75301ad7f96cd26d45/ast_serialize-0.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15bd6af3f136c61dae27805eb6b8f3269e85a545c4c27ffe9e530ead78d2b36d", size = 1450504, upload-time = "2026-04-30T23:24:16.076Z" },
{ url = "https://files.pythonhosted.org/packages/68/cf/97ef9e1c315601db74365955c8edd3292e3055500d6317602815dbdf08ae/ast_serialize-0.3.0-cp314-cp314t-win32.whl", hash = "sha256:d188bfe37b674b49708497683051d4b571366a668799c9b8e8a94513694969d9", size = 1058662, upload-time = "2026-04-30T23:24:17.535Z" },
{ url = "https://files.pythonhosted.org/packages/f8/d6/e2c3483c31580fdb623f92ad38d2f856cde4b9205a3e6bd84760f3de7d82/ast_serialize-0.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5832c2fdf8f8a6cf682b4cfcf677f5eaf39b4ddbc490f5480cfccdd1e7ce8fa1", size = 1100349, upload-time = "2026-04-30T23:24:18.992Z" },
{ url = "https://files.pythonhosted.org/packages/ab/89/29abcb1fe18a429cda60c6e0bbd1d6e90499339842a2f548d7567542357e/ast_serialize-0.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:670f177188d128fb7f9f15b5ad0e1b553d22c34e3f584dcb83eb8077600437f0", size = 1072895, upload-time = "2026-04-30T23:24:20.706Z" },
{ url = "https://files.pythonhosted.org/packages/bc/93/72abad83966ed6235647c9f956417dc1e17e997696388521910e3d1fa3f4/ast_serialize-0.3.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ec2fafa5e4313cc8feed96e436ebe19ac7bc6fa41fbc2827e826c48b9e4c3a9", size = 1190024, upload-time = "2026-04-30T23:24:22.486Z" },
{ url = "https://files.pythonhosted.org/packages/85/4f/eb88584b2f0234e581762011208ca203252bf6c98e59b4769daa571f3576/ast_serialize-0.3.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ef6d3c08b7b4cd29b48410338e134764a00e76d25841eb02c1084e868c888ecc", size = 1178633, upload-time = "2026-04-30T23:24:24.35Z" },
{ url = "https://files.pythonhosted.org/packages/56/51/cf1ec1ff3e616373d0dcbd5fad502e0029dc541f13ab642259762a7d127f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d841424f41b886e98044abc80769c14a956e6e5ccd5fb5b0d9f5ead72be18a4", size = 1241351, upload-time = "2026-04-30T23:24:25.987Z" },
{ url = "https://files.pythonhosted.org/packages/0d/44/68fcf50478cf1093f2d423f034ae06453122c8b415d8e21a44668eca485d/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d21453734ad39367ede5d37efe4f59f830ce1c09f432fc72a90e368f77a4a3e7", size = 1239582, upload-time = "2026-04-30T23:24:27.808Z" },
{ url = "https://files.pythonhosted.org/packages/9d/c1/a6c9fa284eceb5fc6f21347e968445a051d7ca2c4d34e6a04314646dbcee/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5e110cdce2a347e1dd987529c88ef54d26f67848dce3eba1b3b2cc2cf085c94", size = 1448853, upload-time = "2026-04-30T23:24:29.534Z" },
{ url = "https://files.pythonhosted.org/packages/23/5f/8ad3829a09e4e8c5328a53ce7d4711d660944e3e164c5f6abcc2c8f27167/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6e23a98e57560a055f5c4b68700a0fd5ce483d2814c23140b3638c7f5d1e61", size = 1262204, upload-time = "2026-04-30T23:24:31.482Z" },
{ url = "https://files.pythonhosted.org/packages/25/13/44aa28d97f10e25247e8576b5f6b2795d4fa1a80acc88acc942c508d06f7/ast_serialize-0.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c9e763d70293d65ce1e1ea8c943140c68d0953f0268c7ee0998f2e07f77dd0", size = 1266458, upload-time = "2026-04-30T23:24:33.088Z" },
{ url = "https://files.pythonhosted.org/packages/d8/58/b3a8be3777cd3744324fd5cec0d80d37cd96fc7cbb0fb010e03dff1e870f/ast_serialize-0.3.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4388a1796c228f1ce5c391426f7d21a0003ad3b47f677dbeded9bd1a85c7209f", size = 1308700, upload-time = "2026-04-30T23:24:34.657Z" },
{ url = "https://files.pythonhosted.org/packages/13/03/f8312d6b57f5471a9dc7946f22b8798a1fc296d38c25766223aacadec42c/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5283cdcc0c64c3d8b9b688dc6aaa012d9c0cf1380a7f774a6bae6a1c01b3205a", size = 1416724, upload-time = "2026-04-30T23:24:36.562Z" },
{ url = "https://files.pythonhosted.org/packages/50/5d/13fc3789a7abac00559da2e2e9f386db4612aa1f84fc53d09bf714c37545/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ef88cc5842a5d7a6ac09dc0d5fc2c98f5d276c1f076f866d55047ce886785b", size = 1515441, upload-time = "2026-04-30T23:24:38.018Z" },
{ url = "https://files.pythonhosted.org/packages/eb/b9/7ab43fc7a23b1f970281093228f5f79bed6edeed7a3e672bde6d7a832a58/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cc14bf402bdc0978594ecce783793de2c7470cd4f5cd7eb286ca97ed8ff7cba9", size = 1510522, upload-time = "2026-04-30T23:24:39.798Z" },
{ url = "https://files.pythonhosted.org/packages/56/ec/d75fc2b788d319f1fad77c14156896f31afdfc68af85b505e5bdebcb9592/ast_serialize-0.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11eae0cf1b7b3e0678133cc2daa974ea972caf02eb4b3aa062af6fa9acd52c57", size = 1460917, upload-time = "2026-04-30T23:24:41.305Z" },
{ url = "https://files.pythonhosted.org/packages/95/74/f99c81193a2725911e1911ae567ed27c2f2419332c7f3537366f9d238cac/ast_serialize-0.3.0-cp39-abi3-win32.whl", hash = "sha256:2db3dd99de5e6a5a11d7dda73de8750eb6e5baaf25245adf7bdcfe64b6108ae2", size = 1067804, upload-time = "2026-04-30T23:24:43.091Z" },
{ url = "https://files.pythonhosted.org/packages/16/81/76af00c47daa151e89f98ae21fbbcb2840aaa9f5766579c4da76a3c57188/ast_serialize-0.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:a2cd125adccf7969470621905d302750cd25951f22ea430d9a25b7be031e5549", size = 1105561, upload-time = "2026-04-30T23:24:44.578Z" },
{ url = "https://files.pythonhosted.org/packages/bd/46/d3ec57ad500f598d1554bd14ce4df615960549ab2844961bc4e1f5fbd174/ast_serialize-0.3.0-cp39-abi3-win_arm64.whl", hash = "sha256:0dd00da29985f15f50dc35728b7e1e7c84507bccfea1d9914738530f1c72238a", size = 1077165, upload-time = "2026-04-30T23:24:46.377Z" },
]
[[package]]
name = "backports-asyncio-runner"
version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
]
[[package]]
name = "certifi"
version = "2026.4.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "exceptiongroup"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
]
[[package]]
name = "gaiia"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
]
[package.optional-dependencies]
dev = [
{ name = "mypy" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "respx" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.27" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" },
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8" },
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" },
{ name = "respx", marker = "extra == 'dev'", specifier = ">=0.21" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.5" },
]
provides-extras = ["dev"]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.14"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/b1/efac073e0c297ecf2fb33c346989a529d4e19164f1759102dee5953ee17e/idna-3.14.tar.gz", hash = "sha256:466d810d7a2cc1022bea9b037c39728d51ae7dad40d480fc9b7d7ecf98ba8ee3", size = 198272, upload-time = "2026-05-10T20:32:15.935Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6c/3c/3f62dee257eb3d6b2c1ef2a09d36d9793c7111156a73b5654d2c2305e5ce/idna-3.14-py3-none-any.whl", hash = "sha256:e677eaf072e290f7b725f9acf0b3a2bd55f9fd6f7c70abe5f0e34823d0accf69", size = 72184, upload-time = "2026-05-10T20:32:14.295Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "librt"
version = "0.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/10/37fd9e9ba96cb0bd742dfb20fc3d082e54bdbec759d7300df927f360ef07/librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f", size = 141706, upload-time = "2026-05-10T18:15:16.129Z" },
{ url = "https://files.pythonhosted.org/packages/cf/72/1b1466f358e4a0b728051f69bc27e67b432c6eaa2e05b88db49d3785ae0d/librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45", size = 142605, upload-time = "2026-05-10T18:15:18.148Z" },
{ url = "https://files.pythonhosted.org/packages/ca/85/ed26dd2f6bc9a0baf48306433e579e8d354d70b2bcb78134ed950a5d0e1e/librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c", size = 476555, upload-time = "2026-05-10T18:15:19.569Z" },
{ url = "https://files.pythonhosted.org/packages/66/fe/11891191c0e0a3fd617724e891f6e67a71a7658974a892b9a9a97fdb2977/librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33", size = 468434, upload-time = "2026-05-10T18:15:20.87Z" },
{ url = "https://files.pythonhosted.org/packages/6f/50/5ec949d7f9ce1a07af903aa3e13abb98b717923bdead6e719b2f824ccc07/librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884", size = 496918, upload-time = "2026-05-10T18:15:22.616Z" },
{ url = "https://files.pythonhosted.org/packages/ea/c4/177336c7524e34875a38bf668e88b193a6723a4eb4045d07f74df6e1506c/librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280", size = 490334, upload-time = "2026-05-10T18:15:24.2Z" },
{ url = "https://files.pythonhosted.org/packages/13/1f/da3112f7569eda3b49f9a2629bae1fe059812b6085df16c885f6454dff49/librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c", size = 511287, upload-time = "2026-05-10T18:15:26.226Z" },
{ url = "https://files.pythonhosted.org/packages/fa/94/03fec301522e172d105581431223be56b27594ff46440ebfbb658a3735d5/librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb", size = 517202, upload-time = "2026-05-10T18:15:27.965Z" },
{ url = "https://files.pythonhosted.org/packages/b7/6e/339f6e5a7b413ce014f1917a756dae630fe59cc99f34153205b1cb540901/librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783", size = 497517, upload-time = "2026-05-10T18:15:29.614Z" },
{ url = "https://files.pythonhosted.org/packages/cd/43/acdd5ce317cb46e8253ca9bfbdb8b12e68a24d745949336a7f3d5fb79ba0/librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0", size = 538878, upload-time = "2026-05-10T18:15:30.928Z" },
{ url = "https://files.pythonhosted.org/packages/29/b5/7a25bb12e3172839f647f196b3e988318b7bb1ca7501732a225c4dce2ec0/librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89", size = 100070, upload-time = "2026-05-10T18:15:32.551Z" },
{ url = "https://files.pythonhosted.org/packages/c6/0d/ebbcf4d77999c02c937b05d2b90ff4cd4dcc7e9a365ba132329ac1fe7a0f/librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4", size = 117918, upload-time = "2026-05-10T18:15:33.678Z" },
{ url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" },
{ url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" },
{ url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" },
{ url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" },
{ url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" },
{ url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" },
{ url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" },
{ url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" },
{ url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" },
{ url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" },
{ url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" },
{ url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" },
{ url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" },
{ url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" },
{ url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" },
{ url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" },
{ url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" },
{ url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" },
{ url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" },
{ url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" },
{ url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" },
{ url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" },
{ url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" },
{ url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" },
{ url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" },
{ url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" },
{ url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" },
{ url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" },
{ url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" },
{ url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" },
{ url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" },
{ url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" },
{ url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" },
{ url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" },
{ url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" },
{ url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" },
{ url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" },
{ url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" },
{ url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" },
{ url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" },
{ url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" },
{ url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" },
{ url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" },
{ url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" },
{ url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" },
{ url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" },
{ url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" },
{ url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" },
{ url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" },
{ url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" },
{ url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" },
{ url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" },
{ url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" },
{ url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" },
{ url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" },
{ url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" },
{ url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" },
{ url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" },
{ url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" },
{ url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" },
{ url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" },
{ url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" },
{ url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" },
]
[[package]]
name = "mypy"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ast-serialize" },
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/71/d351dca3e9b30da2328ee9d445c88b8388072808ebfbc49eb69d30b67749/mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", size = 14778792, upload-time = "2026-05-11T18:36:23.605Z" },
{ url = "https://files.pythonhosted.org/packages/2f/45/7d51594b644c17c0bcf74ed8cd5fc33b324276d708e8506f220b70dab9d9/mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", size = 13645739, upload-time = "2026-05-11T18:37:22.752Z" },
{ url = "https://files.pythonhosted.org/packages/65/01/455c31b170e9468265074840bf18863a8482a24103fdaabe4e199392aa5f/mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", size = 14074199, upload-time = "2026-05-11T18:35:09.292Z" },
{ url = "https://files.pythonhosted.org/packages/41/5a/93093f0b29a9e982deafde698f740a2eb2e05886e79ccf0594c7fd5413a3/mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", size = 14953128, upload-time = "2026-05-11T18:31:57.678Z" },
{ url = "https://files.pythonhosted.org/packages/7f/2f/a196f5331d96170ad3d28f144d2aba690d4b2911381f68d51e489c7ab82a/mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", size = 15249378, upload-time = "2026-05-11T18:33:00.101Z" },
{ url = "https://files.pythonhosted.org/packages/54/de/94d321cc12da9f71341ac0c270efbed5c725750c7b4c334d957de9a087d9/mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", size = 11060994, upload-time = "2026-05-11T18:33:18.848Z" },
{ url = "https://files.pythonhosted.org/packages/e1/62/0c27ca55219a7c764a7fb88c7bb2b7b2f9780ade8bbf16bc8ed8400eef6b/mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", size = 9976743, upload-time = "2026-05-11T18:31:25.554Z" },
{ url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" },
{ url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" },
{ url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" },
{ url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" },
{ url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" },
{ url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" },
{ url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" },
{ url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" },
{ url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" },
{ url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" },
{ url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" },
{ url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" },
{ url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" },
{ url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" },
{ url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" },
{ url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" },
{ url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" },
{ url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" },
{ url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" },
{ url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" },
{ url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" },
{ url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" },
{ url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" },
{ url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" },
{ url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" },
{ url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" },
{ url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" },
{ url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" },
{ url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" },
{ url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" },
{ url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" },
{ url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" },
{ url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" },
{ url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" },
{ url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "packaging"
version = "26.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
name = "pathspec"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "pytest"
version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
name = "pytest-asyncio"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" },
{ name = "pytest" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" },
]
[[package]]
name = "respx"
version = "0.23.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/98/4e55c9c486404ec12373708d015ebce157966965a5ebe7f28ff2c784d41b/respx-0.23.1.tar.gz", hash = "sha256:242dcc6ce6b5b9bf621f5870c82a63997e8e82bc7c947f9ffe272b8f3dd5a780", size = 29243, upload-time = "2026-04-08T14:37:16.008Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1d/4a/221da6ca167db45693d8d26c7dc79ccfc978a440251bf6721c9aaf251ac0/respx-0.23.1-py2.py3-none-any.whl", hash = "sha256:b18004b029935384bccfa6d7d9d74b4ec9af73a081cc28600fffc0447f4b8c1a", size = 25557, upload-time = "2026-04-08T14:37:14.613Z" },
]
[[package]]
name = "ruff"
version = "0.15.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" },
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" },
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" },
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" },
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" },
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" },
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" },
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" },
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" },
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" },
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" },
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" },
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" },
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" },
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" },
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" },
]
[[package]]
name = "tomli"
version = "2.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
{ url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
{ url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
{ url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
{ url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
{ url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
{ url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
{ url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
{ url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
{ url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
{ url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
{ url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
{ url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
{ url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
{ url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
{ url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
{ url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
{ url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
{ url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
{ url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
{ url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
{ url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
{ url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
{ url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
{ url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
{ url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
{ url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
{ url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
{ url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
{ url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
{ url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
{ url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
{ url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
{ url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
{ url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
{ url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
{ url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
{ url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
{ url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
{ url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
{ url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
{ url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
{ url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
{ url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
# 2026-05-14 13:40:52 by RouterOS 7.21.4
# 2026-07-10 10:54:21 by RouterOS 7.21.4
# software id = K4QG-8NQV
#
# model = RB5009UG+S+
@ -218,15 +218,15 @@ add add-arp=yes address-pool=mgmt interface=management lease-script="{\
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=494-cust-pd-1 prefix=2606:1c80:1a01::/48 prefix-length=56
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250 local-address=\
204.110.188.94 remote-address=public use-upnp=no
add change-tcp-mss=yes dhcpv6-pd-pool=494-cust-pd-1 dns-server=\
204.110.191.240,204.110.191.20 local-address=100.64.175.254 name=494 \
on-down="{\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.175.254 name=494 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
@ -334,8 +334,9 @@ add change-tcp-mss=yes dhcpv6-pd-pool=494-cust-pd-1 dns-server=\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat remote-ipv6-prefix-pool=494-cust-pd-1 use-upnp=\
no
\n" only-one=yes remote-address=cgnat remote-ipv6-prefix-pool=\
494-cust-pd-1 use-upnp=no
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue type
add kind=fq-codel name=fq-codel
/queue interface
@ -355,10 +356,10 @@ add as=393837 name=bgp-instance-1 router-id=10.254.254.111
add disabled=no id=10.254.254.111 name=id-1 select-dynamic-id=any \
select-from-vrf=main
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=ospf-instance-1 \
originate-default=never out-filter-chain=ospf-out router-id=id-1
add in-filter-chain=ospf-in name=default-v2 originate-default=never \
out-filter-chain=ospf-out redistribute=connected router-id=id-1
/routing ospf area
add disabled=no instance=ospf-instance-1 name=ospf-area-1
add instance=default-v2 name=backbone-v2
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
@ -389,8 +390,8 @@ set tcp-syncookies=yes
/ipv6 settings
set accept-router-advertisements=no
/interface pppoe-server server
add default-profile=494 disabled=no interface=494 max-mru=1492 max-mtu=1492 \
one-session-per-host=yes service-name=494
add authentication=mschap2 default-profile=494 disabled=no interface=494 \
max-mru=1492 max-mtu=1492 one-session-per-host=yes service-name=494
/ip address
add address=10.254.254.111 comment=Loopback interface=lo network=\
10.254.254.111
@ -403,22 +404,35 @@ add address=100.64.175.254/20 interface=494 network=100.64.160.0
add interface=ether1
# Interface not active
add interface=ether3
/ip dhcp-server alert
add disabled=no interface=494 on-alert=rogue-dhcp valid-server=\
DC:2C:6E:DD:87:58
/ip dhcp-server lease
add address=10.10.175.10 mac-address=78:8A:20:AC:C5:32
add address=10.10.175.11 mac-address=58:C1:7A:75:4D:F2
/ip dhcp-server network
add address=10.10.160.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.175.254 ntp-server=204.110.191.19
add address=100.64.160.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.175.254 ntp-server=204.110.191.19
add address=10.10.160.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.175.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.160.0/20 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=100.64.175.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=154.66.115.255 list=CPF
add address=10.0.0.0/8 list=CPF
add address=192.168.0.0/16 list=CPF
add address=172.16.0.0/12 list=CPF
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether2-climax
add action=accept chain=forward comment=\
@ -502,12 +516,12 @@ set propagate-ttl=no
/ppp aaa
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.188.94 timeout=300ms
add address=204.110.191.2 require-message-auth=no service=ppp src-address=\
204.110.188.94 timeout=300ms
add accounting-backup=yes address=45.76.56.5 disabled=yes \
require-message-auth=no service=ppp src-address=204.110.188.94 timeout=\
add address=204.110.191.248 secret=dyhhtcUft3CJtDfYFG1S6 service=ppp \
src-address=204.110.188.94 timeout=2s
add address=204.110.191.2 secret=dyhhtcUft3CJtDfYFG1S6 service=ppp \
src-address=204.110.188.94 timeout=2s
add accounting-backup=yes address=45.76.56.5 disabled=yes secret=\
dyhhtcUft3CJtDfYFG1S6 service=ppp src-address=204.110.188.94 timeout=\
300ms
/routing bfd configuration
add disabled=no interfaces=ether2-climax min-rx=200ms min-tx=200ms \
@ -529,12 +543,11 @@ add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1A00::/44 || dst==2\
606:1c80:0:10::111/128 || (dst-len==64 && dst==2606:1c80:0:17::/64)) { acc\
ept; } else { reject; }"
/routing ospf interface-template
add area=ospf-area-1 auth=sha512 auth-id=1 disabled=no interfaces=\
ether2-climax type=ptp use-bfd=no
add area=ospf-area-1 disabled=no passive
add area=backbone-v2 cost=10 interfaces=ether2-climax priority=1 type=ptp \
use-bfd=no
add area=backbone-v2 passive
/routing ospf static-neighbor
add address=10.250.1.70%ether2-climax area=ospf-area-1 disabled=no \
poll-interval=10s
add address=10.250.1.70%ether2-climax area=*1 disabled=no poll-interval=10s
/snmp
set contact=graham@vntx.net enabled=yes location="33.208204, -96.462530"
/system clock
@ -559,3 +572,7 @@ set auto-upgrade=yes
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2025-02-16 start-time=03:40:00
/system script
add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""

View file

@ -1,4 +1,4 @@
# 2026-05-14 13:40:53 by RouterOS 7.21.4
# 2026-07-10 10:54:21 by RouterOS 7.21.4
# software id = FUVS-HCM5
#
# model = CCR1009-7G-1C-1S+
@ -117,16 +117,18 @@ add address-pool=mgmt interface=mgmt lease-script="{\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=10m name=mgmt
\n" lease-time=1h name=mgmt
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=982-cust-pd-1 prefix=2606:1c80:1301::/48 prefix-length=56
/ppp profile
add dhcpv6-pd-pool=982-cust-pd-1 local-address=100.64.63.253 name=982 \
on-down="{\
set *0 dns-server=204.110.191.240,204.110.191.250
add change-tcp-mss=yes dhcpv6-pd-pool=982-cust-pd-1 dns-server=\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.63.253 name=982 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
@ -234,7 +236,9 @@ add dhcpv6-pd-pool=982-cust-pd-1 local-address=100.64.63.253 name=982 \
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat remote-ipv6-prefix-pool=982-cust-pd-1
\n" only-one=yes remote-address=cgnat remote-ipv6-prefix-pool=\
982-cust-pd-1
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue type
add kind=fq-codel name=fq-codel
/queue interface
@ -290,16 +294,13 @@ add bridge=mgmt ingress-filtering=no interface=vlan10_ether6 \
internal-path-cost=10 path-cost=10
add bridge=982 interface=combo1
/ip firewall connection tracking
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip settings
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
set tcp-syncookies=yes
/ipv6 settings
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
set accept-router-advertisements=no
/interface pppoe-server server
add authentication=mschap2 default-profile=982 disabled=no interface=982 \
@ -311,6 +312,9 @@ add address=10.10.63.254/20 interface=mgmt network=10.10.48.0
add address=204.110.188.126/27 interface=982 network=204.110.188.96
add address=100.64.63.254/20 comment="New CGNAT subnet" interface=982 \
network=100.64.48.0
/ip dhcp-server alert
add disabled=no interface=mgmt on-alert=rogue-dhcp valid-server=\
DC:2C:6E:66:50:BF
/ip dhcp-server lease
add address=10.10.63.2 mac-address=F4:92:BF:2F:29:7E
add address=10.10.63.3 mac-address=F4:92:BF:2F:29:93
@ -320,12 +324,14 @@ add address=10.10.63.6 mac-address=F4:92:BF:2F:08:38
add address=10.10.63.1 client-id=1:d0:21:f9:f0:f5:23 mac-address=\
D0:21:F9:F0:F5:23
/ip dhcp-server network
add address=10.10.48.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.63.254 ntp-server=204.110.191.19
add address=100.64.28.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.31.254 ntp-server=204.110.191.19
add address=10.10.48.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.63.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.28.0/22 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.31.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=100.64.35.199 comment="Scott Anderson (2203)" list=BusinessUltra
add address=100.64.35.199 comment="Scott Anderson (2203)" list=Active
@ -369,9 +375,17 @@ add address=100.64.35.186 comment="Rick Beckham (464)" list=Active
add address=100.64.35.185 comment="Kris Dougherty (421)" list=\
ResidentialAdvanced
add address=100.64.35.185 comment="Kris Dougherty (421)" list=Active
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether7-380
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether7-380
add action=fasttrack-connection chain=forward comment=\
@ -446,15 +460,14 @@ add interface=ether7-380
/mpls settings
set propagate-ttl=no
/ppp aaa
set use-radius=yes
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.188.126 timeout=300ms
add address=204.110.191.2 disabled=yes require-message-auth=no service=ppp \
add address=204.110.191.248 secret=wj61jc2gitzeomx6 service=ppp src-address=\
204.110.188.126 timeout=2s
add address=204.110.191.2 disabled=yes secret=wj61jc2gitzeomx6 service=ppp \
src-address=204.110.188.126 timeout=300ms
add accounting-backup=yes address=45.76.56.5 disabled=yes \
require-message-auth=no service=ppp src-address=204.110.188.126 timeout=\
300ms
add accounting-backup=yes address=45.76.56.5 disabled=yes secret=\
wj61jc2gitzeomx6 service=ppp src-address=204.110.188.126 timeout=300ms
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing bgp connection
@ -470,8 +483,8 @@ add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1300::/44 || dst==2\
606:1c80:0:10::110/128 || (dst-len==64 && dst==2606:1c80:0:14::/64)) { acc\
ept; } else { reject; }"
/routing ospf interface-template
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=ether7-380 \
networks=10.250.1.32/29 priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth-id=1 auth-key="" cost=10 disabled=no interfaces=\
ether7-380 networks=10.250.1.32/29 priority=1 type=ptp use-bfd=no
add area=backbone-v2 disabled=no passive
/snmp
set contact=graham@vntx.net enabled=yes location="33.148902, -96.495811"
@ -490,9 +503,12 @@ add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add name=reboot on-event="/system reboot" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2024-09-16 start-time=03:35:00
/system script
add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""

View file

@ -0,0 +1,4 @@
# Add gaiia user with full access to all MikroTik routers
# First try to update existing user, then add if it doesn't exist
/user set [find name=gaiia] password=ordQnwVQWyUAnLMTbJgyk0xH6Z group=full
/user add name=gaiia password=ordQnwVQWyUAnLMTbJgyk0xH6Z group=full

View file

@ -0,0 +1,3 @@
/routing ospf interface-template set numbers=*D use-bfd=no
/routing ospf interface-template set numbers=*8 use-bfd=no
/routing ospf interface-template set numbers=*7 use-bfd=no

View file

@ -0,0 +1,2 @@
/routing ospf interface-template set numbers=*6 use-bfd=no
/routing ospf interface-template set numbers=*7 use-bfd=no

View file

@ -0,0 +1 @@
/routing ospf interface-template set numbers=*2 use-bfd=no

View file

@ -0,0 +1,3 @@
/routing ospf interface-template set numbers=*2 use-bfd=no
/routing ospf interface-template set numbers=*6 use-bfd=no
/routing ospf interface-template set numbers=*E use-bfd=no

View file

@ -1,4 +1,4 @@
# 2026-05-14 13:40:52 by RouterOS 7.21.4
# 2026-07-10 10:54:21 by RouterOS 7.21.4
# software id = UETF-WF31
#
# model = CCR2004-16G-2S+
@ -41,7 +41,7 @@ add name=cgnat ranges=100.64.7.1-100.64.7.250
add name=public-temp ranges=204.110.190.1-204.110.190.99
add name=climax-mgmt ranges=10.10.16.1-10.10.30.254
/ip dhcp-server
add address-pool=climaxtower interface=climax-bridge name=dhcp1
add address-pool=cgnat interface=climax-bridge lease-time=1h name=dhcp1
add address-pool=climax-mgmt interface=mgmt lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
@ -132,15 +132,16 @@ add address-pool=climax-mgmt interface=mgmt lease-script="{\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" name=climax-mgmt
\n" lease-time=1h name=climax-mgmt
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=climax-cust-pd-1 prefix=2606:1c80:1101::/48 prefix-length=56
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250
add bridge-learning=no change-tcp-mss=yes dhcpv6-pd-pool=climax-cust-pd-1 \
dns-server=204.110.191.240,204.110.191.20 local-address=cgnat name=pppoe \
on-down="{\
dns-server=204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
cgnat name=pppoe on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
@ -248,8 +249,10 @@ add bridge-learning=no change-tcp-mss=yes dhcpv6-pd-pool=climax-cust-pd-1 \
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat remote-ipv6-prefix-pool=climax-cust-pd-1 \
use-compression=no use-encryption=no use-ipv6=no use-mpls=no use-upnp=no
\n" only-one=yes remote-address=cgnat remote-ipv6-prefix-pool=\
climax-cust-pd-1 use-compression=no use-encryption=no use-ipv6=no \
use-mpls=no use-upnp=no
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue type
add kind=fq-codel name=fq-codel
/queue interface
@ -296,7 +299,6 @@ add bridge=climax-bridge ingress-filtering=no interface=ether8-michael \
add bridge=climax-bridge ingress-filtering=no interface=ether2 \
internal-path-cost=10 multicast-router=disabled path-cost=10
add bridge=climax-bridge interface=sfp-sfpplus1
add bridge=mgmt interface=vlan10_ether7
add bridge=mgmt interface=vlan10_sfp-sfpplus1
/ip firewall connection tracking
set enabled=yes icmp-timeout=30s tcp-close-wait-timeout=1m \
@ -310,7 +312,7 @@ set max-neighbor-entries=8192 tcp-syncookies=yes
/ipv6 settings
set accept-router-advertisements=no
/interface pppoe-server server
add authentication=chap,mschap2 default-profile=pppoe disabled=no interface=\
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
climax-bridge max-mru=1492 max-mtu=1492 one-session-per-host=yes \
service-name=Climax
/ip address
@ -324,17 +326,33 @@ add address=10.250.1.70/29 interface=ether5-494 network=10.250.1.64
add address=10.250.1.94/29 interface=ether4-380-airfiber24 network=\
10.250.1.88
add address=10.10.31.254/20 interface=mgmt network=10.10.16.0
/ip dhcp-server alert
add disabled=no interface=climax-bridge on-alert=rogue-dhcp valid-server=\
F4:1E:57:6B:41:C1
/ip dhcp-server network
add address=10.0.102.0/24 dns-server=9.9.9.9 gateway=10.0.102.254
add address=10.10.16.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.31.254 ntp-server=204.110.191.19
add address=100.64.4.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.7.254 ntp-server=204.110.191.19
add address=204.110.188.32/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.62 ntp-server=204.110.191.19
add address=10.0.102.0/24 dns-server=204.110.191.240,204.110.191.250 gateway=\
10.0.102.254 ntp-server=23.150.40.242,108.61.215.221,162.254.225.152
add address=10.10.16.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.31.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.4.0/22 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.7.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=204.110.188.32/27 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=204.110.188.62 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether5-494
add action=accept chain=forward comment=\
@ -350,6 +368,11 @@ add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-verona-11ghz
add action=passthrough chain=unused-hs-chain comment=\
"place hotspot rules here" disabled=yes
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether3-culleoka-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
ether3-culleoka-11ghz
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
@ -459,6 +482,7 @@ add advertise-dns=yes interface=climax-bridge other-configuration=yes
add interface=ether6-verona-11ghz mpls-mtu=1508
add interface=ether4-380-airfiber24 mpls-mtu=1508
add interface=ether5-494 mpls-mtu=1508
add interface=ether3-culleoka-11ghz mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.102 transport-addresses=10.254.254.102 vrf=\
main
@ -466,14 +490,15 @@ add disabled=no lsr-id=10.254.254.102 transport-addresses=10.254.254.102 vrf=\
add interface=ether6-verona-11ghz
add interface=ether4-380-airfiber24
add interface=ether5-494
add interface=ether3-culleoka-11ghz
/mpls settings
set propagate-ttl=no
/ppp aaa
set interim-update=1h use-radius=yes
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.248 secret=ich7cp0j1qpe0801 service=ppp,hotspot,dhcp \
src-address=204.110.188.62 timeout=2s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.2 secret=ich7cp0j1qpe0801 service=ppp,hotspot,dhcp \
src-address=204.110.188.62 timeout=2s
/radius incoming
set accept=yes
@ -494,12 +519,14 @@ add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1100::/44 || dst==2\
==2606:1c80:0:17::/64 || dst==2606:1c80:0:20::/64 || dst==2606:1c80:0:21::\
/64))) { accept; } else { reject; }"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-verona-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether5-494 priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4-380-airfiber24 priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether6-verona-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 cost=10 disabled=no interfaces=ether5-494 priority=1 \
type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether4-380-airfiber24 priority=1 type=ptp use-bfd=no
/routing ospf static-neighbor
add address=10.250.1.25%ether6-verona-11ghz area=backbone-v2 disabled=no \
poll-interval=10s
@ -522,3 +549,7 @@ add address=0.us.pool.ntp.org
set channel=long-term
/system routerboard settings
set auto-upgrade=yes enter-setup-on=delete-key
/system script
add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""

View file

@ -0,0 +1 @@
/ip firewall connection tracking set tcp-established-timeout=1d

View file

@ -1,11 +1,9 @@
# 2026-05-14 13:40:53 by RouterOS 7.21.4
# software id = XS5B-41QR
# 2026-07-10 10:54:21 by RouterOS 7.21.4
# software id = QL96-4NKM
#
# model = CCR1009-7G-1C-1S+
# serial number = 8495073BCE3B
# model = CCR2004-16G-2S+
# serial number = HMC0B6HX8XX
/interface ethernet
set [ find default-name=combo1 ] l2mtu=9000 name=combo1-380 rx-flow-control=\
auto tx-flow-control=auto
set [ find default-name=ether1 ] l2mtu=9000 name=ether1-982-60ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether2 ] l2mtu=9000 name=ether2-office \
@ -18,16 +16,16 @@ set [ find default-name=ether5 ] l2mtu=2024 name=ether5-climax \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether6 ] l2mtu=2024 name=ether6-culleoka-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether7 ] disabled=yes l2mtu=9000 name=ether7-380tower \
set [ find default-name=ether7 ] l2mtu=9000 name=ether7-380tower \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether8 ] l2mtu=9000 name=ether8-380-lan \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus1 ] l2mtu=9000 name=\
sfp-sfpplus1-edge-preseem rx-flow-control=auto tx-flow-control=auto
/interface vlan
add interface=combo1-380 name=vlan10_combo1 vlan-id=10
add interface=ether8-380-lan name=vlan10_ether8 vlan-id=10
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
@ -36,7 +34,7 @@ add name=sstp ranges=172.16.91.1-172.16.91.253
add name=380 ranges=100.64.8.1-100.64.11.230
add name=380-mgmt ranges=10.10.64.1-10.10.79.230
/ip dhcp-server
add address-pool=380 interface=combo1-380 lease-script="{\
add address-pool=380 interface=ether8-380-lan lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
@ -127,7 +125,7 @@ add address-pool=380 interface=combo1-380 lease-script="{\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=380
add address-pool=380-mgmt interface=vlan10_combo1 lease-script="{\
add address-pool=380-mgmt interface=vlan10_ether8 lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
@ -221,13 +219,14 @@ add address-pool=380-mgmt interface=vlan10_combo1 lease-script="{\
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=core-cust-pd-1 prefix=2606:1c80:1401::/48 prefix-length=56
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250
add change-tcp-mss=yes dhcpv6-pd-pool=core-cust-pd-1 dns-server=\
204.110.191.240,204.110.191.20 local-address=100.64.11.253 name=380 \
on-down="{\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.11.253 name=380 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
@ -335,13 +334,13 @@ add change-tcp-mss=yes dhcpv6-pd-pool=core-cust-pd-1 dns-server=\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=380 remote-ipv6-prefix-pool=core-cust-pd-1 use-ipv6=no \
use-upnp=no
set *FFFFFFFE local-address=172.16.91.254 remote-address=sstp
\n" only-one=yes remote-address=380 remote-ipv6-prefix-pool=\
core-cust-pd-1 use-ipv6=no use-upnp=no
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250 local-address=\
172.16.91.254 remote-address=sstp
/queue type
add kind=fq-codel name=FQ_Codel
/queue interface
set combo1-380 queue=FQ_Codel
set ether1-982-60ghz queue=FQ_Codel
set ether2-office queue=FQ_Codel
set ether3-edge-direct queue=FQ_Codel
@ -349,10 +348,11 @@ set ether4-newhope queue=FQ_Codel
set ether5-climax queue=FQ_Codel
set ether6-culleoka-11ghz queue=FQ_Codel
set ether7-380tower queue=FQ_Codel
set ether8-380-lan queue=FQ_Codel
set sfp-sfpplus1-edge-preseem queue=FQ_Codel
/routing bgp instance
add as=393837 cluster-id=10.254.254.253 name=bgp-instance-1 router-id=\
10.254.254.253
add as=393837 cluster-id=10.254.254.253 disabled=no name=bgp-instance-1 \
router-id=10.254.254.253
/routing bgp template
set default as=65530 disabled=no
/routing id
@ -362,6 +362,8 @@ add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected,static router-id=id-1 routing-table=main
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
/routing rip instance
add disabled=no name=rip-instance-1
/routing table
add fib name=testnat
/snmp community
@ -383,9 +385,8 @@ set accept-router-advertisements=no max-neighbor-entries=8192 \
soft-max-neighbor-entries=8191
/interface pppoe-server server
add authentication=mschap2 default-profile=380 disabled=no interface=\
combo1-380 max-mru=1492 max-mtu=1492 service-name=380
/interface sstp-server server
set certificate=ServerCA enabled=yes
ether8-380-lan max-mru=1492 max-mtu=1492 one-session-per-host=yes \
service-name=380
/ip address
add address=204.110.191.185/30 interface=sfp-sfpplus1-edge-preseem network=\
204.110.191.184
@ -400,18 +401,36 @@ add address=10.250.2.6/29 interface=ether2-office network=10.250.2.0
add address=10.250.1.38/29 interface=ether1-982-60ghz network=10.250.1.32
add address=10.250.1.54/29 interface=ether6-culleoka-11ghz network=\
10.250.1.48
add address=10.250.1.246/29 disabled=yes interface=combo1-380 network=\
add address=10.250.1.246/29 disabled=yes interface=ether8-380-lan network=\
10.250.1.240
add address=100.64.11.254/22 interface=combo1-380 network=100.64.8.0
add address=10.10.79.254/20 interface=vlan10_combo1 network=10.10.64.0
add address=100.64.11.254/22 interface=ether8-380-lan network=100.64.8.0
add address=10.10.79.254/20 interface=vlan10_ether8 network=10.10.64.0
/ip dhcp-server alert
add disabled=no interface=ether8-380-lan on-alert=rogue-dhcp valid-server=\
D0:EA:11:77:4E:5D
/ip dhcp-server network
add address=10.10.64.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.79.254 ntp-server=204.110.191.19
add address=100.64.8.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.11.254 ntp-server=204.110.191.19
add address=10.10.64.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.79.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.8.0/22 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.11.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether1-982-60ghz
add action=accept chain=forward comment=\
@ -441,8 +460,8 @@ add action=accept chain=forward comment=\
sfp-sfpplus1-edge-preseem
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=drop chain=forward disabled=yes src-address-list=suspended
add action=drop chain=forward disabled=yes dst-address-list=suspended
add action=drop chain=forward src-address-list=suspended
add action=drop chain=forward dst-address-list=suspended
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip ipsec profile
@ -457,25 +476,29 @@ add src-address=100.64.0.0/10
add src-address=204.110.188.0/22
add action=deny
/ip route
add check-gateway=ping disabled=no dst-address=0.0.0.0/0 gateway=\
add check-gateway=ping disabled=yes dst-address=0.0.0.0/0 gateway=\
204.110.191.186
add disabled=no distance=1 dst-address=204.110.188.32/27 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=no distance=1 dst-address=10.250.1.88/29 gateway=10.250.1.94 \
add disabled=yes distance=1 dst-address=10.250.1.88/29 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=yes dst-address=10.254.254.101/32 gateway=10.250.1.94
add disabled=yes dst-address=10.254.254.102/32 gateway=10.250.1.94
add disabled=yes dst-address=10.250.1.64/29 gateway=10.250.1.94
add disabled=yes distance=1 dst-address=10.250.1.24/29 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=10.250.1.8/29 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=10.10.16.0/20 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=yes distance=1 dst-address=10.10.0.0/20 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add dst-address=10.254.254.101/32 gateway=10.250.1.94
add dst-address=10.254.254.102/32 gateway=10.250.1.94
add dst-address=10.250.1.64/29 gateway=10.250.1.94
add dst-address=10.250.1.24/29 gateway=10.250.1.94
add dst-address=10.250.1.8/29 gateway=10.250.1.94
add dst-address=10.10.16.0/20 gateway=10.250.1.94
add dst-address=10.10.0.0/20 gateway=10.250.1.94
add dst-address=204.110.191.0/27 gateway=10.250.1.94
add dst-address=100.64.4.0/22 gateway=10.250.1.94
add dst-address=100.64.12.0/22 gateway=10.250.1.94
add dst-address=100.64.0.0/22 gateway=10.250.1.94
add dst-address=204.110.188.224/27 gateway=10.250.1.94
add dst-address=10.250.1.144/29 gateway=10.250.1.94
add disabled=yes dst-address=10.250.1.144/29 gateway=10.250.1.94
add dst-address=10.254.254.111/32 gateway=10.250.1.94
add dst-address=100.64.160.0/20 gateway=10.250.1.94
add dst-address=204.110.188.64/27 gateway=10.250.1.94
@ -507,9 +530,9 @@ add comment="lowry loopback (via newhope)" distance=1 dst-address=\
/ip service
set ftp disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set ssh address=204.110.188.0/22,10.0.0.0/8,0.0.0.0/0 port=1022
set www address=204.110.188.225/32 port=2080
set winbox address=204.110.188.0/22,10.0.0.0/8
set winbox address=0.0.0.0/0
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=myCa
/ip ssh
@ -530,13 +553,13 @@ add address=2606:1c80:0:14::1 advertise=no comment="P2P core<->982 60GHz" \
interface=ether1-982-60ghz
add address=2606:1c80:0:15::1 advertise=no comment="P2P core<->culleoka" \
interface=ether6-culleoka-11ghz
add address=2606:1c80:1400::1 comment="380 mgmt LAN" interface=vlan10_combo1
add address=2606:1c80:1400::1 comment="380 mgmt LAN" interface=vlan10_ether8
add address=2606:1c80:1400:1::1 comment="380 customer LAN (PPPoE+DHCP)" \
interface=combo1-380
interface=ether8-380-lan
/ipv6 dhcp-server
add dhcp-option=v6-dns interface=vlan10_combo1 name=core-mgmt-stateless
add address-pool=core-cust-pd-1 dhcp-option=v6-dns interface=combo1-380 name=\
core-cust-pd-server
add dhcp-option=v6-dns interface=vlan10_ether8 name=core-mgmt-stateless
add address-pool=core-cust-pd-1 dhcp-option=v6-dns interface=ether8-380-lan \
name=core-cust-pd-server
/ipv6 firewall filter
add action=accept chain=input comment=established connection-state=\
established,related
@ -556,8 +579,8 @@ add action=accept chain=forward comment="v6 outbound (all vntx sources)" \
add action=drop chain=forward comment="default deny"
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
add advertise-dns=yes interface=vlan10_combo1 other-configuration=yes
add advertise-dns=yes interface=combo1-380 other-configuration=yes
add advertise-dns=yes interface=vlan10_ether8 other-configuration=yes
add advertise-dns=yes interface=ether8-380-lan other-configuration=yes
/mpls interface
add interface=ether5-climax mpls-mtu=1508
add interface=ether6-culleoka-11ghz mpls-mtu=1508
@ -576,14 +599,10 @@ add interface=ether1-982-60ghz
/mpls settings
set propagate-ttl=no
/ppp aaa
set use-radius=yes
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.191.185 timeout=300ms
add address=204.110.191.2 require-message-auth=no service=ppp src-address=\
204.110.191.185 timeout=300ms
add accounting-backup=yes address=45.76.56.5 require-message-auth=no service=\
ppp src-address=204.110.191.185 timeout=300ms
add address=204.110.191.248 secret=TpYBHmrHd8vRelg service=ppp src-address=\
204.110.191.185 timeout=2s
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing bgp connection
@ -640,27 +659,34 @@ add chain=v6-rr-out disabled=no rule="if (dst==::/0 || dst-len==44 || dst-len=\
=64 || dst-len==128) { accept; } else { reject; }"
add chain=v6-rr-in disabled=no rule="if (dst==::/0 || dst-len==44 || dst-len==\
64 || dst-len==128) { accept; } else { reject; }"
/routing ospf area range
add area=backbone-v2 disabled=no prefix=::/0
/routing ospf interface-template
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=\
sfp-sfpplus1-edge-preseem priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether5-climax priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4-newhope priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth-id=1 cost=100 disabled=no interfaces=\
ether3-edge-direct priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=ether2-office \
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether6-culleoka-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether4-newhope priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth-id=1 cost=10 disabled=yes interfaces=ether2-office \
priority=1 type=nbma use-bfd=no
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=\
ether1-982-60ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-culleoka-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether5-climax priority=1 type=ptp use-bfd=no
/routing ospf static-neighbor
add address=204.110.191.186%sfp-sfpplus1-edge-preseem area=backbone-v2 \
disabled=no
add address=10.250.2.1%ether2-office area=backbone-v2 disabled=no
/routing rip interface-template
add disabled=no instance=rip-instance-1
/routing rip static-neighbor
add address="" disabled=no instance=*1
add address="" disabled=no instance=rip-instance-1
/routing rule
add action=lookup-only-in-table disabled=yes src-address=10.10.0.0/16 table=\
testnat
@ -688,11 +714,17 @@ add address=216.229.4.66
add address=1.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
set auto-upgrade=yes enter-setup-on=delete-key
/system scheduler
add name=reboot on-event="/system reboot\
\n" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2025-01-07 start-time=04:00:00
/system script
add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""
/tool graphing interface
add
/tool graphing queue

View file

@ -0,0 +1,75 @@
# On the DHCP server "culleoka" (ether2-netonix), update the lease script
# to add/remove hotspot bypass entries based on Option 82 presence.
# Devices WITH Option 82 → bypassed (internet access)
# Devices WITHOUT Option 82 → fall through to 0.0.0.0/0 type=blocked
/ip dhcp-server set lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/production/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cdc24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Deassign: remove hotspot bypass for this IP\
\n /ip hotspot ip-binding remove [find address=\$leaseActIP];\
\n\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment sent to iptrack for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED deassignment iptrack attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment sent to gaiia for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED deassignment gaiia attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$leaseActIP] agent-remote-id];\
\n\
\n # If Option 82 remote ID is present, bypass hotspot for this IP\
\n :if (\$remoteID != \"\") do={\
\n /ip hotspot ip-binding add address=\$leaseActIP mac-address=\$leaseActMAC server=hotspot1 to-address=\$leaseActIP type=bypassed;\
\n :log info \"DHCP Option 82 present for \$leaseActMAC / \$leaseActIP (remoteID=\$remoteID) - hotspot bypassed\";\
\n } else={\
\n :log warning \"DHCP NO Option 82 for \$leaseActMAC / \$leaseActIP - staying blocked by hotspot\";\
\n }\
\n\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment sent to iptrack for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED assignment iptrack attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment sent to gaiia for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED assignment gaiia attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}" [find name=culleoka]

View file

@ -0,0 +1,24 @@
/system scheduler remove [find name=opt82-hotspot-sync]
/system script remove [find name=opt82-hotspot-sync]
:delay 1s
/system script add name=opt82-hotspot-sync owner=admin policy=read,write,test source={
# Add bypass for any bound lease with Option 82
/ip dhcp-server lease foreach={
:if ($activeAgentRemoteId != "" && $status = "bound") do={
:if ([/ip hotspot ip-binding find where address=$activeAddress server=hotspot1 mac-address=$activeMacAddress] = "") do={
/ip hotspot ip-binding add address=$activeAddress mac-address=$activeMacAddress server=hotspot1 to-address=$activeAddress type=bypassed
:log info "opt82-poll: bypassed $activeMacAddress / $activeAddress"
}
}
}
# Remove bypasses for IPs that no longer have any bound lease
/ip hotspot ip-binding foreach={
:if ($server = "hotspot1" && $mac-address != "") do={
:if ([/ip dhcp-server lease find where activeAddress=$address] = "") do={
/ip hotspot ip-binding remove $.
:log info "opt82-poll: removed bypass for $mac-address / $address (no lease)"
}
}
}
}
/system scheduler add name=opt82-hotspot-sync interval=10s on-event="/system script run opt82-hotspot-sync" policy=read,write,test

View file

@ -1,4 +1,7 @@
# 2026-05-14 13:40:53 by RouterOS 7.21.4
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
# 2026-07-10 17:44:20 by RouterOS 7.21.5
# software id = HVP9-3G0K
#
# model = CCR1009-7G-1C-1S+
@ -41,10 +44,11 @@ set [ find default=yes ] ip-type=ipv4 use-network-apn=no
set [ find default=yes ] supplicant-identity=MikroTik
/ip hotspot profile
add dns-name=culleoka.vntx.net hotspot-address=10.100.111.254 login-by=mac \
mac-auth-mode=mac-as-username-and-password name=hsprof1
mac-auth-mode=mac-as-username-and-password name=hsprof1 \
radius-accounting=no use-radius=yes
/ip hotspot
add addresses-per-mac=unlimited idle-timeout=none interface=ether2-netonix \
name=hotspot1 profile=hsprof1
add addresses-per-mac=unlimited disabled=no idle-timeout=none interface=\
ether2-netonix name=hotspot1 profile=hsprof1
/ip hotspot user profile
set [ find default=yes ] add-mac-cookie=no on-logout=" /ip hotspot host remove\
\_[find where address=\94\$address\94 and !authorized and !bypassed]" \
@ -149,7 +153,7 @@ add address-pool=cpe interface=mgmt lease-script="{\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=cpe use-radius=accounting
\n" lease-time=1h name=cpe
add address-pool=culleoka-tower interface=culleoka-tower lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
@ -208,21 +212,24 @@ add address-pool=culleoka-tower interface=culleoka-tower lease-script="{\
\n\
\n}\
\n" lease-time=1h name=culleoka-tower
add address-pool=clayton disabled=yes interface=vlan_30_clayton lease-time=\
10m name=clayton
add address-pool=clayton disabled=yes interface=vlan_30_clayton lease-time=1h \
name=clayton
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=culleoka-cust-pd-1 prefix=2606:1c80:1601::/48 prefix-length=56
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250
add change-tcp-mss=yes dhcpv6-pd-pool=culleoka-cust-pd-1 dns-server=\
204.110.191.240,204.110.191.20 local-address=100.64.27.253 name=pppoe \
on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.27.253 name=pppoe on-down="{\
\n#:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/pro\
duction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0\
a7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url \"https://workflows.gaiia.systems/tenant_eHH8oyd82GgaXstfRhHW\
Di/workflow_38tqPV3f6PF3dVJHe22KVR\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
@ -274,9 +281,11 @@ add change-tcp-mss=yes dhcpv6-pd-pool=culleoka-cust-pd-1 dns-server=\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n#:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/pro\
duction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0\
a7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url \"https://workflows.gaiia.systems/tenant_eHH8oyd82GgaXstfRhHW\
Di/workflow_38tqPV3f6PF3dVJHe22KVR\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
@ -327,9 +336,10 @@ add change-tcp-mss=yes dhcpv6-pd-pool=culleoka-cust-pd-1 dns-server=\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" only-one=no remote-address=culleoka-cgnat remote-ipv6-prefix-pool=\
\n" only-one=yes remote-address=culleoka-cgnat remote-ipv6-prefix-pool=\
culleoka-cust-pd-1 use-compression=no use-encryption=no use-ipv6=no \
use-mpls=no use-upnp=no
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue simple
add burst-limit=1/1 burst-threshold=1/1 burst-time=1s/1s limit-at=1/1 \
max-limit=1/1 name=Inactive packet-marks=Inactive priority=2/2 target=""
@ -384,22 +394,29 @@ set tcp-syncookies=yes
/ipv6 settings
set accept-router-advertisements=no
/interface pppoe-server server
add accept-empty-service=no authentication=chap,mschap2 default-profile=pppoe \
disabled=no interface=ether2-netonix max-mtu=1492 service-name=culleoka
add default-profile=pppoe disabled=no interface=vlan_30_clayton max-mtu=1492 \
one-session-per-host=yes service-name=clayton
add default-profile=pppoe disabled=no interface=ether5-jeff-tv max-mtu=1492 \
one-session-per-host=yes service-name=jeff-tv
add default-profile=pppoe disabled=no interface=vlan100_netonix8 max-mtu=1492 \
one-session-per-host=yes service-name=service1
add default-profile=pppoe disabled=no interface=vlan101_netonix9 max-mtu=1492 \
one-session-per-host=yes service-name=service2
add default-profile=pppoe disabled=no interface=vlan102_netonix10 max-mtu=\
1492 one-session-per-host=yes service-name=service3
add default-profile=pppoe disabled=no interface=vlan103_netonix13 max-mtu=\
1492 one-session-per-host=yes service-name=service4
add default-profile=pppoe disabled=no interface=vlan104_netonix14 max-mtu=\
1492 one-session-per-host=yes service-name=service5
add accept-empty-service=no authentication=mschap2 default-profile=pppoe \
disabled=no interface=ether2-netonix max-mtu=1492 one-session-per-host=\
yes service-name=culleoka
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
vlan_30_clayton max-mtu=1492 one-session-per-host=yes service-name=\
clayton
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
ether5-jeff-tv max-mtu=1492 one-session-per-host=yes service-name=jeff-tv
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
vlan100_netonix8 max-mtu=1492 one-session-per-host=yes service-name=\
service1
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
vlan101_netonix9 max-mtu=1492 one-session-per-host=yes service-name=\
service2
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
vlan102_netonix10 max-mtu=1492 one-session-per-host=yes service-name=\
service3
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
vlan103_netonix13 max-mtu=1492 one-session-per-host=yes service-name=\
service4
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
vlan104_netonix14 max-mtu=1492 one-session-per-host=yes service-name=\
service5
/interface sstp-server server
set certificate=vntx.net.pem_0
/ip address
@ -504,10 +521,10 @@ add add-arp=yes address-pool=culleoka-cgnat dhcp-option-set=vntx interface=\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=culleoka use-radius=accounting
\n" lease-time=1h name=culleoka
/ip dhcp-server alert
add disabled=no interface=culleoka on-alert=rogue-dhcp valid-server=\
64:D1:54:D3:E1:50
add disabled=no interface=ether2-netonix on-alert=rogue-dhcp valid-server=\
64:D1:54:D3:E1:53
/ip dhcp-server lease
add address=10.10.111.14 mac-address=44:D9:E7:A8:58:C9
add address=10.10.111.50 mac-address=DC:9F:DB:6F:A6:24
@ -523,34 +540,56 @@ add address=10.10.111.11 mac-address=78:45:58:A2:EC:F9
add address=10.10.111.61 mac-address=78:8A:20:EC:76:DC
add address=10.10.111.60 mac-address=44:D9:E7:5A:A8:0E
/ip dhcp-server network
add address=10.0.104.0/24 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.0.104.254 ntp-server=204.110.191.19
add address=10.10.96.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.111.254 ntp-server=204.110.191.19
add address=100.64.24.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.27.254 ntp-server=204.110.191.19
add address=100.64.96.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.111.254 ntp-server=204.110.191.19
add address=204.110.188.128/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.158 ntp-server=204.110.191.19
add address=10.0.104.0/24 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.0.104.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=10.10.96.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.111.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.24.0/22 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.27.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.96.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.111.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=204.110.188.128/27 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=204.110.188.158 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dhcp-server option sets
add name=vntx options=*1
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=10.254.254.104 disabled=yes list=bgp-networks
add address=10.10.96.0/20 disabled=yes list=bgp-networks
add address=100.64.24.0/22 disabled=yes list=bgp-networks
add address=204.110.188.128/27 disabled=yes list=bgp-networks
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=passthrough chain=unused-hs-chain comment=\
"place hotspot rules here" disabled=yes
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether6-380-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-380-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether1-climax-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether1-climax-11ghz
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip firewall nat
add action=passthrough chain=unused-hs-chain comment=\
"place hotspot rules here" disabled=yes
/ip hotspot ip-binding
add address=100.64.27.97 type=bypassed
add address=100.64.25.4 type=bypassed
@ -641,23 +680,25 @@ add advertise-dns=yes interface=mgmt other-configuration=yes
add interface=mgmt prefix=2606:1c80:1600::/64
/mpls interface
add interface=ether6-380-11ghz mpls-mtu=1508
add interface=ether1-climax-11ghz mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.104 transport-addresses=10.254.254.104 vrf=\
main
/mpls ldp interface
add interface=ether6-380-11ghz
add interface=ether1-climax-11ghz
/mpls settings
set propagate-ttl=no
/ppp aaa
set interim-update=1h use-radius=yes
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.248 secret=x6ov8lwph4kxp0d7 service=ppp,hotspot,dhcp \
src-address=204.110.188.158 timeout=2s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.2 secret=x6ov8lwph4kxp0d7 service=ppp,hotspot,dhcp \
src-address=204.110.188.158 timeout=2s
add accounting-backup=yes address=45.76.56.5 disabled=yes \
require-message-auth=no service=ppp,hotspot,dhcp src-address=\
204.110.188.158 timeout=2s
add accounting-backup=yes address=45.76.56.5 disabled=yes secret=\
x6ov8lwph4kxp0d7 service=ppp,hotspot,dhcp src-address=204.110.188.158 \
timeout=2s
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing bgp connection
@ -673,10 +714,12 @@ add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1600::/44 || dst==2\
606:1c80:0:10::104/128 || (dst-len==64 && (dst==2606:1c80:0:15::/64 || dst\
==2606:1c80:0:21::/64))) { accept; } else { reject; }"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=20 disabled=no interfaces=\
ether1-climax-11ghz priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-380-11ghz priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=20 disabled=no \
interfaces=ether1-climax-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether6-380-11ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 disabled=no passive
/routing ospf static-neighbor
add address=10.250.1.14%ether1-climax-11ghz area=backbone-v2 comment=climax \
@ -720,6 +763,7 @@ add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add interval=5m name=remove_hotspot on-event=\
@ -735,3 +779,5 @@ add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
":log warning message=\"Rogue DHCP server detected!\""
/tool bandwidth-server
set enabled=no
/tool romon
set secrets=vntx1830

View file

@ -0,0 +1,3 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=dhcp1]
/ip dhcp-server set lease-time=1h [find name=dhcp2]

View file

@ -0,0 +1,2 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=mgmt]

View file

@ -0,0 +1,3 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=dhcp1]
/ip dhcp-server set lease-time=1h [find name=climax-mgmt]

View file

@ -0,0 +1,3 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=380]
/ip dhcp-server set lease-time=1h [find name=380-mgmt]

View file

@ -0,0 +1,5 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=culleoka]
/ip dhcp-server set lease-time=1h [find name=cpe]
/ip dhcp-server set lease-time=1h [find name=culleoka-tower]
/ip dhcp-server set lease-time=1h [find name=clayton]

View file

@ -0,0 +1,3 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=dhcp1]
/ip dhcp-server set lease-time=1h [find name=mgmt]

View file

@ -0,0 +1,4 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=newhope]
/ip dhcp-server set lease-time=1h [find name=management]
/ip dhcp-server set lease-time=1h [find name=newhope-tower]

View file

@ -0,0 +1,5 @@
# set all DHCP lease times to 1h
/ip dhcp-server set lease-time=1h [find name=verona-cpe]
/ip dhcp-server set lease-time=1h [find name=altoga-cpe]
/ip dhcp-server set lease-time=1h [find name=verona-tower]
/ip dhcp-server set lease-time=1h [find name=verona]

View file

@ -0,0 +1 @@
/ip dhcp-server lease set [find where dynamic=no] server=all

View file

@ -0,0 +1 @@
/ip dhcp-server set use-radius=no [find]

5
mikrotik-tool/diag.rsc Normal file
View file

@ -0,0 +1,5 @@
/routing ospf neighbor print
/interface print
/routing ospf interface-template print
/routing ospf interface print
/ip address print

View file

@ -0,0 +1,3 @@
/ip dns set servers=204.110.191.240,204.110.191.250
/ip dhcp-server network set [find] dns-server=204.110.191.240,204.110.191.250
/ppp profile set [find] dns-server=204.110.191.240,204.110.191.250

View file

@ -0,0 +1,6 @@
# Set fleet-wide DNS to Quad9 (9.9.9.9 and 149.112.112.112)
# Applies to: /ip dns, all DHCP networks, all PPPoE profiles
/ip dns set servers=9.9.9.9,149.112.112.112
/ip dhcp-server network set [find] dns-server=9.9.9.9,149.112.112.112
/ppp profile set [find] dns-server=9.9.9.9,149.112.112.112

View file

@ -0,0 +1,3 @@
/ppp profile set [find] dns-server=204.110.191.240,204.110.191.250
/ip dhcp-server network set [find] dns-server=204.110.191.240,204.110.191.250
/ip dns set servers=204.110.191.240,204.110.191.250

View file

@ -0,0 +1,2 @@
/ppp profile set [find] dns-server=204.110.191.240,204.110.191.250
/ip dhcp-server network set [find] dns-server=204.110.191.240,204.110.191.250

View file

@ -0,0 +1,16 @@
# Block incoming winbox from non-trusted IPs
# Run: ./mikrotik-tool script edge edge-block-winbox.rsc
# Add missing RFC 1918 private ranges to trusted list
/ip firewall address-list
add address=172.16.0.0/12 list=trusted comment="RFC 1918 private"
add address=192.168.0.0/16 list=trusted comment="RFC 1918 private"
# Restrict winbox service to trusted networks only
/ip service
set winbox address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16
# Firewall drop for winbox from non-trusted sources (belt and suspenders)
# Place before "Established Input" to catch new connection attempts early
/ip firewall filter
add chain=input action=drop dst-port=8291 protocol=tcp src-address-list=!trusted comment="block winbox from non-trusted" place-before="Established Input"

View file

@ -0,0 +1 @@
/ip firewall connection tracking set tcp-established-timeout=1d

View file

@ -1,4 +1,4 @@
# may/14/2026 13:40:52 by RouterOS 6.49.18
# jul/10/2026 10:54:22 by RouterOS 6.49.18
# software id = 8XZE-R7EJ
#
# model = CCR2004-1G-12S+2XS
@ -31,7 +31,9 @@ add name=servers-public ranges=204.110.191.193-204.110.191.217
add address-pool=380building disabled=no interface=sfp-sfpplus8-server-switch \
name=380-building
/ppp profile
add name=RWB_sstp_profile
set *0 dns-server=204.110.191.240,204.110.191.250
add dns-server=204.110.191.240,204.110.191.250 name=RWB_sstp_profile
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue simple
add burst-limit=1M/1M burst-threshold=1M/1M burst-time=1s/1s disabled=yes \
limit-at=1M/1M max-limit=1M/1M name=Delinquent packet-marks=Delenquent \
@ -205,6 +207,8 @@ add address=204.110.190.251/25 interface=cgnat network=204.110.190.128
add address=204.110.190.252/25 interface=cgnat network=204.110.190.128
add address=204.110.190.253/25 interface=cgnat network=204.110.190.128
add address=204.110.190.254/25 interface=cgnat network=204.110.190.128
add address=192.168.0.5/24 interface=sfp-sfpplus8-server-switch network=\
192.168.0.0
/ip dhcp-server lease
add address=10.0.0.249 client-id=1:0:21:9b:91:5c:b6 mac-address=\
00:21:9B:91:5C:B6 server=380-building
@ -219,10 +223,10 @@ add address=10.0.0.252 client-id=1:bc:24:11:f5:81:d6 mac-address=\
add address=10.0.0.251 client-id=1:bc:24:11:6b:73:53 mac-address=\
BC:24:11:6B:73:53 server=380-building
/ip dhcp-server network
add address=10.0.0.0/24 dns-server=9.9.9.9,1.1.1.1 domain=vntx.net gateway=\
10.0.0.254 ntp-server=23.150.41.122
add address=204.110.191.192/26 dns-server=\
204.110.191.240,204.110.191.250,1.1.1.1 gateway=204.110.191.254
add address=10.0.0.0/24 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.0.0.254 ntp-server=23.150.41.122
add address=204.110.191.192/26 dns-server=204.110.191.240,204.110.191.250 \
gateway=204.110.191.254
/ip dns
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
@ -267,6 +271,8 @@ add address=204.110.191.252 comment="Virtual RouterOS" list=BGP-NEIGHBORS
add address=204.110.188.0/22 list=bgp-networks
add address=151.243.11.0/24 list=abusive
add address=172.245.56.83 list=trusted
add address=172.16.0.0/12 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="BGP Input" dst-port=179 protocol=tcp
add action=accept chain=output comment="BGP Output" protocol=tcp src-port=179
@ -277,6 +283,10 @@ add action=accept chain=forward comment="BGP Forward Reply" protocol=tcp \
add action=accept chain=input comment="OSPF Input" protocol=ospf
add action=accept chain=output comment="OSPF Output" protocol=ospf
add action=accept chain=forward comment="OSPF Forward" protocol=ospf
add action=drop chain=input comment="block winbox from non-trusted" dst-port=\
8291 protocol=tcp src-address-list=!trusted
add action=drop chain=input comment="block winbox from non-trusted" dst-port=\
8291 protocol=tcp src-address-list=!trusted
add action=accept chain=input comment="Established Input" connection-state=\
established,related
add action=accept chain=forward comment="Established Forward" \
@ -530,6 +540,8 @@ set ftp disabled=yes
set www address=204.110.188.0/22 port=2080
set ssh address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10 port=1022
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set winbox address=\
204.110.188.0/22,10.0.0.0/8,100.64.0.0/10,172.16.0.0/12,192.168.0.0/16
set api-ssl address=0.0.0.0/0 certificate=server
/ip ssh
set always-allow-password-login=yes forwarding-enabled=remote strong-crypto=\

View file

@ -0,0 +1,3 @@
/routing ospf interface-template set [find where interfaces=ether4-newhope] auth=sha512 authentication-key=5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet use-bfd=no
/routing ospf interface-template set [find where interfaces=ether6-culleoka-11ghz] auth=sha512 authentication-key=5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet use-bfd=no
/routing ospf interface-template add area=backbone-v2 auth=sha512 auth-id=1 authentication-key=5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no interfaces=ether5-climax priority=1 type=ptp use-bfd=no

View file

@ -0,0 +1,38 @@
# home-recursive-failover.rsc
# Replace stateful netwatch enable/disable failover with stateless recursive
# check-gateway failover. Root cause of the "VNTX default stuck disabled"
# incident: netwatch down-script persists disabled=yes (survives reboots),
# and its probe to 4.2.2.2 is only glued to the VNTX path while the probe-pin
# route is active — so the check can die permanently (verona hotspot-binding
# loss) or leak out another WAN (TMO netwatch reads "up" today with the TMO
# interface physically down, answered via VNTX).
#
# New model (standard ROS recursive failover):
# - probe pin: 4.2.2.x/32 -> WAN gw %interface, scope=10, NO check-gateway.
# Stays active whenever the interface has link, so probes always egress
# the WAN under test regardless of any default-route state.
# - default: 0.0.0.0/0 gw=4.2.2.x target-scope=11 check-gateway=ping.
# check-gateway pings 4.2.2.x THROUGH the pin; 2 misses (~20s) -> route
# inactive (not disabled), first success -> active again. Nothing latches.
# - Detection is 10s-cadence/2-miss instead of netwatch's 2s/1s single
# packet — deliberate: the AF11 backbone has documented >1s RF bursts
# (same reason BFD was turned off fleet-wide 2026-05-09).
/ip route
# pins: remove check-gateway (the check belongs on the recursive default)
set [find comment="TMO probe pin"] check-gateway=none
set [find comment="VNTX probe pin"] check-gateway=none
set [find comment="Starlink probe pin"] check-gateway=none
# defaults: recurse through the probe IP, carry the check
set [find comment="Default via TMO (primary)"] gateway=4.2.2.1 check-gateway=ping
set [find comment="Default via VNTX (secondary)"] gateway=4.2.2.2 check-gateway=ping
# Starlink default already gateway=4.2.2.3 + check-gateway=ping (correct form)
# tmo table default (eweka policy routing) already recurses via 4.2.2.1;
# add the check so it goes inactive (falls back to main) when TMO upstream dies
set [find comment="tmo table default"] check-gateway=ping
# netwatch scripts are the latch — remove them
/tool netwatch
remove [find comment="TMO path"]
remove [find comment="VNTX path"]
remove [find comment="Starlink path"]

View file

@ -1,4 +1,4 @@
# 2026-05-14 13:40:52 by RouterOS 7.22.3
# 2026-07-17 08:45:20 by RouterOS 7.23.2
# software id = ZGNY-ZJW7
#
# model = RB5009UG+S+
@ -9,7 +9,6 @@ add admin-mac=74:4D:28:1A:67:0A auto-mac=no comment=defconf mtu=1500 name=\
add name=containers
add mtu=1500 name=docker port-cost-mode=short
add mtu=1500 name=dockers port-cost-mode=short
add disabled=yes mtu=1500 name=public
/interface ethernet
set [ find default-name=ether1 ] l2mtu=9578
set [ find default-name=ether2 ] l2mtu=9578
@ -20,12 +19,14 @@ set [ find default-name=ether6 ] l2mtu=9578 name=ether6-tmobile
set [ find default-name=ether7 ] l2mtu=9578 name=ether7-starlink
set [ find default-name=ether8 ] disabled=yes l2mtu=9578
set [ find default-name=sfp-sfpplus1 ] l2mtu=9586
/interface pppoe-client
add interface=ether8 max-mtu=1500 name=pppoe-out1 use-peer-dns=yes user=\
grahammcintire
/interface veth
add address=172.17.0.2/16 container-mac-address=4C:B3:A4:3A:BC:FF dhcp=no \
gateway=172.17.0.1 gateway6="" mac-address=4C:B3:A4:3A:BC:FE name=veth1
/container
add envlists=tailscale interface=veth1 layer-dir="" logging=yes name=\
tailscale-mikrotik:latest remote-image=\
fluent-networks/tailscale-mikrotik:latest root-dir=\
/disk1/containers/tailscale start-on-boot=yes workdir=/
/interface list
add comment=defconf name=WAN
add comment=defconf name=LAN
@ -40,12 +41,11 @@ add add-arp=yes address-pool=home bootp-lease-time=lease-time bootp-support=\
add address-pool=dhcp_pool1 interface=ether3-servers name=servers
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:4700:4700::1111''2606:4700:4700::1001'"
/ipv6 pool
add name=tunnerbroker prefix=2001:470:ba50::/48 prefix-length=48
/port
set 0 baud-rate=9600
/interface ppp-client
add apn=internet name=ppp-out1 port=usb1
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue type
set 0 kind=fq-codel
/routing table
@ -55,8 +55,9 @@ add comment="vntx v6 /56 egress via verona" disabled=no fib name=v6-verona
/snmp community
set [ find default=yes ] addresses=10.0.16.0/22,10.0.0.0/8,204.110.188.0/22 \
name=kdyyJrT0Mm
add addresses=::/0 authentication-protocol=SHA1 encryption-protocol=AES name=\
testtest security=private
add addresses=::/0 authentication-password=testtest authentication-protocol=\
SHA1 encryption-password=testtest encryption-protocol=AES name=testtest \
security=private
add addresses=10.0.16.0/22 name=testlocal
/system script
add dont-require-permissions=no name=api-ssl-certgen owner=admin policy=\
@ -72,11 +73,33 @@ add dont-require-permissions=no name=api-ssl-certgen owner=admin policy=\
ys-valid=825; /certificate sign \$certName ca=\$caName; /certificate set \
\$certName trusted=yes; /ip/service set api-ssl certificate=\$certName dis\
abled=no; /ip/service set api disabled=yes;"
/container
add envlists=tailscale interface=veth1 layer-dir="" logging=yes name=\
tailscale-mikrotik:latest remote-image=\
fluent-networks/tailscale-mikrotik:latest root-dir=\
/disk1/containers/tailscale start-on-boot=yes workdir=/
/zerotier
set zt1 identity="a5f13ab687:0:487e3dd9982223e1aa0b13752a061609180dd3926b46be2\
7527a299db6fb1959bc79f486a0e8e70a50a1c0c81c5048ced502f97a4db2cac167551339d\
137d3c2:8620c195d88a8ccd49c6264419b9bfcb9af6456cb1c0a35c83f7ed3396375fe446\
e18897162866ebcadb374304f379a5ab006e4f0c0e3bb59438fccfcccef0c1"
/app
set HA-otbr-matter required-hw-devices=ttyACM0:usb1:default:/dev/ttyACM0
set cinny firewall-redirects=8094:80:tcp:web
set goaway container-command-lines=goaway:none:docker.io/pommee/goaway:latest
set home-assistant container-command-lines=\
home-assistant:none:lscr.io/linuxserver/homeassistant
set lorawan-stack secrets="lorawan-stack__admin_password:="
set n8n firewall-redirects=5678:5678:tcp:web
set nextcloud container-command-lines="db:none:docker.io/postgres:17,redis:non\
e:docker.io/valkey/valkey:/bin/sh -c 'valkey-server --port 6379 --appendon\
ly yes --requirepass \$VALKEY_PASSWORD',server:none:docker.io/nextcloud:ap\
ache"
set otbr required-hw-devices=ttyACM0:usb1:default:/dev/ttyACM0
set pihole environment="pihole:FTLCONF_dns_listeningMode=all,pihole:FTLCONF_we\
bserver_api_password=password"
set redlib firewall-redirects=8087:8080:tcp:web
set solr container-command-lines=solr:none:docker.io/solr:latest
set uptime-kuma container-command-lines=\
uptime-kuma:none:docker.io/louislam/uptime-kuma:1
set zulip secrets="zulip__postgres_password:=,zulip__memcached_password:=,zuli\
p__rabbitmq_password:=,zulip__redis_password:=,zulip__secret_key:=,zulip__\
email_password:="
/container config
set registry-url=https://ghcr.io tmpdir=/disk1/pull
/container envs
@ -122,16 +145,16 @@ add address=204.110.191.1/27 interface=ether5-vntx-static network=\
add address=10.0.19.254/22 interface=bridge network=10.0.16.0
add address=10.0.15.254/21 interface=ether3-servers network=10.0.8.0
/ip dhcp-client
# Interface not active
add add-default-route=no interface=ether6-tmobile name=client1 use-peer-dns=\
no use-peer-ntp=no
# Interface not active
add add-default-route=no interface=ether7-starlink name=client2 use-peer-dns=\
no use-peer-ntp=no
/ip dhcp-server lease
add address=10.0.16.2 client-id=\
ff:85:d2:82:8a:0:2:0:0:ab:11:cb:63:d8:6c:a1:65:c1:58 comment=unifi \
mac-address=74:83:C2:1D:4C:51 server=server1
add address=10.0.16.251 client-id=1:34:98:b5:ae:bc:e3 mac-address=\
34:98:B5:AE:BC:E3 server=server1
add address=10.0.16.1 client-id=1:c8:7f:54:d0:4:2f mac-address=\
C8:7F:54:D0:04:2F server=server1
add address=10.0.19.250 client-id=\
@ -167,20 +190,18 @@ add address=10.0.15.253 client-id=1:78:9a:18:3f:cb:fe mac-address=\
add address=10.0.19.241 client-id=1:f4:92:bf:91:8a:61 mac-address=\
F4:92:BF:91:8A:61 server=server1
add address=10.0.15.21 mac-address=BC:24:11:98:1C:19 server=servers
add address=10.0.17.185 client-id=1:74:4d:bd:c5:87:cc mac-address=\
add address=10.0.16.11 client-id=1:74:4d:bd:c5:87:cc mac-address=\
74:4D:BD:C5:87:CC server=server1
add address=10.0.17.17 client-id=1:f0:24:f9:55:b8:94 mac-address=\
add address=10.0.16.14 client-id=1:f0:24:f9:55:b8:94 mac-address=\
F0:24:F9:55:B8:94 server=server1
add address=10.0.17.184 mac-address=50:02:91:38:EB:98 server=server1
add address=10.0.16.3 client-id=1:52:54:0:5c:f7:36 mac-address=\
52:54:00:5C:F7:36 server=server1
add address=10.0.17.25 client-id=1:20:f8:3b:9:49:cd mac-address=\
add address=10.0.16.15 client-id=1:20:f8:3b:9:49:cd mac-address=\
20:F8:3B:09:49:CD server=server1
add address=10.0.17.51 mac-address=40:F5:20:C5:9B:EE server=server1
add address=10.0.16.5 client-id=\
ff:ef:a8:c2:c6:0:1:0:1:31:4c:1f:7:b0:dc:ef:a8:c2:c6 mac-address=\
B0:DC:EF:A8:C2:C6 server=server1
add address=10.0.17.22 mac-address=EC:94:CB:AA:56:A3 server=server1
add address=10.0.16.13 mac-address=EC:94:CB:AA:56:A3 server=server1
add address=10.0.15.24 client-id=\
ff:23:7c:24:3e:0:2:0:0:ab:11:ad:b5:e:a0:e1:d9:51:1a mac-address=\
F6:86:CC:17:A7:F9 server=servers
@ -191,12 +212,21 @@ add address=10.0.15.30 mac-address=94:C6:91:A4:19:80 server=servers
add address=10.0.15.31 client-id=\
ff:a:12:17:11:0:1:0:1:31:8f:72:ff:7c:d3:a:12:17:11 mac-address=\
7C:D3:0A:12:17:11 server=servers
add address=10.0.16.10 mac-address=40:F5:20:C5:C7:5E server=server1
add address=10.0.16.12 client-id=1:40:4c:ca:69:9:6c mac-address=\
40:4C:CA:69:09:6C server=server1
add address=10.0.16.6 client-id=1:e8:f6:a:e0:c4:4 mac-address=\
E8:F6:0A:E0:C4:04 server=server1
add address=10.0.15.25 mac-address=BC:24:11:FB:42:B0 server=servers
add address=10.0.19.253 client-id=1:b8:69:f4:9f:b7:12 mac-address=\
B8:69:F4:9F:B7:12 server=server1
/ip dhcp-server network
add address=10.0.8.0/21 domain=mcintire.me gateway=10.0.15.254
add address=10.0.16.0/22 dns-server=10.0.19.250,9.9.9.9 domain=w5isp.com \
add address=10.0.8.0/21 dns-server=204.110.191.240,204.110.191.250 domain=\
mcintire.me gateway=10.0.15.254
add address=10.0.16.0/22 dns-server=1.1.1.1,204.110.191.250 domain=w5isp.com \
gateway=10.0.19.254
/ip dns
set servers=9.9.9.9,149.112.112.112
set servers=9.9.9.9,204.110.191.250
/ip dns static
add address=192.168.88.1 comment=defconf name=router.lan type=A
add address=10.0.16.31 comment=dhcp-lease-script_server1_lease-hostname name=\
@ -279,7 +309,15 @@ add address=10.0.17.189 list=iot-blocked
add address=news.eweka.nl list=eweka-tmo
add address=185.90.196.0/22 comment="eweka range" list=eweka-tmo
add address=81.171.92.0/23 comment="eweka range" list=eweka-tmo
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=input dst-address=0.0.0.0 protocol=udp src-address=\
104.238.146.79
add action=accept chain=forward dst-port=25565 in-interface=\
@ -388,7 +426,7 @@ add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
add action=dst-nat chain=dstnat disabled=yes dst-address=204.110.191.1 \
dst-port=8920 protocol=tcp to-addresses=10.0.16.1 to-ports=8920
add action=masquerade chain=srcnat src-address=172.17.0.0/24
add action=masquerade chain=srcnat out-interface=ether7-starlink
add action=masquerade chain=srcnat disabled=yes out-interface=ether7-starlink
/ip proxy
set port=8198 src-address=10.0.19.254
/ip route
@ -420,27 +458,25 @@ add disabled=no dst-address=204.110.188.0/22 gateway=204.110.191.30 \
routing-table=main
add disabled=no dst-address=100.64.0.0/16 gateway=204.110.191.30 \
routing-table=main
add disabled=no dst-address=10.43.0.0/16 gateway=204.110.191.2 routing-table=\
main
add comment=wigle.net disabled=no distance=1 dst-address=54.70.85.50/32 \
gateway=204.110.191.30 routing-table=main scope=30 target-scope=10
add dst-address=100.64.0.0/10 gateway=172.17.0.2
add comment="Default via TMO (primary)" disabled=no distance=1 dst-address=\
0.0.0.0/0 gateway=192.168.12.1 target-scope=11
add comment="Default via VNTX (secondary)" disabled=no distance=2 \
dst-address=0.0.0.0/0 gateway=204.110.191.30 target-scope=11
add check-gateway=ping comment="Default via TMO (primary)" disabled=no \
distance=1 dst-address=0.0.0.0/0 gateway=4.2.2.1 target-scope=11
add check-gateway=ping comment="Default via VNTX (secondary)" disabled=no \
distance=2 dst-address=0.0.0.0/0 gateway=4.2.2.2 target-scope=11
add check-gateway=ping comment="Default via Starlink (last\
\n resort)" distance=3 dst-address=0.0.0.0/0 gateway=4.2.2.3 \
target-scope=11
add comment="tmo table default" disabled=no dst-address=0.0.0.0/0 gateway=\
4.2.2.1 routing-table=tmo
add check-gateway=ping comment="TMO probe pin" distance=1 dst-address=\
\n resort)" disabled=yes distance=3 dst-address=0.0.0.0/0 gateway=\
4.2.2.3 target-scope=11
add check-gateway=ping comment="tmo table default" disabled=no dst-address=\
0.0.0.0/0 gateway=4.2.2.1 routing-table=tmo
add check-gateway=none comment="TMO probe pin" distance=1 dst-address=\
4.2.2.1/32 gateway=192.168.12.1%ether6-tmobile scope=10
add check-gateway=ping comment="VNTX probe pin" distance=1 dst-address=\
add check-gateway=none comment="VNTX probe pin" distance=1 dst-address=\
4.2.2.2/32 gateway=204.110.191.30%ether5-vntx-static scope=10
add check-gateway=ping comment="Starlink probe pin" distance=1 dst-address=\
4.2.2.3/32 gateway=192.168.1.1%ether7-starlink scope=10
add disabled=no distance=1 dst-address=192.168.100.0/24 gateway=\
add check-gateway=none comment="Starlink probe pin" disabled=yes distance=1 \
dst-address=4.2.2.3/32 gateway=192.168.1.1%ether7-starlink scope=10
add disabled=yes distance=1 dst-address=192.168.100.0/24 gateway=\
ether7-starlink routing-table=main scope=30 target-scope=10
/ip service
set ftp disabled=yes
@ -526,17 +562,3 @@ add allow-address=10.0.16.0/24
set allowed-interface-list=LAN
/tool mac-server mac-winbox
set allowed-interface-list=LAN
/tool netwatch
add comment="TMO path" down-script="/ip route disable [find comment=\"Default \
via TMO (primary)\"]; /ip route disable [find comment=\"tmo table default\
\"]" host=4.2.2.1 interval=2s timeout=1s type=simple up-script="/ip route \
enable [find comment=\"Default via TMO (primary)\"]; /ip route enable [fin\
d comment=\"tmo table default\"]"
add comment="VNTX path" down-script=\
"/ip route disable [find comment=\"Default via VNTX (secondary)\"]" host=\
4.2.2.2 interval=2s timeout=1s type=simple up-script=\
"/ip route enable [find comment=\"Default via VNTX (secondary)\"]"
add comment="Starlink path" down-script=\
"/ip route disable [find comment=\"Default via Starlink (last resort)\"]" \
host=4.2.2.3 interval=2s timeout=1s type=simple up-script=\
"/ip route enable [find comment=\"Default via Starlink (last resort)\"]"

View file

@ -1,4 +1,4 @@
# 2026-05-14 13:40:53 by RouterOS 7.21.4
# 2026-07-10 10:54:21 by RouterOS 7.21.4
# software id = 2I9X-PQZP
#
# model = CCR1009-7G-1C-1S+
@ -35,8 +35,7 @@ add name=video_http regexp="http/(0\\.9|1\\.0|1\\.1)[\\x20][1-5][0-9][0-9].*[\
\\x09-\\x0d].*[\\x09-\\x0d]content-type:.video/"
/ip hotspot profile
add dns-name=lowrycrossing.vntx.net hotspot-address=100.64.23.254 login-by=\
mac,http-chap mac-auth-mode=mac-as-username-and-password name=hsprof1 \
radius-interim-update=1h use-radius=yes
mac,http-chap mac-auth-mode=mac-as-username-and-password name=hsprof1
/ip hotspot
add idle-timeout=none interface=lowrycrossing name=hotspot1 profile=hsprof1
/ip hotspot user profile
@ -144,13 +143,14 @@ add address-pool=mgmt interface=management lease-script="{\
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=lowry-cust-pd-1 prefix=2606:1c80:1901::/48 prefix-length=56
/ppp profile
set *0 dns-server=204.110.191.240,204.110.191.250
add change-tcp-mss=yes dhcpv6-pd-pool=lowry-cust-pd-1 dns-server=\
204.110.191.20,204.110.191.240 local-address=100.64.159.254 name=pppoe \
on-down="{\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.159.254 name=pppoe on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
@ -261,6 +261,7 @@ add change-tcp-mss=yes dhcpv6-pd-pool=lowry-cust-pd-1 dns-server=\
\n" only-one=yes remote-address=cgnat remote-ipv6-prefix-pool=\
lowry-cust-pd-1 use-compression=no use-encryption=no use-ipv6=no \
use-upnp=no
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue type
add kind=pcq name=delinquent_download pcq-classifier=dst-address \
pcq-dst-address6-mask=64 pcq-rate=64k pcq-src-address6-mask=64 \
@ -374,8 +375,9 @@ set tcp-syncookies=yes
/ipv6 settings
set accept-router-advertisements=no
/interface pppoe-server server
add default-profile=pppoe disabled=no interface=lowrycrossing max-mtu=1492 \
mrru=1600 one-session-per-host=yes service-name=lowrycrossing
add authentication=mschap2 default-profile=pppoe disabled=no interface=\
lowrycrossing max-mtu=1492 one-session-per-host=yes service-name=\
lowrycrossing
/interface sstp-server server
set certificate=vntx.net.pem_0
/ip address
@ -487,6 +489,9 @@ add add-arp=yes address-pool=cgnat dhcp-option-set=vntx interface=\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=dhcp1
/ip dhcp-server alert
add disabled=no interface=lowrycrossing on-alert=rogue-dhcp valid-server=\
64:D1:54:D3:E2:1F
/ip dhcp-server lease
add address=10.10.159.13 mac-address=58:C1:7A:75:4D:66
add address=10.10.159.11 mac-address=00:04:56:C4:FE:9D
@ -494,16 +499,19 @@ add address=10.10.144.34 mac-address=00:04:56:60:49:43
add address=10.10.159.12 mac-address=58:C1:7A:71:A7:A8
add address=10.10.159.10 mac-address=00:04:56:D7:62:F6
/ip dhcp-server network
add address=10.10.144.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.159.254 ntp-server=204.110.191.19
add address=100.64.144.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.159.254 ntp-server=204.110.191.19
add address=204.110.188.192/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.222 ntp-server=204.110.191.19
add address=10.10.144.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.159.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.144.0/20 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=100.64.159.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=204.110.188.192/27 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=204.110.188.222 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dhcp-server option sets
add name=vntx options=*1
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=204.110.191.229 list=whitelist
add address=portal.vntx.net list=whitelist
@ -1222,7 +1230,15 @@ add address=100.64.23.25 comment="Abbie Andrews (1081)" list=Active
add address=100.64.23.12 comment="Nhu Morrison (2111)" list=\
ResidentialAdvanced
add address=100.64.23.12 comment="Nhu Morrison (2111)" list=Active
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether1-newhope
add action=accept chain=forward comment=\
@ -1241,7 +1257,7 @@ add address=10.100.0.144-10.100.158.254
add address=10.100.159.0/24 type=bypassed
add address=0.0.0.0/0 type=blocked
/ip hotspot user
add name=admin
add name=admin password=h8xd9tkryg
/ip hotspot walled-garden
add comment="place hotspot rules here" disabled=yes
/ip hotspot walled-garden ip
@ -1320,13 +1336,13 @@ add interface=ether1-newhope
/mpls settings
set propagate-ttl=no
/ppp aaa
set interim-update=1h use-radius=yes
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.248 secret=260oy5od2x2ltubh service=ppp,hotspot,dhcp \
src-address=204.110.188.222 timeout=3s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.2 secret=260oy5od2x2ltubh service=ppp,hotspot,dhcp \
src-address=204.110.188.222 timeout=3s
add accounting-backup=yes address=45.76.56.5 require-message-auth=no service=\
add accounting-backup=yes address=45.76.56.5 secret=260oy5od2x2ltubh service=\
ppp,hotspot,dhcp src-address=204.110.188.222 timeout=3s
/radius incoming
set accept=yes
@ -1345,8 +1361,9 @@ add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1900::/44 || dst==2\
606:1c80:0:10::109/128 || (dst-len==64 && dst==2606:1c80:0:18::/64)) { acc\
ept; } else { reject; }"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether1-newhope priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether1-newhope priority=1 type=ptp use-bfd=no
add area=backbone-v2 disabled=no passive
/snmp
set contact=graham@vntx.net enabled=yes location="33.160873, -96.540479"
@ -1417,5 +1434,7 @@ add dont-require-permissions=no name=rogue-dhcp owner=admin policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
":log warning message=\"Rogue DHCP server detected!\""
/tool e-mail
set certificate-verification=no from=<LowryCrossing@vntx.net> server=\
smtp.sendgrid.net tls=starttls user=veronanetworks
set certificate-verification=no from=<LowryCrossing@vntx.net> password=\
F9K2B2yhvE6YUnv server=smtp.sendgrid.net tls=starttls user=veronanetworks
/tool romon
set secrets=vntx1830

View file

@ -404,13 +404,21 @@ func exportRouter(cfg *Config, r Router) error {
return err
}
log.Printf("[%s] ssh %s:%d /export", r.Name, r.Host, sshPort)
body, err := runOverSSH(r, sshPort, "/export")
// Try show-sensitive first (ROS 7); fall back to plain export for ROS 6.
cmd := "/export show-sensitive"
log.Printf("[%s] ssh %s:%d %s", r.Name, r.Host, sshPort, cmd)
body, err := runOverSSH(r, sshPort, cmd)
if err != nil {
return err
log.Printf("[%s] show-sensitive failed (%v), falling back to /export", r.Name, err)
cmd = "/export"
log.Printf("[%s] ssh %s:%d %s", r.Name, r.Host, sshPort, cmd)
body, err = runOverSSH(r, sshPort, cmd)
if err != nil {
return err
}
}
if len(body) == 0 {
return fmt.Errorf("/export returned no output")
return fmt.Errorf("%s returned no output", cmd)
}
out := r.Name + ".rsc"

Binary file not shown.

View file

@ -1,4 +1,4 @@
# 2026-05-14 13:40:53 by RouterOS 7.21.4
# 2026-07-10 10:54:21 by RouterOS 7.21.4
# software id = 5HTF-YFWV
#
# model = CCR1009-7G-1C-1S+
@ -34,8 +34,7 @@ set [ find default=yes ] ip-type=ipv4 use-network-apn=no
set [ find default=yes ] supplicant-identity=MikroTik
/ip hotspot profile
add dns-name=newhope.vntx.net hotspot-address=100.64.19.254 login-by=\
mac,http-chap mac-auth-mode=mac-as-username-and-password name=hsprof1 \
use-radius=yes
mac,http-chap mac-auth-mode=mac-as-username-and-password name=hsprof1
/ip hotspot
add addresses-per-mac=unlimited idle-timeout=none interface=\
sfp-sfpplus1-edgepoint name=hotspot1 profile=hsprof1
@ -151,14 +150,14 @@ add address-pool=dhcp_pool6 interface=ether7-tower lease-time=1h name=\
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
add code=23 name=v6-dns
/ipv6 pool
add name=newhope-cust-pd-1 prefix=2606:1c80:1801::/48 prefix-length=56
/ppp profile
set *0 remote-address=dhcp_pool3
set *0 dns-server=204.110.191.240,204.110.191.250 remote-address=dhcp_pool3
add change-tcp-mss=yes dhcpv6-pd-pool=newhope-cust-pd-1 dns-server=\
204.110.191.240,204.110.191.20 local-address=100.64.19.253 name=newhope \
on-down="{\
204.110.191.240,204.110.191.250 idle-timeout=1h local-address=\
100.64.19.253 name=newhope on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
@ -266,8 +265,9 @@ add change-tcp-mss=yes dhcpv6-pd-pool=newhope-cust-pd-1 dns-server=\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=cgnat remote-ipv6-prefix-pool=newhope-cust-pd-1 \
use-upnp=no
\n" only-one=yes remote-address=cgnat remote-ipv6-prefix-pool=\
newhope-cust-pd-1 use-upnp=no
set *FFFFFFFE dns-server=204.110.191.240,204.110.191.250
/queue type
add kind=pcq name=delinquent_download pcq-classifier=dst-address \
pcq-dst-address6-mask=64 pcq-rate=64k pcq-src-address6-mask=64 \
@ -295,9 +295,11 @@ set default as=393837 disabled=no output.network=bgp-networks .redistribute=\
add disabled=no id=10.254.254.108 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected,rip router-id=id-1
ospf-out redistribute=connected router-id=id-1
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
/routing rip instance
add name=rip-instance-1
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
@ -314,16 +316,13 @@ add bridge=newhope ingress-filtering=no interface=combo1 internal-path-cost=\
add bridge=newhope interface=ether5-switch
add bridge=newhope interface=sfp-sfpplus1-edgepoint
/ip firewall connection tracking
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
set icmp-timeout=30s tcp-close-wait-timeout=1m tcp-established-timeout=4h \
tcp-fin-wait-timeout=2m tcp-last-ack-timeout=30s \
tcp-syn-received-timeout=1m tcp-syn-sent-timeout=2m \
tcp-time-wait-timeout=2m udp-stream-timeout=2m
/ip settings
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
set tcp-syncookies=yes
/ipv6 settings
# ipv6 *accept router advertisements* configuration has changed, please restart device to apply settings
set accept-router-advertisements=no
/interface pppoe-server server
add authentication=mschap2 default-profile=newhope disabled=no interface=\
@ -439,25 +438,40 @@ add address-pool=cgnat dhcp-option-set=vntx interface=newhope lease-script="{\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=newhope
/ip dhcp-server alert
add disabled=no interface=newhope on-alert=rogue-dhcp valid-server=\
6C:3B:6B:E1:5D:46
/ip dhcp-server lease
add address=100.64.19.250 mac-address=0C:73:EB:B0:BB:5B server=newhope
add address=100.64.19.252 client-id=1:dc:2c:6e:b4:77:0 mac-address=\
DC:2C:6E:B4:77:00 server=newhope
add address=100.64.19.251 mac-address=74:AC:B9:A3:E0:5C
/ip dhcp-server network
add address=10.0.108.0/24 dns-server=204.110.191.240,204.110.191.20 gateway=\
10.0.108.254 ntp-server=204.110.191.19
add address=10.10.128.0/20 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=10.10.143.254 ntp-server=204.110.191.19
add address=100.64.16.0/22 dns-server=204.110.191.240,204.110.191.20 domain=\
vntx.net gateway=100.64.19.254 ntp-server=204.110.191.19
add address=204.110.188.160/27 dns-server=204.110.191.240,204.110.191.20 \
domain=vntx.net gateway=204.110.188.190 ntp-server=204.110.191.19
add address=10.0.108.0/24 dns-server=204.110.191.240,204.110.191.250 gateway=\
10.0.108.254 ntp-server=23.150.40.242,108.61.215.221,162.254.225.152
add address=10.10.128.0/20 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=10.10.143.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.16.0/22 dns-server=204.110.191.240,204.110.191.250 domain=\
vntx.net gateway=100.64.19.254 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
add address=204.110.188.160/27 dns-server=204.110.191.240,204.110.191.250 \
domain=vntx.net gateway=204.110.188.190 ntp-server=\
23.150.40.242,108.61.215.221,162.254.225.152
/ip dhcp-server option sets
add name=vntx options=*1
/ip dns
set servers=9.9.9.9,1.1.1.1,2606:1c80::240,2606:1c80::250
set servers=204.110.191.240,204.110.191.250
/ip firewall address-list
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
ether6-lowrycrossing
@ -484,7 +498,7 @@ add address=10.100.128.1-10.100.142.254
add address=10.100.143.0/24 type=bypassed
add address=0.0.0.0/0 type=blocked
/ip hotspot user
add name=admin
add name=admin password=h8xd9tkryg
/ip hotspot walled-garden
add comment="place hotspot rules here" disabled=yes
add comment="place hotspot rules here" disabled=yes
@ -580,13 +594,13 @@ add interface=ether6-lowrycrossing
/mpls settings
set propagate-ttl=no
/ppp aaa
set interim-update=1h use-radius=yes
set interim-update=15m use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.248 secret=ivcgfagb6f0t64ws service=ppp,hotspot,dhcp \
src-address=204.110.188.190 timeout=3s
add address=204.110.191.2 require-message-auth=no service=ppp,hotspot,dhcp \
add address=204.110.191.2 secret=ivcgfagb6f0t64ws service=ppp,hotspot,dhcp \
src-address=204.110.188.190 timeout=3s
add accounting-backup=yes address=45.76.56.5 require-message-auth=no service=\
add accounting-backup=yes address=45.76.56.5 secret=ivcgfagb6f0t64ws service=\
ppp,hotspot,dhcp src-address=204.110.188.190 timeout=3s
/radius incoming
set accept=yes
@ -607,15 +621,20 @@ add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1800::/44 || dst==2\
606:1c80:0:10::108/128 || (dst-len==64 && (dst==2606:1c80:0:13::/64 || dst\
==2606:1c80:0:18::/64))) { accept; } else { reject; }"
/routing ospf interface-template
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4 priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-lowrycrossing priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether2-380 priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether4 priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether6-lowrycrossing priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 auth-key=\
5YHHWmoooAyVaFDX48ffjGjc36XMO5Irb7zS44Tsvet cost=10 disabled=no \
interfaces=ether2-380 priority=1 type=ptp use-bfd=no
add area=backbone-v2 disabled=no passive
/routing rip interface-template
add instance=rip-instance-1
/routing rip static-neighbor
add address="" disabled=no instance=*1
add address="" disabled=no instance=rip-instance-1
/snmp
set contact=graham@vntx.net enabled=yes location="33.207391, -96.537840"
/system clock
@ -652,7 +671,6 @@ add address=0.us.pool.ntp.org
/system package update
set channel=long-term
/system routerboard settings
# Firmware upgraded successfully, please reboot for changes to take effect!
set auto-upgrade=yes
/system scheduler
add interval=5m name=remove_hotspot on-event=\
@ -685,11 +703,13 @@ set auto-send-supout=yes ping-start-after-boot=1h send-email-from=\
support@vntx.net send-email-to=graham@vntx.net send-smtp-server=\
204.110.191.236 watchdog-timer=no
/tool e-mail
set certificate-verification=no from=NewHope@vntx.net server=\
smtp.sendgrid.net tls=starttls user=veronanetworks
set certificate-verification=no from=NewHope@vntx.net password=\
F9K2B2yhvE6YUnv server=smtp.sendgrid.net tls=starttls user=veronanetworks
/tool graphing interface
add
/tool graphing queue
add
/tool graphing resource
add
/tool romon
set secrets=vntx1830

717
mikrotik-tool/newrouter.rsc Normal file
View file

@ -0,0 +1,717 @@
# Translated from core.rsc (CCR1009-7G-1C-1S+) for CCR2004-16G-2S+
# Port mapping:
# core combo1 → new ether8 (customer LAN: PPPoE+DHCP, mgmt vlan10)
# core ether1..ether7 → new ether1..ether7 (unchanged)
# core sfp-sfpplus1 → new sfp-sfpplus1 (edge via Preseem, unchanged)
# new sfp-sfpplus2 → unused
# new ether9..ether16 → unused
/interface ethernet
set [ find default-name=ether1 ] l2mtu=9000 name=ether1-982-60ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether2 ] l2mtu=9000 name=ether2-office \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether3 ] l2mtu=9000 name=ether3-edge-direct \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether4 ] l2mtu=9000 name=ether4-newhope \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether5 ] l2mtu=2024 name=ether5-climax \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether6 ] l2mtu=2024 name=ether6-culleoka-11ghz \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether7 ] disabled=yes l2mtu=9000 name=ether7-380tower \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=ether8 ] l2mtu=9000 name=ether8-380-lan \
rx-flow-control=auto tx-flow-control=auto
set [ find default-name=sfp-sfpplus1 ] l2mtu=9000 name=\
sfp-sfpplus1-edge-preseem rx-flow-control=auto tx-flow-control=auto
/interface vlan
add interface=ether8-380-lan name=vlan10_ether8 vlan-id=10
/interface lte apn
set [ find default=yes ] ip-type=ipv4 use-network-apn=no
/interface wireless security-profiles
set [ find default=yes ] supplicant-identity=MikroTik
/ip ipsec proposal
set [ find default=yes ] auth-algorithms=sha256 enc-algorithms=\
aes-256-cbc,aes-128-cbc pfs-group=modp2048
/ip pool
add name=sstp ranges=172.16.91.1-172.16.91.253
add name=380 ranges=100.64.8.1-100.64.11.230
add name=380-mgmt ranges=10.10.64.1-10.10.79.230
/ip dhcp-server
add address-pool=380 interface=ether8-380-lan lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=380
add address-pool=380-mgmt interface=vlan10_ether8 lease-script="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/cd\
c24944-c947-4e01-9c54-07a1653d2e3e\"\
\n:local url2 \"https://iptrack.vntx.net/api/dhcp\"\
\n:local max 1\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n :if (\$leaseBound = 0) do {\
\n # Try url2 (iptrack.vntx.net) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP deassignment successfully sent to iptrack.vntx.ne\
t for \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to iptrack.vntx.net \
on attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n # Try url (gaiia) - deassignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP deassignment successfully sent to gaiia AWS API f\
or \$leaseActMAC / \$leaseActIP\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send deassignment to gaiia AWS API on \
attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 3s;\
\n }\
\n } else {\
\n :delay 1s;\
\n :local remoteID [/ip dhcp-server lease get [find where address=\$lea\
seActIP] agent-remote-id];\
\n\
\n # Try url2 (iptrack.vntx.net) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=pos\
t http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success1 1;\
\n :log info \"DHCP assignment successfully sent to iptrack.vntx.net \
for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to iptrack.vntx.net on\
\_attempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\"\
;\
\n }\
\n\
\n # Try url (gaiia) - assignment\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post\
\_http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bo\
und\\\":\\\"\$leaseBound\\\",\\\"mac\\\":\\\"\$leaseActMAC\\\",\\\"ip\\\":\
\\\"\$leaseActIP\\\",\\\"server\\\":\\\"\$leaseServerName\\\",\\\"remoteId\
\\\":\\\"\$remoteID\\\"}\"\
\n :set success2 1;\
\n :log info \"DHCP assignment successfully sent to gaiia AWS API for\
\_\$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n } on-error={\
\n :log error \"DHCP FAILED to send assignment to gaiia AWS API on at\
tempt \$attempts/\$max for \$leaseActMAC / \$leaseActIP / \$remoteID\";\
\n }\
\n\
\n :if (\$success1 != 1 || \$success2 != 1) do={\
\n :delay 30s;\
\n }\
\n }\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" lease-time=1h name=380-mgmt
/ip smb users
set [ find default=yes ] disabled=yes
/ipv6 dhcp-server option
add code=23 name=v6-dns value="'2606:1c80::240''2606:1c80::250'"
/ipv6 pool
add name=core-cust-pd-1 prefix=2606:1c80:1401::/48 prefix-length=56
/ppp profile
set *0 dns-server=1.1.1.1,9.9.9.9
add change-tcp-mss=yes dhcpv6-pd-pool=core-cust-pd-1 dns-server=\
1.1.1.1,9.9.9.9 local-address=100.64.11.253 name=380 on-down="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 1\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE deassignment successfully sent to iptrack.vntx.net\
\_for \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send deassignment to iptrack.vntx.net o\
n attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (AWS API)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"0\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE deassignment successfully sent to gaiia AWS API fo\
r \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoPoE FAILED to send deassignment to gaiia AWS API on\
\_attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" on-up="{\
\n:local url \"https://xtjlpnubrg.execute-api.us-east-1.amazonaws.com/prod\
uction/on-start-workflow-execution/6f19a48f-04f7-40c1-a4e0-8cb66df99213/0a\
7f4443-fc8a-4fe0-807c-f535d2aa1865\"\
\n:local url2 \"https://iptrack.vntx.net/api/pppoe\"\
\n:local max 5\
\n\
\n:local localAddr \$\"local-address\"\
\n:local remoteAddr \$\"remote-address\"\
\n:local callerId \$\"caller-id\"\
\n:local calledId \$\"called-id\"\
\n:local interfaceName [/interface get \$interface name]\
\n\
\n:local attempts 0\
\n:local success1 0\
\n:local success2 0\
\n:do {\
\n :set attempts (\$attempts+1);\
\n\
\n # Try url2 (iptrack.vntx.net)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url2\" http-method=post \
http-header-field=\"Content-Type: application/json\" http-data=\"{\\\"boun\
d\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\
\\\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success1 1;\
\n :log info \"PPPoE assignment successfully sent to iptrack.vntx.net f\
or \$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to iptrack.vntx.net on \
attempt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n # Try url (gaiia)\
\n :do {\
\n /tool fetch duration=30s mode=https url=\"\$url\" http-method=post h\
ttp-header-field=\"Content-Type: application/json\" http-data=\"{\\\"bound\
\\\":\\\"1\\\",\\\"username\\\":\\\"\$user\\\",\\\"callingstationid\\\":\\\
\"\$callerId\\\",\\\"framedip\\\":\\\"\$remoteAddr\\\"}\"\
\n :set success2 1;\
\n :log info \"PPPoE assignment successfully sent to gaiia AWS API for \
\$user / \$remoteAddr\";\
\n } on-error={\
\n :log error \"PPPoE FAILED to send assignment to gaiia AWS API on att\
empt \$attempts out of \$max for \$user / \$remoteAddr\";\
\n }\
\n\
\n :if (\$success1 = 1 && \$success2 = 1) do {\
\n :set attempts \$max;\
\n } else {\
\n :delay 3s;\
\n }\
\n} while ( \$attempts < \$max )\
\n}\
\n" remote-address=380 remote-ipv6-prefix-pool=core-cust-pd-1 use-ipv6=no \
use-upnp=no
set *FFFFFFFE dns-server=1.1.1.1,9.9.9.9 local-address=172.16.91.254 \
remote-address=sstp
/queue type
add kind=fq-codel name=FQ_Codel
/queue interface
set ether1-982-60ghz queue=FQ_Codel
set ether2-office queue=FQ_Codel
set ether3-edge-direct queue=FQ_Codel
set ether4-newhope queue=FQ_Codel
set ether5-climax queue=FQ_Codel
set ether6-culleoka-11ghz queue=FQ_Codel
set ether7-380tower queue=FQ_Codel
set ether8-380-lan queue=FQ_Codel
set sfp-sfpplus1-edge-preseem queue=FQ_Codel
/routing bgp instance
add as=393837 cluster-id=10.254.254.253 disabled=no name=bgp-instance-1 \
router-id=10.254.254.253
/routing bgp template
set default as=65530 disabled=no
/routing id
add disabled=no id=10.254.254.253 name=id-1 select-dynamic-id=""
/routing ospf instance
add disabled=no in-filter-chain=ospf-in name=default-v2 out-filter-chain=\
ospf-out redistribute=connected,static router-id=id-1 routing-table=main
/routing ospf area
add disabled=no instance=default-v2 name=backbone-v2
/routing table
add fib name=testnat
/snmp community
set [ find default=yes ] addresses=204.110.188.0/22,10.0.0.0/8 name=\
kdyyJrT0Mm
/system logging action
add name=logs remote=204.110.191.229 src-address=10.254.254.253 target=remote
/ip smb
set enabled=no
/ip firewall connection tracking
set tcp-established-timeout=4h tcp-fin-wait-timeout=2m tcp-time-wait-timeout=\
2m
/ip neighbor discovery-settings
set discover-interface-list=!dynamic
/ip settings
set max-neighbor-entries=8192 tcp-syncookies=yes
/ipv6 settings
set accept-router-advertisements=no max-neighbor-entries=8192 \
soft-max-neighbor-entries=8191
/interface pppoe-server server
add authentication=mschap2 default-profile=380 disabled=no interface=\
ether8-380-lan max-mru=1492 max-mtu=1492 service-name=380
/interface sstp-server server
set certificate=ServerCA enabled=yes
/ip address
add address=204.110.191.185/30 interface=sfp-sfpplus1-edge-preseem network=\
204.110.191.184
add address=10.0.0.254/24 disabled=yes interface=ether7-380tower network=\
10.0.0.0
add address=10.250.1.89/29 interface=ether5-climax network=10.250.1.88
add address=10.250.1.62/29 interface=ether4-newhope network=10.250.1.56
add address=204.110.191.181/30 interface=ether3-edge-direct network=\
204.110.191.180
add address=10.254.254.253 interface=lo network=10.254.254.253
add address=10.250.2.6/29 interface=ether2-office network=10.250.2.0
add address=10.250.1.38/29 interface=ether1-982-60ghz network=10.250.1.32
add address=10.250.1.54/29 interface=ether6-culleoka-11ghz network=\
10.250.1.48
add address=10.250.1.246/29 disabled=yes interface=ether8-380-lan network=\
10.250.1.240
add address=100.64.11.254/22 interface=ether8-380-lan network=100.64.8.0
add address=10.10.79.254/20 interface=vlan10_ether8 network=10.10.64.0
/ip dhcp-server network
add address=10.10.64.0/20 dns-server=1.1.1.1,9.9.9.9 domain=vntx.net gateway=\
10.10.79.254 ntp-server=23.150.40.242,108.61.215.221,162.254.225.152
add address=100.64.8.0/22 dns-server=1.1.1.1,9.9.9.9 domain=vntx.net gateway=\
100.64.11.254 ntp-server=23.150.40.242,108.61.215.221,162.254.225.152
/ip dns
set servers=1.1.1.1,9.9.9.9
/ip firewall address-list
add address=204.110.188.0/22 comment="VNTX public IPs" list=trusted
add address=100.64.0.0/10 comment="CGNAT pools" list=trusted
add address=10.0.0.0/8 comment="RFC 1918 private" list=trusted
add address=192.168.0.0/16 comment="RFC 1918 private" list=trusted
/ip firewall filter
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=accept chain=input comment="allow trusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp src-address-list=trusted
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=drop chain=input comment="drop untrusted to management services" \
dst-port=8728-8729,8291,22 protocol=tcp
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether1-982-60ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether1-982-60ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether4-newhope
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether4-newhope
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
ether6-culleoka-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether6-culleoka-11ghz
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether5-climax
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether5-climax
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=ether3-edge-direct
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=ether3-edge-direct
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (in)" in-interface=\
sfp-sfpplus1-edge-preseem
add action=accept chain=forward comment=\
"bypass fasttrack for MPLS spine (out)" out-interface=\
sfp-sfpplus1-edge-preseem
add action=fasttrack-connection chain=forward comment=\
"fasttrack established/related" connection-state=established,related
add action=drop chain=forward disabled=yes src-address-list=suspended
add action=drop chain=forward disabled=yes dst-address-list=suspended
add action=accept chain=forward comment="accept established/related" \
connection-state=established,related
/ip ipsec profile
set [ find default=yes ] dh-group=modp2048 dpd-interval=2m \
dpd-maximum-failures=5 enc-algorithm=aes-256,aes-128 hash-algorithm=\
sha256
/ip proxy
set port=29347
/ip proxy access
add src-address=10.0.0.0/8
add src-address=100.64.0.0/10
add src-address=204.110.188.0/22
add action=deny
/ip route
add check-gateway=ping disabled=yes dst-address=0.0.0.0/0 gateway=\
204.110.191.186
add disabled=no distance=1 dst-address=204.110.188.32/27 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add disabled=no distance=1 dst-address=10.250.1.88/29 gateway=10.250.1.94 \
routing-table=main scope=30 target-scope=10
add dst-address=10.254.254.101/32 gateway=10.250.1.94
add dst-address=10.254.254.102/32 gateway=10.250.1.94
add dst-address=10.250.1.64/29 gateway=10.250.1.94
add dst-address=10.250.1.24/29 gateway=10.250.1.94
add dst-address=10.250.1.8/29 gateway=10.250.1.94
add dst-address=10.10.16.0/20 gateway=10.250.1.94
add dst-address=10.10.0.0/20 gateway=10.250.1.94
add dst-address=204.110.191.0/27 gateway=10.250.1.94
add dst-address=100.64.4.0/22 gateway=10.250.1.94
add dst-address=100.64.12.0/22 gateway=10.250.1.94
add dst-address=100.64.0.0/22 gateway=10.250.1.94
add dst-address=204.110.188.224/27 gateway=10.250.1.94
add dst-address=10.250.1.144/29 gateway=10.250.1.94
add dst-address=10.254.254.111/32 gateway=10.250.1.94
add dst-address=100.64.160.0/20 gateway=10.250.1.94
add dst-address=204.110.188.64/27 gateway=10.250.1.94
/ipv6 route
add comment="default via preseem (primary)" distance=1 dst-address=::/0 \
gateway=2606:1c80:0:1002::1
add comment="default via direct (backup)" distance=2 dst-address=::/0 \
gateway=2606:1c80:0:11::1
add comment="edge loopback via preseem" distance=1 dst-address=\
2606:1c80:0:10::254/128 gateway=2606:1c80:0:1002::1
add comment="edge loopback via direct" distance=2 dst-address=\
2606:1c80:0:10::254/128 gateway=2606:1c80:0:11::1
add blackhole comment="core /44 aggregate (iBGP origin)" distance=200 \
dst-address=2606:1c80:1400::/44
add comment="verona loopback via climax" dst-address=2606:1c80:0:10::101/128 \
gateway=2606:1c80:0:12::2
add comment="climax loopback via climax P2P" dst-address=\
2606:1c80:0:10::102/128 gateway=2606:1c80:0:12::2
add comment="494 loopback (iBGP bootstrap, via climax)" distance=1 \
dst-address=2606:1c80:0:10::111/128 gateway=2606:1c80:0:12::2
add comment="982 loopback (iBGP bootstrap)" distance=1 dst-address=\
2606:1c80:0:10::110/128 gateway=2606:1c80:0:14::2
add comment="culleoka loopback (iBGP bootstrap)" distance=1 dst-address=\
2606:1c80:0:10::104/128 gateway=2606:1c80:0:15::2
add comment="newhope loopback (iBGP bootstrap)" distance=1 dst-address=\
2606:1c80:0:10::108/128 gateway=2606:1c80:0:13::2
add comment="lowry loopback (via newhope)" distance=1 dst-address=\
2606:1c80:0:10::109/128 gateway=2606:1c80:0:13::2
/ip service
set ftp disabled=yes
set telnet address=204.110.188.0/22,10.0.0.0/8 disabled=yes
set ssh address=204.110.188.0/22,10.0.0.0/8 port=1022
set www address=204.110.188.225/32 port=2080
set winbox address=204.110.188.0/22,10.0.0.0/8
set api address=204.110.188.0/22,10.0.0.0/8,100.64.0.0/10
set api-ssl certificate=myCa
/ip ssh
set host-key-type=ed25519 password-authentication=yes strong-crypto=yes
/ipv6 address
add address=2606:1c80:0:10::253/128 advertise=no comment=loopback interface=\
lo
add address=2606:1c80:0:11::2 advertise=no comment=\
"P2P core<->edge direct (backup)" interface=ether3-edge-direct
add address=2606:1c80:0:1002::2 advertise=no comment=\
"P2P core<->edge via preseem (primary)" interface=\
sfp-sfpplus1-edge-preseem
add address=2606:1c80:0:12::1 advertise=no comment="P2P core<->climax" \
interface=ether5-climax
add address=2606:1c80:0:13::1 advertise=no comment="P2P core<->newhope" \
interface=ether4-newhope
add address=2606:1c80:0:14::1 advertise=no comment="P2P core<->982 60GHz" \
interface=ether1-982-60ghz
add address=2606:1c80:0:15::1 advertise=no comment="P2P core<->culleoka" \
interface=ether6-culleoka-11ghz
add address=2606:1c80:1400::1 comment="380 mgmt LAN" interface=vlan10_ether8
add address=2606:1c80:1400:1::1 comment="380 customer LAN (PPPoE+DHCP)" \
interface=ether8-380-lan
/ipv6 dhcp-server
add dhcp-option=v6-dns interface=vlan10_ether8 name=core-mgmt-stateless
add address-pool=core-cust-pd-1 dhcp-option=v6-dns interface=ether8-380-lan \
name=core-cust-pd-server
/ipv6 firewall filter
add action=accept chain=input comment=established connection-state=\
established,related
add action=drop chain=input comment=invalid connection-state=invalid
add action=accept chain=input comment=icmpv6 protocol=icmpv6
add action=accept chain=input comment=link-local src-address=fe80::/10
add action=accept chain=input comment=internal src-address=2606:1c80::/32
add action=drop chain=input comment="default deny"
add action=accept chain=forward comment=established connection-state=\
established,related
add action=drop chain=forward comment=invalid connection-state=invalid
add action=accept chain=forward comment=PMTUD hop-limit=equal:1 protocol=\
icmpv6
add action=accept chain=forward comment=icmpv6 protocol=icmpv6
add action=accept chain=forward comment="v6 outbound (all vntx sources)" \
src-address=2606:1c80::/32
add action=drop chain=forward comment="default deny"
/ipv6 nd
set [ find default=yes ] advertise-dns=yes
add advertise-dns=yes interface=vlan10_ether8 other-configuration=yes
add advertise-dns=yes interface=ether8-380-lan other-configuration=yes
/mpls interface
add interface=ether5-climax mpls-mtu=1508
add interface=ether6-culleoka-11ghz mpls-mtu=1508
add interface=ether4-newhope mpls-mtu=1508
add interface=ether1-982-60ghz mpls-mtu=1508
add interface=ether3-edge-direct mpls-mtu=1508
add interface=sfp-sfpplus1-edge-preseem mpls-mtu=1508
/mpls ldp
add disabled=no lsr-id=10.254.254.253 transport-addresses=10.254.254.253 vrf=\
main
/mpls ldp interface
add interface=ether5-climax
add interface=ether6-culleoka-11ghz
add interface=ether4-newhope
add interface=ether1-982-60ghz
/mpls settings
set propagate-ttl=no
/ppp aaa
set use-radius=yes
/radius
add address=204.110.191.248 require-message-auth=no service=ppp src-address=\
204.110.191.185 timeout=300ms
add address=204.110.191.2 require-message-auth=no service=ppp src-address=\
204.110.191.185 timeout=300ms
add accounting-backup=yes address=45.76.56.5 require-message-auth=no service=\
ppp src-address=204.110.191.185 timeout=300ms
/routing bfd configuration
add disabled=no interfaces=all min-rx=200ms min-tx=200ms multiplier=5
/routing bgp connection
add afi=ip,ipv6 as=393837 comment="iBGP to edge, v4 transport / v6 NLRI" \
input.filter=v6-rr-in instance=bgp-instance-1 local.address=\
2606:1c80:0:10::253 .role=ibgp-rr multihop=yes name=ibgp-rr-edge \
nexthop-choice=default output.filter-chain=v6-rr-out .redistribute=\
connected,static remote.address=2606:1c80:0:10::254 .as=393837 templates=\
default
add afi=ip,ipv6 as=393837 comment="RR client: climax" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-climax nexthop-choice=default \
output.filter-chain=v6-rr-out .redistribute=connected,static \
remote.address=2606:1c80:0:10::102 .as=393837 templates=default
add afi=ip,ipv6 as=393837 comment="RR client: culleoka" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-culleoka nexthop-choice=default \
output.filter-chain=v6-rr-out .redistribute=connected,static \
remote.address=2606:1c80:0:10::104 .as=393837 templates=default
add afi=ip,ipv6 as=393837 comment="RR client: newhope" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-newhope nexthop-choice=default \
output.filter-chain=v6-rr-out .redistribute=connected,static \
remote.address=2606:1c80:0:10::108 .as=393837 templates=default
add afi=ip,ipv6 as=393837 comment="RR client: lowry" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-lowry nexthop-choice=default \
output.filter-chain=v6-rr-out .redistribute=connected,static \
remote.address=2606:1c80:0:10::109 .as=393837 templates=default
add afi=ip,ipv6 as=393837 comment="RR client: 982" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-982 nexthop-choice=default output.filter-chain=\
v6-rr-out .redistribute=connected,static remote.address=\
2606:1c80:0:10::110 .as=393837 templates=default
add afi=ip,ipv6 as=393837 comment="RR client: 494" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-494 nexthop-choice=default output.filter-chain=\
v6-rr-out .redistribute=connected,static remote.address=\
2606:1c80:0:10::111 .as=393837 templates=default
add afi=ip,ipv6 as=393837 comment="RR client: verona" input.filter=v6-rr-in \
instance=bgp-instance-1 local.address=2606:1c80:0:10::253 .role=ibgp-rr \
multihop=yes name=ibgp-rr-verona nexthop-choice=default \
output.filter-chain=v6-rr-out .redistribute=connected,static \
remote.address=2606:1c80:0:10::101 .as=393837 templates=default
/routing filter rule
add chain=ospf-in disabled=no rule="accept;"
add chain=ospf-out disabled=no rule="accept;"
add chain=v6-ibgp-out disabled=no rule="if (dst==2606:1c80:1400::/44 || dst==2\
606:1c80:0:10::253/128 || (dst-len==64 && (dst==2606:1c80:0:11::/64 || dst\
==2606:1c80:0:12::/64 || dst==2606:1c80:0:13::/64 || dst==2606:1c80:0:14::\
/64 || dst==2606:1c80:0:15::/64 || dst==2606:1c80:0:1002::/64))) { accept;\
\_} else { reject; }"
add chain=v6-rr-out disabled=no rule="if (dst==::/0 || dst-len==44 || dst-len=\
=64 || dst-len==128) { accept; } else { reject; }"
add chain=v6-rr-in disabled=no rule="if (dst==::/0 || dst-len==44 || dst-len==\
64 || dst-len==128) { accept; } else { reject; }"
/routing ospf interface-template
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=\
sfp-sfpplus1-edge-preseem priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether5-climax priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether4-newhope priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth-id=1 cost=100 disabled=no interfaces=\
ether3-edge-direct priority=1 type=ptp use-bfd=yes
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=ether2-office \
priority=1 type=nbma use-bfd=no
add area=backbone-v2 auth-id=1 cost=10 disabled=no interfaces=\
ether1-982-60ghz priority=1 type=ptp use-bfd=no
add area=backbone-v2 auth=sha512 auth-id=1 cost=10 disabled=no interfaces=\
ether6-culleoka-11ghz priority=1 type=ptp use-bfd=no
/routing ospf static-neighbor
add address=204.110.191.186%sfp-sfpplus1-edge-preseem area=backbone-v2 \
disabled=no
add address=10.250.2.1%ether2-office area=backbone-v2 disabled=no
/routing rip static-neighbor
add address="" disabled=no instance=*1
/routing rule
add action=lookup-only-in-table disabled=yes src-address=10.10.0.0/16 table=\
testnat
add action=lookup-only-in-table disabled=yes src-address=100.64.0.0/22 table=\
testnat
add action=lookup-only-in-table disabled=yes src-address=100.64.0.0/10 table=\
testnat
/snmp
set contact=graham@vntx.net enabled=yes location="33.174156, -96.491941" \
trap-generators=temp-exception,interfaces,start-trap trap-interfaces=all \
trap-target=204.110.191.234
/system clock
set time-zone-name=America/Chicago
/system identity
set name=Core
/system logging
add action=logs topics=info
add action=logs disabled=yes prefix=ospf topics=ospf
/system ntp client
set enabled=yes
/system ntp client servers
add address=204.110.191.19
add address=0.us.pool.ntp.org
add address=216.229.4.66
add address=1.us.pool.ntp.org
/system package update
set channel=long-term
/system scheduler
add name=reboot on-event="/system reboot\
\n" policy=\
ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
start-date=2025-01-07 start-time=04:00:00
/tool graphing interface
add
/tool graphing queue
add
/tool graphing resource
add

View file

@ -0,0 +1,20 @@
:global opt82sync do={
# Add bypass for any bound lease with Option 82
/ip dhcp-server lease foreach={
:if ($activeAgentRemoteId != "" && $status = "bound") do={
:if ([/ip hotspot ip-binding find where address=$activeAddress server=hotspot1 mac-address=$activeMacAddress] = "") do={
/ip hotspot ip-binding add address=$activeAddress mac-address=$activeMacAddress server=hotspot1 to-address=$activeAddress type=bypassed
:log info "opt82-poll: bypassed $activeMacAddress / $activeAddress"
}
}
}
# Remove bypasses for IPs that no longer have any bound lease
/ip hotspot ip-binding foreach={
:if ($server = "hotspot1" && $mac-address != "") do={
:if ([/ip dhcp-server lease find where activeAddress=$address] = "") do={
/ip hotspot ip-binding remove $.
:log info "opt82-poll: removed bypass for $mac-address / $address (no lease)"
}
}
}
}

View file

@ -0,0 +1,18 @@
# Fix 5+6: Rebuild OSPF with standard naming + add redistribute=connected
# Remove old OSPF config (templates first, then area, then instance)
/routing ospf interface-template remove numbers=*5
/routing ospf interface-template remove numbers=*4
/routing ospf area remove numbers=*1
/routing ospf instance remove numbers=*0
# Recreate with standard naming and redistribute
/routing ospf instance add name=default-v2 router-id=id-1 originate-default=never redistribute=connected in-filter-chain=ospf-in out-filter-chain=ospf-out version=2 vrf=main
/routing ospf area add name=backbone-v2 area-id=0.0.0.0 instance=default-v2 type=default
/routing ospf interface-template add area=backbone-v2 interfaces=ether2-climax cost=10 type=ptp use-bfd=no dead-interval=40s hello-interval=10s priority=1
/routing ospf interface-template add area=backbone-v2 passive type=broadcast cost=1
# Fix 9: Add idle timeout to PPPoE profile
/ppp profile set [find name="494"] idle-timeout=1h
# Fix 10: Increase RADIUS timeout from 300ms to 2s
/radius set [find address=204.110.191.248] timeout=2s
/radius set [find address=204.110.191.2] timeout=2s

View file

@ -0,0 +1,5 @@
# Fix 9: Add idle timeout to PPPoE profile
/ppp profile set [find name="982"] idle-timeout=1h
# Fix 10: Increase RADIUS timeout from 300ms to 2s
/radius set [find address=204.110.191.248] timeout=2s

View file

@ -0,0 +1,2 @@
# Fix 9: Add idle timeout to PPPoE profile
/ppp profile set [find name="pppoe"] idle-timeout=1h

View file

@ -0,0 +1,5 @@
# Fix 9: Add idle timeout to PPPoE profile
/ppp profile set [find name="380"] idle-timeout=1h
# Fix 10: Increase RADIUS timeout from 300ms to 2s
/radius set [find address=204.110.191.248] timeout=2s

Some files were not shown because too many files have changed in this diff Show more