Metadata-Version: 2.4
Name: ip-reporter
Version: 0.1.5
Summary: Reliable WAN/LAN IP discovery and Cloudflare DNS synchronization
Author: IP Reporter contributors
License: MIT License
        
        Copyright (c) 2026 IP Reporter contributors
        
        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.
License-File: LICENSE
Keywords: cloudflare,dynamic-dns,http3,ip,ipv4,ipv6
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Provides-Extra: prefect
Requires-Dist: prefect>=3.0; extra == 'prefect'
Description-Content-Type: text/markdown

# ip-reporter

`ip-reporter` is a dependency-free Python package and CLI for discovering local
LAN addresses, public WAN addresses, and public addresses obtained through
HTTP/2 or HTTP/3. It can synchronize one or many A/AAAA records in Cloudflare.

The import name is `ip_reporter`; the installed command is `ip-reporter`.

## Install with uv

Install the command as a uv tool:

```bash
uv tool install ip-reporter
ip-reporter --scope wan --family ipv4
```

Use it as a library dependency:

```bash
uv add ip-reporter
```

For HTTP/2 and HTTP/3 probing on macOS, install Homebrew curl. The package
rejects `/usr/bin/curl` on macOS so the system curl cannot silently disable
those probes.

```bash
brew install curl
export IP_REPORTER_CURL="$(brew --prefix curl)/bin/curl"
```

The selected curl must report `HTTP2` or `HTTP3` in `curl --version` depending
on the requested probe. HTTP/3 requires a curl build with HTTP/3 support.

## CLI

With no subcommand, the CLI performs discovery. The default family is IPv4.

```bash
# Public IPv4; text output includes the scope, family, and transport.
ip-reporter --scope wan --family ipv4

# LAN IPv4 and IPv6.
ip-reporter discover --scope lan --family ipv4 --family ipv6

# Force HTTP/3 over IPv4 and IPv6.
ip-reporter --scope wan --family h3ipv4 --family h3ipv6

# Structured output for scripts.
ip-reporter --scope both --family ipv4 --family ipv6 --format json

# Print only address values, one per line.
ip-reporter --scope wan --family ipv4 --format values
```

WAN probing has independent failover endpoints. The defaults include two
providers for IPv4, two for IPv6, and two candidates for each HTTP/3 family.
Override them with repeated `--wan-endpoint` options or with the Python
`RemoteEndpoint` API.

## Internet speed

Run a simple receive/download and send/upload throughput check against
Cloudflare's edge:

```bash
ip-reporter speed
```

Use `ip-reporter speed --format json` for structured output. The defaults
download 10 MB and upload 2 MB. This is a quick single-transfer estimate, not a
full multi-connection benchmark; sample sizes can be changed with
`--download-bytes` and `--upload-bytes`.

The same check is available from Python:

```python
from ip_reporter import check_internet_speed

speed = check_internet_speed()
print(speed.download_mbps, speed.upload_mbps)
```

## Cloudflare DNS

Use an API token with the DNS Write permission. Prefer environment variables so
credentials do not enter shell history:

```bash
export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ZONE_ID="..."
```

Update one existing record:

```bash
ip-reporter dns update \
  --record home.example.com \
  --family ipv4
```

Update many names and both address families:

```bash
ip-reporter dns update \
  --record home.example.com \
  --record vpn.example.com \
  --family ipv4 \
  --family ipv6 \
  --ttl 60 \
  --not-proxied
```

Existing records are updated with PATCH and unchanged values are skipped.
Missing records are not created unless `--create-missing` is supplied. Stale
records are not deleted. Use `--dry-run` to inspect discovery without changing
Cloudflare.

If you already know the Cloudflare record ID, pass it to skip the record-list
request. Direct-ID mode supports exactly one DNS name and one discovered IP:

```bash
ip-reporter dns update \
  --record home.example.com \
  --record-id your-record-id \
  --family ipv4
```

Because direct-ID mode does not read the record first, a successful PATCH is
reported as `updated` even when the value at Cloudflare was already identical.

If you have a zone name rather than an ID, pass `--zone-name`; the client can
resolve it through the Cloudflare API. Relative names and `@` are accepted when
the zone name is supplied:

```bash
ip-reporter dns update \
  --zone-name example.com \
  --record home \
  --family ipv4 \
  --create-missing
```

`h3ipv4` writes an A record and `h3ipv6` writes an AAAA record. The HTTP/3
transport is discovery metadata; the DNS type is determined from the resulting
IP address.

## Python API

The structured API is suitable for another package, a scheduler, or a daemon:

```python
from ip_reporter import AddressFamily, IPReporter

reporter = IPReporter()
report = reporter.discover(
    scope="wan",
    families=(AddressFamily.IPV4, AddressFamily.IPV6),
)

for address in report.wan:
    print(address.value, address.family.value, address.protocol.value)

# Convenience methods return only strings.
wan_ipv4 = reporter.get_wan(families=(AddressFamily.IPV4,))
lan_ipv6 = reporter.get_lan(families=(AddressFamily.IPV6,))
```

The discovery dependencies are injectable, which makes applications and tests
independent of the network:

```python
from ip_reporter import AddressDiscovery, AddressFamily, IPReporter, RemoteEndpoint

class Probe:
    def fetch_ip(self, url, family):
        return ("203.0.113.10", "http2")

reporter = IPReporter(
    AddressDiscovery(
        wan_probe=Probe(),
        endpoints=(RemoteEndpoint("https://probe.example/ip", (AddressFamily.IPV4,)),),
    )
)
```

Synchronize Cloudflare from the same service:

```python
from ip_reporter import AddressFamily, IPReporter

updates = IPReporter().update_cloudflare_dns(
    "home.example.com",
    zone_id="your-zone-id",
    record_id="your-record-id",
    families=(AddressFamily.IPV4,),
)
print(updates.as_dict())
```

`CloudflareClient.from_env()` uses `CLOUDFLARE_API_TOKEN`, or the legacy
`CLOUDFLARE_EMAIL` and `CLOUDFLARE_API_KEY` pair. A custom HTTP transport can
be injected into `CloudflareClient` for testing.

## Prefect IPv4 task

Install the optional Prefect integration, then use the task in a deployment or
call it directly. It persists the last IPv4 to the defined state path and only
calls Cloudflare when the newly discovered value differs. The default state
path is `~/.local/state/ip-reporter/wan_ipv4.txt`; provide an absolute path in
production so the state survives task-run environments.

```bash
uv add 'ip-reporter[prefect]'
export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ZONE_ID="..."
```

```python
from ip_reporter.prefect import fetch_ipv4_and_sync_cloudflare

result = fetch_ipv4_and_sync_cloudflare(
    ["home.example.com", "vpn.example.com"],
    ipv4_state_path="/var/lib/ip-reporter/home-ipv4.txt",
    create_missing=True,
)
print(result)
```

The state file is atomically written only after Cloudflare succeeds. If the
task fails, its next run will retry the DNS synchronization rather than assume
the address was updated.

## Build and publish

From a checkout:

```bash
uv sync --extra dev
uv run pytest
uv build
uv publish
```

Set `UV_PUBLISH_TOKEN` before `uv publish`, or configure another uv-supported
PyPI credential method. Bump `project.version` in `pyproject.toml` for each
release.

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/).
