151 lines
4.9 KiB
Markdown
151 lines
4.9 KiB
Markdown
# 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.
|