Add README and remove hardcoded resolver
This commit is contained in:
150
README.md
Normal file
150
README.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Technitium DDNS
|
||||
|
||||
Python utility that fetches the host's public IPv4 and IPv6 and updates `A` and `AAAA` records for a given hostname in a zone hosted on a Technitium DNS server.
|
||||
|
||||
This repository contains a single service that supports two update backends:
|
||||
|
||||
- **HTTP_API** — use Technitium's HTTP API (token-based) to add/overwrite records.
|
||||
- **RFC2136** — use standard DNS dynamic updates (RFC2136), optionally authenticated with a TSIG key.
|
||||
|
||||
The service runs in a continuous loop by default and updates every `UPDATE_INTERVAL` seconds (configurable via environment variables).
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Create `.env` and fill in secrets and other variables.
|
||||
2. Build and run:
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Or to run with Docker directly:
|
||||
|
||||
```bash
|
||||
docker build -t technitium-ddns .
|
||||
docker run -d --name technitium-ddns --env-file .env technitium-ddns
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
You can set these in `.env`, via `docker-compose.yml`, or when running the container.
|
||||
|
||||
General:
|
||||
- `LOG_LEVEL` - `DEBUG`, `INFO`, `WARNING`, `ERROR` or `FATAL`.
|
||||
- `LOG_FILE` - path of the log file or empty to log to stdout/stderr.
|
||||
- `BACKEND` — `HTTP_API` (default) or `RFC2136`.
|
||||
- `RUN_ONCE` — `1` to run a single update and exit, `0` to loop continuously (default `0`).
|
||||
- `UPDATE_INTERVAL` — seconds between updates when looping (default `300`).
|
||||
- `ZONE` — DNS zone to update the record into.
|
||||
- `RECORD_NAME` — record name (left part) — the script updates `<RECORD_NAME>.<ZONE>`.
|
||||
- `TTL` — record TTL in seconds (default `300`).
|
||||
- `SKIP_IPV6` — set to `1` to skip AAAA updates.
|
||||
|
||||
HTTP API backend (Technitium):
|
||||
|
||||
- `TDNS_HOST` — Technitium base URL.
|
||||
- `TDNS_API_TOKEN` — API token generated in Technitium web console (**required** for `HTTP_API`).
|
||||
|
||||
RFC2136 backend:
|
||||
|
||||
- `RFC2136_SERVER` — authoritative DNS server IP or hostname for dynamic updates (required for `RFC2136`).
|
||||
- `RFC2136_PORT` — port (default `53`).
|
||||
- `RFC2136_TSIG_NAME` — optional TSIG key name.
|
||||
- `RFC2136_TSIG_KEY` — optional TSIG key (base64).
|
||||
- `RFC2136_TSIG_ALGO` — optional TSIG algorithm (default `hmac-sha256.`).
|
||||
|
||||
## Example `.env`
|
||||
|
||||
```ini
|
||||
# Log level and log file
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=/var/log/technitium-ddns.log
|
||||
|
||||
# Choose HTTP_API or RFC2136
|
||||
BACKEND=HTTP_API
|
||||
|
||||
# If RUN_ONCE=1 the container runs once and exits (good for host cron).
|
||||
RUN_ONCE=0
|
||||
UPDATE_INTERVAL=300
|
||||
|
||||
ZONE=example.com
|
||||
RECORD_NAME=ddns
|
||||
TTL=300
|
||||
SKIP_IPV6=0
|
||||
|
||||
# HTTP API (Technitium)
|
||||
TDNS_HOST=http://technitium:5380
|
||||
TDNS_API_TOKEN=YOUR_TECHNITIUM_TOKEN_HERE
|
||||
|
||||
# RFC2136 (if using RFC2136 backend)
|
||||
RFC2136_SERVER=192.0.2.5
|
||||
RFC2136_PORT=53
|
||||
# Optional TSIG:
|
||||
RFC2136_TSIG_NAME=ddns-key
|
||||
RFC2136_TSIG_KEY=base64encodedkey==
|
||||
RFC2136_TSIG_ALGO=hmac-sha256.
|
||||
```
|
||||
|
||||
## How it behaves
|
||||
|
||||
- On each run (single-run or loop iteration) the script:
|
||||
1. Detects public IPv4 (via `api.ipify.org`, fallback to other services).
|
||||
2. Detects public IPv6 (unless `SKIP_IPV6=1`).
|
||||
3. Queries current `A` / `AAAA` values; if they already match the current public IP, update is skipped.
|
||||
4. Otherwise updates the record(s) using the selected backend.
|
||||
- `HTTP_API` backend calls Technitium endpoint `/api/zones/records/add` with `overwrite=true`.
|
||||
- `RFC2136` backend issues a dynamic DNS update packet and can use TSIG for authentication.
|
||||
|
||||
## Running as a cron job on the host (recommended for many setups)
|
||||
|
||||
If you prefer not to run a continuously-looping container, set `RUN_ONCE=1` and run the container from a host cron or systemd timer:
|
||||
|
||||
Example host systemd timer unit (recommended):
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/tdns-updater-run.service
|
||||
[Unit]
|
||||
Description=Run tdns-updater once
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=/path/to/project
|
||||
ExecStart=/usr/bin/docker compose run --rm tdns-updater
|
||||
```
|
||||
|
||||
```ini
|
||||
# /etc/systemd/system/tdns-updater-run.timer
|
||||
[Unit]
|
||||
Description=Run tdns-updater every 5 minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=1min
|
||||
OnUnitActiveSec=5min
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
```
|
||||
|
||||
## RFC2136 notes
|
||||
|
||||
- Many servers expect dynamic updates to come from an allowed IP or require TSIG authentication. If using Technitium as the RFC2136 target, check its dynamic update / ACL settings and configure TSIG if required.
|
||||
- The script supports TSIG via `RFC2136_TSIG_NAME` and `RFC2136_TSIG_KEY` (base64). Provide `RFC2136_TSIG_ALGO` like `hmac-sha256.` if needed.
|
||||
|
||||
## TLS / self-signed certs
|
||||
|
||||
If your `TDNS_HOST` is `https://` with a self-signed certificate, mount CA certificate(s) into the container and set `REQUESTS_CA_BUNDLE` or `SSL_CERT_FILE` appropriately, or use valid certificates to avoid modifying the image:
|
||||
|
||||
Example `docker-compose.yml` snippet for a CA bundle file:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./my-ca.pem:/usr/local/share/ca-certificates/my-ca.crt:ro
|
||||
```
|
||||
|
||||
(then update CA store inside the image at build time, or pass `REQUESTS_CA_BUNDLE` pointing to the file.)
|
||||
|
||||
## Security
|
||||
|
||||
- Treat `TDNS_API_TOKEN` and `RFC2136_TSIG_KEY` as secrets. Do not commit them to VCS.
|
||||
- For production, use Docker secrets, environment injection from your orchestrator, or mount a read-only file containing secrets.
|
@@ -157,7 +157,6 @@ def rfc2136_update(record_type: str, ip: str) -> Tuple[bool, Any]:
|
||||
def query_current_records(record_type: str) -> list:
|
||||
try:
|
||||
resolver = dns.resolver
|
||||
resolver.nameservers = ['192.168.178.2']
|
||||
answers = resolver.resolve(FULL_DOMAIN, record_type, lifetime=5)
|
||||
return [r.to_text() for r in answers]
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout, dns.resolver.NoNameservers):
|
||||
|
Reference in New Issue
Block a user