Metadata-Version: 2.4
Name: xms-dns
Version: 0.1.0
Summary: Cliente Python para gestionar DNS en el panel XMS (Digital Value). CLI, biblioteca y servidor dyndns2/checkip compatible con ddclient. A, AAAA y TXT.
Project-URL: Homepage, https://github.com/soukron/xms-dns
Project-URL: Repository, https://github.com/soukron/xms-dns
Project-URL: Issues, https://github.com/soukron/xms-dns/issues
Author-email: Sergio Garcia <soukron@gmbros.net>
Maintainer-email: Sergio Garcia <soukron@gmbros.net>
License-Expression: CC-BY-NC-4.0
License-File: LICENSE
Keywords: ddclient,digitalvalue,dns,dyndns,xms
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: License :: Free for non-commercial use
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: Name Service (DNS)
Classifier: Topic :: System :: Networking
Requires-Python: >=3.9
Requires-Dist: beautifulsoup4>=4.12
Requires-Dist: fastapi>=0.100
Requires-Dist: httpx>=0.27
Requires-Dist: lxml>=5.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
Requires-Dist: uvicorn>=0.23
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# xms-dns

Cliente Python para gestionar DNS en el panel XMS (Digital Value). Incluye CLI (`xms-dns`), biblioteca y servidor dyndns2/checkip (`xms-dns-server`) compatible con ddclient. Soporta registros A, AAAA y TXT.

## Setup

```bash
pip install xms-dns
```

For development:

```bash
git clone https://github.com/soukron/xms-dns.git
cd xms-dns
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

### Config file (optional, no secrets)

Create `~/.config/xms-dns/config` with non-sensitive settings only:

```bash
mkdir -p ~/.config/xms-dns
cat > ~/.config/xms-dns/config <<'EOF'
XMS_BASE_URL=https://xms.digitalvalue.es
XMS_LOGIN=admin#example.test
EOF
chmod 600 ~/.config/xms-dns/config
```

Passwords are **not** read from this file. Use `xms-dns login` instead.

Environment variables override the config file. Override paths with `XMS_CONFIG_DIR` or `XMS_CACHE_DIR`.

### Login

```bash
xms-dns login
```

Resolves settings in this order:

1. Environment variables (`XMS_BASE_URL`, `XMS_LOGIN`, `XMS_PASSWORD`)
2. Config file (`XMS_BASE_URL`, `XMS_LOGIN` only)
3. Interactive prompts for anything still missing (password always prompted unless in env)

Connection type is always `siempre` (permanent session). On success, the session cookie is saved to `~/.cache/xms-dns/auth.cache`.

### HTTP calls

Login and operations are separate executions:

- **`xms-dns login`** — authenticates against the panel and saves the session (its own HTTP calls; not counted against other commands).
- **Any other command** — exactly **one** HTTP request to the panel, using the cached session. Credentials are never sent again.

Example: `xms-dns domains` is a single `GET` to `Operacion=Dominios`. `xms-dns list` is a single `GET` to load the zone.

The only exception is **`upsert` when the value changes**: XMS has no inline edit, so the client deletes and recreates the record (two or three requests).

## CLI

Global flags:

- `-o` / `--output` — output format: `table` (default), `csv`, `json`, or `yaml`
- `-v` / `--verbose` — log HTTP requests to stderr
- `--debug` — log requests and response bodies to stderr (includes verbose)

```bash
xms-dns login
xms-dns list gmbros.net
xms-dns -o csv list gmbros.net
xms-dns -o json domains
xms-dns -v list gmbros.net
xms-dns --debug upsert gmbros.net dyndns-test A 203.0.113.10
```

DNS commands use the cached session only. If not logged in or session expired:

```
error: Not logged in. Run: xms-dns login
```

## Server (dyndns2 + checkip)

Start the dynamic DNS server:

```bash
xms-dns-server
```

Environment variables:

| Variable | Default | Purpose |
|----------|---------|---------|
| `XMS_BASE_URL` | `https://xms.digitalvalue.es` | XMS panel URL |
| `XMS_SERVER_HOST` | `127.0.0.1` | Bind address |
| `XMS_SERVER_PORT` | `8080` | Bind port |
| `XMS_TRUST_PROXY` | unset | Set to `1` to trust `X-Forwarded-For` for client IP |
| `XMS_CACHE_DIR` | `~/.cache/xms-dns` | Session cache directory |

> **Reverse proxy (production):** if `xms-dns-server` runs behind nginx, Caddy, Traefik, etc., set **`XMS_TRUST_PROXY=1`**. Otherwise `/checkip` and updates without `myip` see the proxy IP (`127.0.0.1`) instead of the client. Only enable this when a trusted proxy overwrites `X-Forwarded-For` — do not expose the server directly to the internet with this flag on.

### Authentication

The server does **not** use `xms-dns login`. Each dyndns2 client sends XMS credentials via HTTP Basic auth:

- `username` → XMS login (e.g. `admin#example.test`)
- `password` → XMS password

The session cookie is cached per user in `~/.cache/xms-dns/server/{sha256}.cache` (separate from the CLI `auth.cache`). If the cached session expires, the server re-authenticates with the credentials from the current request.

### Endpoints

- `GET /nic/update?hostname=home.example.test&myip=...` — dyndns2 update. Returns `good {value}`, `nochg {value}`, `badauth`, etc. Always HTTP 200.
- Record types: **A** (default), **AAAA**, **TXT**.
  - A / AAAA: pass the address in `myip`, or omit it to use the client IP (`X-Forwarded-For` when `XMS_TRUST_PROXY=1`).
  - AAAA: set `type=AAAA`, or pass an IPv6 address in `myip` (auto-detected).
  - TXT: set `type=TXT` and pass the record data in `txt=` (or `value=`).
- `GET /checkip` — returns the caller's public IPv4. Optional `?format=pfsense` for legacy pfSense.

If `myip` is omitted, the client IP is used (see `X-Forwarded-For` when `XMS_TRUST_PROXY=1`).

### ddclient example

```
protocol=dyndns2
server=dyndns.example.test
login=admin#example.test
password=your-xms-password
home.example.test
```

Put TLS termination on a reverse proxy in production; bind locally by default. Remember **`XMS_TRUST_PROXY=1`** on the server process (see above).

## Library

```python
from xms_dns import XmsClient

# Login once (CLI does this via `xms-dns login`)
with XmsClient(base_url="...", login="...", password="...") as client:
    client.authenticate()

# Later operations reuse auth.cache
with XmsClient.from_env() as client:
    client.ensure_session()
    client.upsert_record("gmbros.net", "home", "A", "203.0.113.10")
```

## Tests

All default tests use mocked HTTP responses and **do not** contact the live XMS panel:

```bash
pytest
```

## Live smoke test

```bash
xms-dns login   # or set XMS_PASSWORD for one-shot login inside the script
XMS_LIVE=1 python scripts/smoke_test.py
```

## Security

- Do not store passwords in the config file
- Keep `~/.cache/xms-dns/auth.cache` private (`chmod 600`)
- Keep server session caches private too: `~/.cache/xms-dns/server/*.cache` (`chmod 600`)

## License

Copyright (c) 2026 Sergio Garcia.

Licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/): non-commercial use with attribution required.
