# 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 ''; CREATE USER 'dyndnsuser'@'::1' IDENTIFIED BY ''; CREATE USER 'dyndnsuser'@'127.0.0.1' IDENTIFIED BY ''; 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 a site‑specific, 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)