Initial project commit
This commit is contained in:
@@ -1,3 +1,270 @@
|
||||
# dyndns
|
||||
# dynDNS
|
||||
|
||||
dynDNS is a POSIX-compliant shell script for managing database-backed dynamic DNS hostnames and synchronizing them with a BIND zone. It handles hostname lifecycle operations (add, check, remove), maintains related database records and generates secure nsupdate transactions with optional DNSSEC and rndc integration.
|
||||
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 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)
|
||||
|
||||
Reference in New Issue
Block a user