Files
dyndns/README.md
T
2026-06-20 13:03:42 +02:00

271 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dynDNS
Lightweight dynDNS backend for BIND-based dynamic DNS updates.
This repository contains only the backend components of the dynDNS service: the main `dyndns.sh` script and a `vars.example` configuration template. It is designed as a backend component behind a web frontend or API that manages member accounts and IP assignments.
## Outline
- [Features](#features)
- [Installation](#installation)
- [MariaDB Initialization](#mariadb-initialization)
- [Database Schema](#database-schema)
- [Configuration](#configuration)
- [Directory Layout](#directory-layout)
- [CRON Job Example](#cron-job-example)
- [License](#license)
- [Authors](#authors)
- [Project Home](#project-home)
## Features
- Dynamic DNS backend script for BIND-based environments
- Database-driven record management
- A and AAAA record handling
- Wildcard CNAME generation for managed subdomains
- `nsupdate` integration with TSIG authentication
- DNSSEC-aware update workflow
- Simple configuration through a separate vars file
- Designed for self-hosted Linux environments
## Installation
1. Clone the repository.
2. Copy `vars.example` to a site-specific vars file.
3. Adjust paths, database settings, BIND zone settings, and TSIG key references.
4. Make `dyndns.sh` executable.
5. Run the script manually once before automation.
Example:
```sh
sudo groupadd --system dyndns
sudo install -d -m 0750 -o root -g dyndns /opt/dyndns
sudo git clone https://dev.town-square.de/cb601/dyndns.git /opt/dyndns
cd /opt/dyndns
sudo cp vars.example vars
sudo chown root:dyndns /opt/dyndns/dyndns.sh /opt/dyndns/vars
sudo chmod 0750 /opt/dyndns/dyndns.sh
sudo chmod 0640 /opt/dyndns/vars
sudo /opt/dyndns/dyndns.sh help
```
The `dyndns.sh` script is owned by root and readable by the dyndns group only. This keeps the contents of the `vars` file (including database credentials) restricted to the backend.
## MariaDB Initialization
Before running the dynDNS backend for the first time, create the application database and a dedicated database user. Using a dedicated user with privileges limited to the dynDNS database is the recommended setup.
Connect to MariaDB as an administrative user:
```sh
mariadb -u root -p
```
Alternatively, when using the system root account and a configured `/root/.my.cnf`:
```sh
sudo mariadb
```
Create the database and grant access to a dedicated backend user:
```sql
CREATE DATABASE dyndns;
CREATE USER 'dyndnsuser'@'localhost' IDENTIFIED BY '<REPLACE_WITH_STRONG_PASSWORD>';
CREATE USER 'dyndnsuser'@'::1' IDENTIFIED BY '<REPLACE_WITH_STRONG_PASSWORD>';
CREATE USER 'dyndnsuser'@'127.0.0.1' IDENTIFIED BY '<REPLACE_WITH_STRONG_PASSWORD>';
GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'localhost';
GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'::1';
GRANT ALL PRIVILEGES ON dyndns.* TO 'dyndnsuser'@'127.0.0.1';
FLUSH PRIVILEGES;
```
This setup allows local access for the dedicated dynDNS user from `localhost`, `127.0.0.1`, and `::1`, and keeps the dynDNS backend user restricted to the `dyndns` database and local connections only.
After database initialization, adjust the matching settings in your vars file:
```sh
DYNDNS_SQL_HOST="localhost"
DYNDNS_SQL_DATABASE="dyndns"
DYNDNS_SQL_USER="dyndnsuser"
DYNDNS_SQL_PASS="<REPLACE_WITH_STRONG_PASSWORD>"
```
Replace `<REPLACE_WITH_STRONG_PASSWORD>` with a sitespecific, strong password.
A first manual connection test is recommended before enabling cron-based automation, so database connectivity and privileges can be verified early.
## Database Schema
The dynDNS backend expects a MariaDB schema with three core tables:
- `members` for account and hostname ownership data
- `connections` for the currently assigned IP address per member
- `login_attempts` for tracking login attempts
The following definitions reflect the currently used database layout.
### Table: `members`
Stores dynDNS account identity and hostname ownership data.
```sql
CREATE TABLE `members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(63) NOT NULL,
`domain` varchar(253) NOT NULL,
`email` varchar(254) NOT NULL,
`password` varchar(255) NOT NULL,
`salt` varchar(255) NOT NULL,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`,`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
Field overview:
- `id`: Internal primary key
- `username`: Host-specific account name, usually the subdomain part
- `domain`: Managed DNS zone name
- `email`: Contact address of the member
- `password`: Stored password hash
- `salt`: Per-user salt used for password hashing
- `timestamp`: Record timestamp stored as unsigned Unix time (seconds since epoch)
- `UNIQUE (username, domain)`: Ensures that a hostname can only exist once per zone
### Table: `connections`
Stores the currently active IP address assignment for a dynDNS member.
```sql
CREATE TABLE `connections` (
`id` int(11) NOT NULL,
`IP` varchar(45) NOT NULL,
`IPv6_flag` tinyint(1) unsigned NOT NULL DEFAULT 0,
`IP_locked` tinyint(1) unsigned NOT NULL DEFAULT 0,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_connections_members`
FOREIGN KEY (`id`) REFERENCES `members` (`id`)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
Field overview:
- `id`: Shared primary key and foreign key referencing `members.id`
- `IP`: Current IP address in text form; `varchar(45)` supports both IPv4 and IPv6
- `IPv6_flag`: `0` for A records, `1` for AAAA records
- `IP_locked`: Prevents automated DNS updates when set
- `timestamp`: Record timestamp stored as unsigned Unix time (seconds since epoch)
### Table: `login_attempts`
Stores the login attempt counter for a member.
```sql
CREATE TABLE `login_attempts` (
`id` int(11) NOT NULL,
`count` smallint(5) unsigned NOT NULL DEFAULT 0,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_login_attempts_members`
FOREIGN KEY (`id`) REFERENCES `members` (`id`)
ON DELETE CASCADE
ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```
Field overview:
- `id`: Shared primary key and foreign key referencing `members.id`
- `count`: Number of tracked login attempts
- `timestamp`: Record timestamp stored as unsigned Unix time (seconds since epoch)
### Referential Integrity
The schema uses explicit foreign key constraints from `connections.id` and `login_attempts.id` to `members.id`. Both child tables use `ON DELETE CASCADE`, so dependent records are removed automatically when a member is deleted. This ensures database-level referential integrity and matches the intended one-to-one relationship design.
When deleting a member, the application only needs to remove the parent row from `members`; dependent rows in `connections` and `login_attempts` are removed automatically by the database.
### Notes
- The schema uses `utf8mb4` with `utf8mb4_unicode_ci`.
- `username` uses `varchar(63)`, which matches the maximum length of a single DNS label.
- `domain` uses `varchar(253)`, which matches the typical maximum length of a FQDN without trailing root dot.
- IP addresses are stored as text in `varchar(45)`, which is sufficient for IPv4 and IPv6 storage.
## Configuration
The backend is configured through a separate configuration file derived from `vars.example`.
Typical settings include:
- Base paths and log paths
- Database connection settings
- DNS zone name and zone file paths
- BIND server endpoint
- TSIG key file used for `nsupdate` and `rndc`
- Default TTL values
Recommended workflow:
1. Start from `vars.example`.
2. Create an environment-specific copy, for example `vars`.
3. Keep secrets and local overrides out of version control.
4. Validate database access and BIND update permissions before enabling automation.
## Directory Layout
```text
.
├── dyndns.sh
├── vars.example
├── README.md
├── Changelog.md
├── LICENSE
└── .gitignore
```
## CRON Job Example
The following example assumes the job is installed in the crontab of a dedicated service user or root, with sufficient read access to the vars file and execute permissions on `/opt/dyndns/dyndns.sh`.
```sh
*/5 * * * * /opt/dyndns/dyndns.sh update-zone >/dev/null 2>&1
```
Running the job every 5 minutes is a reasonable default for typical dynDNS use cases.
If a non-default vars file is used:
```sh
*/5 * * * * /opt/dyndns/dyndns.sh --vars /opt/dyndns/vars update-zone >/dev/null 2>&1
```
## License
[MIT](https://dev.town-square.de/cb601/dyndns/src/branch/main/LICENSE)
See `LICENSE` for details.
## Authors
CB-601 - the open tec Elevator
- Stephan Düsterhaupt ([XMPP](xmpp:me@jabber.stephanduesterhaupt.de))
- Ivo Noack aka Insonic ([XMPP](xmpp:me@jabber.ivonoack.de))
## Project Home
Project Home: [https://dev.town-square.de/cb601/dyndns](https://dev.town-square.de/cb601/dyndns)