ip-reporter 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ip_reporter-0.1.1/.gitignore +10 -0
- ip_reporter-0.1.1/LICENSE +21 -0
- ip_reporter-0.1.1/PKG-INFO +231 -0
- ip_reporter-0.1.1/README.md +185 -0
- ip_reporter-0.1.1/pyproject.toml +53 -0
- ip_reporter-0.1.1/release.sh +34 -0
- ip_reporter-0.1.1/src/ip_reporter/__init__.py +85 -0
- ip_reporter-0.1.1/src/ip_reporter/cli.py +246 -0
- ip_reporter-0.1.1/src/ip_reporter/cloudflare.py +397 -0
- ip_reporter-0.1.1/src/ip_reporter/curl.py +246 -0
- ip_reporter-0.1.1/src/ip_reporter/discovery.py +276 -0
- ip_reporter-0.1.1/src/ip_reporter/models.py +251 -0
- ip_reporter-0.1.1/src/ip_reporter/py.typed +0 -0
- ip_reporter-0.1.1/src/ip_reporter/reporter.py +175 -0
- ip_reporter-0.1.1/tests/test_cloudflare.py +51 -0
- ip_reporter-0.1.1/tests/test_curl.py +40 -0
- ip_reporter-0.1.1/tests/test_discovery.py +84 -0
- ip_reporter-0.1.1/tests/test_models.py +11 -0
- ip_reporter-0.1.1/uv.lock +184 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IP Reporter contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ip-reporter
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Reliable WAN/LAN IP discovery and Cloudflare DNS synchronization
|
|
5
|
+
Author: IP Reporter contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 IP Reporter contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Keywords: cloudflare,dynamic-dns,http3,ip,ipv4,ipv6
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Operating System :: MacOS
|
|
33
|
+
Classifier: Operating System :: POSIX
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Topic :: System :: Networking
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# ip-reporter
|
|
48
|
+
|
|
49
|
+
`ip-reporter` is a dependency-free Python package and CLI for discovering local
|
|
50
|
+
LAN addresses, public WAN addresses, and public addresses obtained through
|
|
51
|
+
HTTP/2 or HTTP/3. It can synchronize one or many A/AAAA records in Cloudflare.
|
|
52
|
+
|
|
53
|
+
The import name is `ip_reporter`; the installed command is `ip-reporter`.
|
|
54
|
+
|
|
55
|
+
## Install with uv
|
|
56
|
+
|
|
57
|
+
Install the command as a uv tool:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv tool install ip-reporter
|
|
61
|
+
ip-reporter --scope wan --family ipv4
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use it as a library dependency:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv add ip-reporter
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For HTTP/2 and HTTP/3 probing on macOS, install Homebrew curl. The package
|
|
71
|
+
rejects `/usr/bin/curl` on macOS so the system curl cannot silently disable
|
|
72
|
+
those probes.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
brew install curl
|
|
76
|
+
export IP_REPORTER_CURL="$(brew --prefix curl)/bin/curl"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The selected curl must report `HTTP2` or `HTTP3` in `curl --version` depending
|
|
80
|
+
on the requested probe. HTTP/3 requires a curl build with HTTP/3 support.
|
|
81
|
+
|
|
82
|
+
## CLI
|
|
83
|
+
|
|
84
|
+
With no subcommand, the CLI performs discovery. The default family is IPv4.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Public IPv4; text output includes the scope, family, and transport.
|
|
88
|
+
ip-reporter --scope wan --family ipv4
|
|
89
|
+
|
|
90
|
+
# LAN IPv4 and IPv6.
|
|
91
|
+
ip-reporter discover --scope lan --family ipv4 --family ipv6
|
|
92
|
+
|
|
93
|
+
# Force HTTP/3 over IPv4 and IPv6.
|
|
94
|
+
ip-reporter --scope wan --family h3ipv4 --family h3ipv6
|
|
95
|
+
|
|
96
|
+
# Structured output for scripts.
|
|
97
|
+
ip-reporter --scope both --family ipv4 --family ipv6 --format json
|
|
98
|
+
|
|
99
|
+
# Print only address values, one per line.
|
|
100
|
+
ip-reporter --scope wan --family ipv4 --format values
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
WAN probing has independent failover endpoints. The defaults include two
|
|
104
|
+
providers for IPv4, two for IPv6, and two candidates for each HTTP/3 family.
|
|
105
|
+
Override them with repeated `--wan-endpoint` options or with the Python
|
|
106
|
+
`RemoteEndpoint` API.
|
|
107
|
+
|
|
108
|
+
## Cloudflare DNS
|
|
109
|
+
|
|
110
|
+
Use an API token with the DNS Write permission. Prefer environment variables so
|
|
111
|
+
credentials do not enter shell history:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export CLOUDFLARE_API_TOKEN="..."
|
|
115
|
+
export CLOUDFLARE_ZONE_ID="..."
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Update one existing record:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
ip-reporter dns update \
|
|
122
|
+
--record home.example.com \
|
|
123
|
+
--family ipv4
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Update many names and both address families:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
ip-reporter dns update \
|
|
130
|
+
--record home.example.com \
|
|
131
|
+
--record vpn.example.com \
|
|
132
|
+
--family ipv4 \
|
|
133
|
+
--family ipv6 \
|
|
134
|
+
--ttl 60 \
|
|
135
|
+
--not-proxied
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Existing records are updated with PATCH and unchanged values are skipped.
|
|
139
|
+
Missing records are not created unless `--create-missing` is supplied. Stale
|
|
140
|
+
records are not deleted. Use `--dry-run` to inspect discovery without changing
|
|
141
|
+
Cloudflare.
|
|
142
|
+
|
|
143
|
+
If you have a zone name rather than an ID, pass `--zone-name`; the client can
|
|
144
|
+
resolve it through the Cloudflare API. Relative names and `@` are accepted when
|
|
145
|
+
the zone name is supplied:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
ip-reporter dns update \
|
|
149
|
+
--zone-name example.com \
|
|
150
|
+
--record home \
|
|
151
|
+
--family ipv4 \
|
|
152
|
+
--create-missing
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`h3ipv4` writes an A record and `h3ipv6` writes an AAAA record. The HTTP/3
|
|
156
|
+
transport is discovery metadata; the DNS type is determined from the resulting
|
|
157
|
+
IP address.
|
|
158
|
+
|
|
159
|
+
## Python API
|
|
160
|
+
|
|
161
|
+
The structured API is suitable for another package, a scheduler, or a daemon:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from ip_reporter import AddressFamily, IPReporter
|
|
165
|
+
|
|
166
|
+
reporter = IPReporter()
|
|
167
|
+
report = reporter.discover(
|
|
168
|
+
scope="wan",
|
|
169
|
+
families=(AddressFamily.IPV4, AddressFamily.IPV6),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for address in report.wan:
|
|
173
|
+
print(address.value, address.family.value, address.protocol.value)
|
|
174
|
+
|
|
175
|
+
# Convenience methods return only strings.
|
|
176
|
+
wan_ipv4 = reporter.get_wan(families=(AddressFamily.IPV4,))
|
|
177
|
+
lan_ipv6 = reporter.get_lan(families=(AddressFamily.IPV6,))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The discovery dependencies are injectable, which makes applications and tests
|
|
181
|
+
independent of the network:
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from ip_reporter import AddressDiscovery, AddressFamily, IPReporter, RemoteEndpoint
|
|
185
|
+
|
|
186
|
+
class Probe:
|
|
187
|
+
def fetch_ip(self, url, family):
|
|
188
|
+
return ("203.0.113.10", "http2")
|
|
189
|
+
|
|
190
|
+
reporter = IPReporter(
|
|
191
|
+
AddressDiscovery(
|
|
192
|
+
wan_probe=Probe(),
|
|
193
|
+
endpoints=(RemoteEndpoint("https://probe.example/ip", (AddressFamily.IPV4,)),),
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Synchronize Cloudflare from the same service:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from ip_reporter import AddressFamily, IPReporter
|
|
202
|
+
|
|
203
|
+
updates = IPReporter().update_cloudflare_dns(
|
|
204
|
+
["home.example.com", "vpn.example.com"],
|
|
205
|
+
zone_id="your-zone-id",
|
|
206
|
+
families=(AddressFamily.IPV4, AddressFamily.IPV6),
|
|
207
|
+
create_missing=True,
|
|
208
|
+
)
|
|
209
|
+
print(updates.as_dict())
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`CloudflareClient.from_env()` uses `CLOUDFLARE_API_TOKEN`, or the legacy
|
|
213
|
+
`CLOUDFLARE_EMAIL` and `CLOUDFLARE_API_KEY` pair. A custom HTTP transport can
|
|
214
|
+
be injected into `CloudflareClient` for testing.
|
|
215
|
+
|
|
216
|
+
## Build and publish
|
|
217
|
+
|
|
218
|
+
From a checkout:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
uv sync --extra dev
|
|
222
|
+
uv run pytest
|
|
223
|
+
uv build
|
|
224
|
+
uv publish
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Set `UV_PUBLISH_TOKEN` before `uv publish`, or configure another uv-supported
|
|
228
|
+
PyPI credential method. Bump `project.version` in `pyproject.toml` for each
|
|
229
|
+
release.
|
|
230
|
+
|
|
231
|
+
Cloudflare API details: [DNS record create](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/), [DNS record update](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/).
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# ip-reporter
|
|
2
|
+
|
|
3
|
+
`ip-reporter` is a dependency-free Python package and CLI for discovering local
|
|
4
|
+
LAN addresses, public WAN addresses, and public addresses obtained through
|
|
5
|
+
HTTP/2 or HTTP/3. It can synchronize one or many A/AAAA records in Cloudflare.
|
|
6
|
+
|
|
7
|
+
The import name is `ip_reporter`; the installed command is `ip-reporter`.
|
|
8
|
+
|
|
9
|
+
## Install with uv
|
|
10
|
+
|
|
11
|
+
Install the command as a uv tool:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv tool install ip-reporter
|
|
15
|
+
ip-reporter --scope wan --family ipv4
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use it as a library dependency:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv add ip-reporter
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
For HTTP/2 and HTTP/3 probing on macOS, install Homebrew curl. The package
|
|
25
|
+
rejects `/usr/bin/curl` on macOS so the system curl cannot silently disable
|
|
26
|
+
those probes.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
brew install curl
|
|
30
|
+
export IP_REPORTER_CURL="$(brew --prefix curl)/bin/curl"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The selected curl must report `HTTP2` or `HTTP3` in `curl --version` depending
|
|
34
|
+
on the requested probe. HTTP/3 requires a curl build with HTTP/3 support.
|
|
35
|
+
|
|
36
|
+
## CLI
|
|
37
|
+
|
|
38
|
+
With no subcommand, the CLI performs discovery. The default family is IPv4.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Public IPv4; text output includes the scope, family, and transport.
|
|
42
|
+
ip-reporter --scope wan --family ipv4
|
|
43
|
+
|
|
44
|
+
# LAN IPv4 and IPv6.
|
|
45
|
+
ip-reporter discover --scope lan --family ipv4 --family ipv6
|
|
46
|
+
|
|
47
|
+
# Force HTTP/3 over IPv4 and IPv6.
|
|
48
|
+
ip-reporter --scope wan --family h3ipv4 --family h3ipv6
|
|
49
|
+
|
|
50
|
+
# Structured output for scripts.
|
|
51
|
+
ip-reporter --scope both --family ipv4 --family ipv6 --format json
|
|
52
|
+
|
|
53
|
+
# Print only address values, one per line.
|
|
54
|
+
ip-reporter --scope wan --family ipv4 --format values
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
WAN probing has independent failover endpoints. The defaults include two
|
|
58
|
+
providers for IPv4, two for IPv6, and two candidates for each HTTP/3 family.
|
|
59
|
+
Override them with repeated `--wan-endpoint` options or with the Python
|
|
60
|
+
`RemoteEndpoint` API.
|
|
61
|
+
|
|
62
|
+
## Cloudflare DNS
|
|
63
|
+
|
|
64
|
+
Use an API token with the DNS Write permission. Prefer environment variables so
|
|
65
|
+
credentials do not enter shell history:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
export CLOUDFLARE_API_TOKEN="..."
|
|
69
|
+
export CLOUDFLARE_ZONE_ID="..."
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Update one existing record:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ip-reporter dns update \
|
|
76
|
+
--record home.example.com \
|
|
77
|
+
--family ipv4
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Update many names and both address families:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ip-reporter dns update \
|
|
84
|
+
--record home.example.com \
|
|
85
|
+
--record vpn.example.com \
|
|
86
|
+
--family ipv4 \
|
|
87
|
+
--family ipv6 \
|
|
88
|
+
--ttl 60 \
|
|
89
|
+
--not-proxied
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Existing records are updated with PATCH and unchanged values are skipped.
|
|
93
|
+
Missing records are not created unless `--create-missing` is supplied. Stale
|
|
94
|
+
records are not deleted. Use `--dry-run` to inspect discovery without changing
|
|
95
|
+
Cloudflare.
|
|
96
|
+
|
|
97
|
+
If you have a zone name rather than an ID, pass `--zone-name`; the client can
|
|
98
|
+
resolve it through the Cloudflare API. Relative names and `@` are accepted when
|
|
99
|
+
the zone name is supplied:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
ip-reporter dns update \
|
|
103
|
+
--zone-name example.com \
|
|
104
|
+
--record home \
|
|
105
|
+
--family ipv4 \
|
|
106
|
+
--create-missing
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`h3ipv4` writes an A record and `h3ipv6` writes an AAAA record. The HTTP/3
|
|
110
|
+
transport is discovery metadata; the DNS type is determined from the resulting
|
|
111
|
+
IP address.
|
|
112
|
+
|
|
113
|
+
## Python API
|
|
114
|
+
|
|
115
|
+
The structured API is suitable for another package, a scheduler, or a daemon:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from ip_reporter import AddressFamily, IPReporter
|
|
119
|
+
|
|
120
|
+
reporter = IPReporter()
|
|
121
|
+
report = reporter.discover(
|
|
122
|
+
scope="wan",
|
|
123
|
+
families=(AddressFamily.IPV4, AddressFamily.IPV6),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
for address in report.wan:
|
|
127
|
+
print(address.value, address.family.value, address.protocol.value)
|
|
128
|
+
|
|
129
|
+
# Convenience methods return only strings.
|
|
130
|
+
wan_ipv4 = reporter.get_wan(families=(AddressFamily.IPV4,))
|
|
131
|
+
lan_ipv6 = reporter.get_lan(families=(AddressFamily.IPV6,))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The discovery dependencies are injectable, which makes applications and tests
|
|
135
|
+
independent of the network:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from ip_reporter import AddressDiscovery, AddressFamily, IPReporter, RemoteEndpoint
|
|
139
|
+
|
|
140
|
+
class Probe:
|
|
141
|
+
def fetch_ip(self, url, family):
|
|
142
|
+
return ("203.0.113.10", "http2")
|
|
143
|
+
|
|
144
|
+
reporter = IPReporter(
|
|
145
|
+
AddressDiscovery(
|
|
146
|
+
wan_probe=Probe(),
|
|
147
|
+
endpoints=(RemoteEndpoint("https://probe.example/ip", (AddressFamily.IPV4,)),),
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Synchronize Cloudflare from the same service:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from ip_reporter import AddressFamily, IPReporter
|
|
156
|
+
|
|
157
|
+
updates = IPReporter().update_cloudflare_dns(
|
|
158
|
+
["home.example.com", "vpn.example.com"],
|
|
159
|
+
zone_id="your-zone-id",
|
|
160
|
+
families=(AddressFamily.IPV4, AddressFamily.IPV6),
|
|
161
|
+
create_missing=True,
|
|
162
|
+
)
|
|
163
|
+
print(updates.as_dict())
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`CloudflareClient.from_env()` uses `CLOUDFLARE_API_TOKEN`, or the legacy
|
|
167
|
+
`CLOUDFLARE_EMAIL` and `CLOUDFLARE_API_KEY` pair. A custom HTTP transport can
|
|
168
|
+
be injected into `CloudflareClient` for testing.
|
|
169
|
+
|
|
170
|
+
## Build and publish
|
|
171
|
+
|
|
172
|
+
From a checkout:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
uv sync --extra dev
|
|
176
|
+
uv run pytest
|
|
177
|
+
uv build
|
|
178
|
+
uv publish
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Set `UV_PUBLISH_TOKEN` before `uv publish`, or configure another uv-supported
|
|
182
|
+
PyPI credential method. Bump `project.version` in `pyproject.toml` for each
|
|
183
|
+
release.
|
|
184
|
+
|
|
185
|
+
Cloudflare API details: [DNS record create](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/create/), [DNS record update](https://developers.cloudflare.com/api/resources/dns/subresources/records/methods/edit/).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ip-reporter"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Reliable WAN/LAN IP discovery and Cloudflare DNS synchronization"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "IP Reporter contributors" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ip", "ipv4", "ipv6", "http3", "cloudflare", "dynamic-dns"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Operating System :: POSIX",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: System :: Networking",
|
|
29
|
+
]
|
|
30
|
+
dependencies = []
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"ruff>=0.8",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
ip-reporter = "ip_reporter.cli:main"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/ip_reporter"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
addopts = "-q"
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 100
|
|
50
|
+
target-version = "py310"
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "I", "UP"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
# Load environment variables from .env if it exists
|
|
5
|
+
if [ -f .env ]; then
|
|
6
|
+
echo "Loading environment variables from .env..."
|
|
7
|
+
# Export variables from .env, ignoring comments and empty lines
|
|
8
|
+
export $(grep -v '^#' .env | grep -v '^[[:space:]]*$' | xargs)
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
# Map PYPI_TOKEN or PYPI to UV_PUBLISH_TOKEN if present
|
|
12
|
+
if [ -n "$PYPI_TOKEN" ] && [ -z "$UV_PUBLISH_TOKEN" ]; then
|
|
13
|
+
export UV_PUBLISH_TOKEN="$PYPI_TOKEN"
|
|
14
|
+
elif [ -n "$PYPI" ] && [ -z "$UV_PUBLISH_TOKEN" ]; then
|
|
15
|
+
export UV_PUBLISH_TOKEN="$PYPI"
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -z "$UV_PUBLISH_TOKEN" ]; then
|
|
19
|
+
echo "Error: Neither UV_PUBLISH_TOKEN nor PYPI_TOKEN is set in .env or the current shell environment."
|
|
20
|
+
echo "Please create a .env file or export the variable. Example:"
|
|
21
|
+
echo "PYPI_TOKEN=pypi-..."
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "Cleaning up dist/ directory..."
|
|
26
|
+
rm -rf dist/
|
|
27
|
+
|
|
28
|
+
echo "Building package..."
|
|
29
|
+
uv build
|
|
30
|
+
|
|
31
|
+
echo "Publishing package to PyPI..."
|
|
32
|
+
uv publish
|
|
33
|
+
|
|
34
|
+
echo "Successfully published to PyPI!"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""WAN/LAN IP discovery and Cloudflare DNS synchronization."""
|
|
2
|
+
|
|
3
|
+
from .cloudflare import (
|
|
4
|
+
CloudflareAPIError,
|
|
5
|
+
CloudflareClient,
|
|
6
|
+
CloudflareError,
|
|
7
|
+
HTTPResponse,
|
|
8
|
+
HTTPTransport,
|
|
9
|
+
RecordNotFoundError,
|
|
10
|
+
UrllibTransport,
|
|
11
|
+
)
|
|
12
|
+
from .curl import (
|
|
13
|
+
CurlClient,
|
|
14
|
+
CurlError,
|
|
15
|
+
CurlFeatureError,
|
|
16
|
+
CurlProbeError,
|
|
17
|
+
CurlUnavailableError,
|
|
18
|
+
CurlVersion,
|
|
19
|
+
find_curl,
|
|
20
|
+
)
|
|
21
|
+
from .discovery import (
|
|
22
|
+
DEFAULT_WAN_ENDPOINTS,
|
|
23
|
+
AddressDiscovery,
|
|
24
|
+
DiscoveryFailedError,
|
|
25
|
+
LocalAddressProvider,
|
|
26
|
+
RemoteEndpoint,
|
|
27
|
+
SocketLocalAddressProvider,
|
|
28
|
+
WanProbe,
|
|
29
|
+
)
|
|
30
|
+
from .models import (
|
|
31
|
+
AddressFamily,
|
|
32
|
+
DiscoveredAddress,
|
|
33
|
+
DiscoveryError,
|
|
34
|
+
DiscoveryResult,
|
|
35
|
+
DNSUpdate,
|
|
36
|
+
DNSUpdateResult,
|
|
37
|
+
ProbeProtocol,
|
|
38
|
+
Scope,
|
|
39
|
+
normalize_families,
|
|
40
|
+
)
|
|
41
|
+
from .reporter import (
|
|
42
|
+
IPReporter,
|
|
43
|
+
discover,
|
|
44
|
+
get_lan,
|
|
45
|
+
get_wan,
|
|
46
|
+
update_cloudflare_dns,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"AddressDiscovery",
|
|
51
|
+
"AddressFamily",
|
|
52
|
+
"CloudflareAPIError",
|
|
53
|
+
"CloudflareClient",
|
|
54
|
+
"CloudflareError",
|
|
55
|
+
"CurlClient",
|
|
56
|
+
"CurlError",
|
|
57
|
+
"CurlFeatureError",
|
|
58
|
+
"CurlProbeError",
|
|
59
|
+
"CurlUnavailableError",
|
|
60
|
+
"CurlVersion",
|
|
61
|
+
"DEFAULT_WAN_ENDPOINTS",
|
|
62
|
+
"DNSUpdate",
|
|
63
|
+
"DNSUpdateResult",
|
|
64
|
+
"DiscoveryError",
|
|
65
|
+
"DiscoveryFailedError",
|
|
66
|
+
"DiscoveryResult",
|
|
67
|
+
"DiscoveredAddress",
|
|
68
|
+
"HTTPResponse",
|
|
69
|
+
"HTTPTransport",
|
|
70
|
+
"IPReporter",
|
|
71
|
+
"LocalAddressProvider",
|
|
72
|
+
"ProbeProtocol",
|
|
73
|
+
"RecordNotFoundError",
|
|
74
|
+
"RemoteEndpoint",
|
|
75
|
+
"Scope",
|
|
76
|
+
"SocketLocalAddressProvider",
|
|
77
|
+
"UrllibTransport",
|
|
78
|
+
"WanProbe",
|
|
79
|
+
"discover",
|
|
80
|
+
"find_curl",
|
|
81
|
+
"get_lan",
|
|
82
|
+
"get_wan",
|
|
83
|
+
"normalize_families",
|
|
84
|
+
"update_cloudflare_dns",
|
|
85
|
+
]
|