Skip to main content

xms-dns

PyPI Python License: CC BY-NC 4.0

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

pip install xms-dns

For development:

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:

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

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)
xms-dns login
xms-dns version
xms-dns whoami
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
xms-dns upsert gmbros.net scrob CNAME ingress-1.gmbros.net
# CNAME targets get a trailing dot if missing → ingress-1.gmbros.net.

version and whoami

xms-dns version              # versión instalada (texto plano)
xms-dns -o json version      # {"version": "0.2.2"}

xms-dns whoami               # config cargada + validez de la sesión cacheada
xms-dns -o json whoami
# {"xms_base_url": "...", "xms_login": "admin#example.test", "session_valid": "yes"}

whoami lee XMS_BASE_URL y XMS_LOGIN desde entorno o config (no pide contraseña) y comprueba la sesión cacheada con una petición al panel.

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:

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 / X-Real-Ip for client IP and access logs
XMS_CACHE_DIR ~/.cache/xms-dns Session cache directory
XMS_HEALTH_TIMEOUT 5 Timeout in seconds for /health XMS connectivity probe

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

health

  • GET /health — JSON con el estado del servicio. Responde 200 si la URL de XMS (XMS_BASE_URL) es alcanzable; 503 si no (timeout, DNS, conexión rechazada, etc.). Cualquier respuesta HTTP 2xx o 3xx (p. ej. el 302 al login del panel) cuenta como alcanzable; no se sigue la redirección ni se autentica.
curl -s https://dyndns.example.test/health
# {"status":"ok","xms_base_url":"https://xms.digitalvalue.es","xms_reachable":true}

dyndns2

  • GET /nic/update?hostname=home.example.test&myip=... — actualización dyndns2. Respuesta en texto plano: good {value}, nochg {value}, badauth, nohost, etc. Siempre HTTP 200 (los clientes leen el cuerpo, no el código).
  • Tipos de registro: A (por defecto), AAAA, TXT.
    • A / AAAA: dirección en myip, o se usa la IP del cliente (X-Forwarded-For con XMS_TRUST_PROXY=1).
    • AAAA: type=AAAA o IPv6 en myip (auto-detectado).
    • TXT: type=TXT y valor en txt= (o value=).

Credenciales XMS vía HTTP Basic auth (username = login XMS, p. ej. admin#dominio.test).

Ejemplo ddclient:

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

Synology (proveedor DDNS personalizado):

https://__USERNAME__:__PASSWORD__@dyndns.example.test/nic/update?hostname=__HOSTNAME__&myip=__MYIP__

checkip (compatible con ifconfig.me)

Ruta Respuesta
GET /checkip IPv4 del cliente (texto plano)
GET /checkip?format=pfsense ip=… (legacy pfSense)
GET /checkip/ip IPv4
GET /checkip/ua cabecera User-Agent
GET /checkip/lang Accept-Language
GET /checkip/encoding Accept-Encoding
GET /checkip/mime Accept
GET /checkip/charset Accept-Charset
GET /checkip/forwarded X-Forwarded-For (raw)
GET /checkip/all todos los campos, una línea key: value por campo
GET /checkip/all.json mismo contenido en JSON
curl https://dyndns.example.test/checkip
curl https://dyndns.example.test/checkip/ua
curl https://dyndns.example.test/checkip/all
curl https://dyndns.example.test/checkip/all.json

Campos en /all y /all.json: ip_addr, remote_host (siempre unavailable), user_agent, port, language, referer, connection, keep_alive, method, encoding, mime, charset, via, forwarded.

En un navegador (cabecera Accept: text/html), la raíz de checkip (/checkip o / vía host dedicado) muestra una página HTML con la tabla de conexión y ejemplos CLI. curl y clientes API siguen recibiendo texto plano.

Access log

El servidor escribe una línea de access log por petición (formato uvicorn), con la IP real del cliente cuando XMS_TRUST_PROXY=1 (lee X-Forwarded-For / X-Real-Ip).

Ejemplos:

INFO:     170.253.60.198:42188 - "GET /checkip (ip=170.253.60.198) HTTP/1.1" 200 OK
INFO:     170.253.60.198:42188 - "GET /nic/update?hostname=home.example.test&myip=… (good type=A value=170.253.60.198) HTTP/1.1" 200 OK
WARNING:  170.253.60.198:42188 - "GET /nic/update?hostname=… (badauth type=A reason=XMS authentication failed) HTTP/1.1" 200 OK

Los logs de dyndns2 no incluyen login ni otras credenciales.

Library

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:

pytest

Releasing

CI runs on every push/PR to main (tests only). PyPI publish runs on tags, not on every push.

The package version comes from the git tag (hatch-vcs), not from pyproject.toml:

# 1. Update CHANGELOG.md
# 2. Commit and push
git commit -am "Release 0.1.2"
git push origin main

# 3. Tag and push — version 0.1.2 is taken from v0.1.2
git tag v0.1.2
git push origin v0.1.2

One-time PyPI setup (Trusted Publishing)

No API token in GitHub secrets. Configure once in PyPI:

  1. https://pypi.org/manage/project/xms-dns/settings/publishing/
  2. Add a new pending publisher → GitHub
  3. Owner: soukron, repository: xms-dns, workflow: publish.yml, environment: pypi
  4. In GitHub: repo Settings → Environments → New environment → name it pypi (no secrets needed)

Live smoke test

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: non-commercial use with attribution required.

Release files for xms-dns 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xms-dns 0.2.5
File Size Uploaded
xms_dns-0.2.5.tar.gz 43.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xms-dns 0.2.5
File Interpreter ABI Platform
xms_dns-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 98.6 kB

Release files / xms_dns-0.2.5.tar.gz

Download URL xms_dns-0.2.5.tar.gz
Size 43.7 kB
Tags Source
SHA-256 checksum
How to use checksums
fb104dcb8cab61f935131e7167d71d0346e405b32da009f051a2bc6a9b8bd047
BLAKE2b-256 checksum
How to use checksums
2678bbe24ab1b779f39798b4371be4d17910e3deeb090877b1c00498291557a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release files / xms_dns-0.2.5-py3-none-any.whl

Download URL xms_dns-0.2.5-py3-none-any.whl
Size 54.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ea2a3e40c795cf52f0bca313f5f8e0f71341e87d38af889bfcc9929507385d62
BLAKE2b-256 checksum
How to use checksums
1b0494d76785a16dd2827c022fc572525fe34fc390e3e5e5746cd42642438a27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page