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

8.8 KiB
Raw Permalink Blame History

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

  • 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:

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:

mariadb -u root -p

Alternatively, when using the system root account and a configured /root/.my.cnf:

sudo mariadb

Create the database and grant access to a dedicated backend user:

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:

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.

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.

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.

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

.
├── 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.

*/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:

*/5 * * * * /opt/dyndns/dyndns.sh --vars /opt/dyndns/vars update-zone >/dev/null 2>&1

License

MIT

See LICENSE for details.

Authors

CB-601 - the open tec Elevator

  • Stephan Düsterhaupt (XMPP)
  • Ivo Noack aka Insonic (XMPP)

Project Home

Project Home: https://dev.town-square.de/cb601/dyndns