Metadata-Version: 2.4
Name: bindantic
Version: 2.0.0
Summary: Pydantic-based BIND9 configuration management library
Author-email: Daniil Gruzdev <gruzdev.daniil@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/DVSAWR/bindantic
Project-URL: Issue Tracker, https://github.com/DVSAWR/bindantic/issues
Keywords: bind,bind9,dns,pydantic,configuration,named,named.conf
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Topic :: Internet :: Name Service (DNS)
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3.0.0,>=2.12.5
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
Requires-Dist: mypy>=1.8.0; extra == "dev"
Requires-Dist: ruff>=0.3.0; extra == "dev"
Requires-Dist: pre-commit>=3.6.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs>=1.6.0; extra == "docs"
Requires-Dist: mkdocs-material>=9.5.0; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.25.0; extra == "docs"
Requires-Dist: griffe-pydantic>=1.0.0; extra == "docs"
Dynamic: license-file

# bindantic

[![PyPI version](https://img.shields.io/pypi/v/bindantic)](https://pypi.org/project/bindantic/)
[![Python versions](https://img.shields.io/pypi/pyversions/bindantic)](https://pypi.org/project/bindantic/)
[![License](https://img.shields.io/github/license/DVSAWR/bindantic)](LICENSE)
[![CI](https://github.com/DVSAWR/bindantic/actions/workflows/ci.yml/badge.svg)](https://github.com/DVSAWR/bindantic/actions/workflows/ci.yml)
[![Coverage](https://codecov.io/gh/DVSAWR/bindantic/branch/main/graph/badge.svg)](https://codecov.io/gh/DVSAWR/bindantic)
[![Docs](https://img.shields.io/badge/docs-mkdocs-blue)](https://DVSAWR.github.io/bindantic/)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/bindantic?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/bindantic)

**bindantic** is a library for managing **[BIND9](https://bind9.readthedocs.io/en/latest/reference.html)**
DNS server configuration via **[Pydantic](https://github.com/pydantic/pydantic)** models.

Instead of manually editing `named.conf`, you describe the configuration in Python, and the
library generates correct BIND9 syntax and (optionally) places files into the required
directories.

## Table of contents

- [Features](#features)
- [Installation](#installation)
- [Quick start](#quick-start)
- [More examples](#more-examples)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [Versioning](#versioning)
- [License](#license)

## Features

- **Full support for all `named.conf` blocks** - `acl`, `controls`, `dnssec-policy`, `http`, `key`,
  `key-store`, `logging`, `options`, `remote-servers`, `server`, `statistics-channels`, `tls`,
  `trust-anchors`, `view`, `zone`.
- **All common resource record types** - `A`, `AAAA`, `CAA`, `CERT`, `CNAME`, `DNAME`, `DNSKEY`,
  `DS`, `HINFO`, `LOC`, `MX`, `NAPTR`, `NSEC`, `NS`, `PTR`, `RP`, `RRSIG`, `SOA`, `SPF`, `SRV`,
  `SSHFP`, `TLSA`, `TXT`.
- **Built-in validation** - pass strings, numbers, IP addresses, durations, and bindantic formats
  them correctly for BIND.
- **Validated against a real BIND9, not just unit tests on strings** - CI generates configuration
  for every block and resource record type and runs it through a real `named-checkconf`, so the
  output isn't just "what we believe BIND9 syntax looks like."
- **Syntax generation in one line** - `model.model_bind_syntax()` for any block or the whole
  `named.conf`, `zone.model_bind_syntax_zone_file()` for a ready-to-use zone file.
- **Generate files without writing, or write straight to disk** - `config.generate_files()`
  returns a list of generated files; `config.write_files("./my_config")` creates `named.conf`,
  zones, keys, and DNSSEC policies, organised into subdirectories.
- **Python 3.10+**, static typing (`py.typed`), 97%+ test coverage.
- **No extra dependencies** - only Pydantic.

## Installation

```bash
pip install bindantic
```

> [!WARNING]
> **`named-checkconf` version**: bindantic generates syntax according to the latest stable
> BIND 9.20.x release. The `named-checkconf` utility from your distro's `bind9-utils`/`bind-utils`
> package may be several minor versions older than that and reject directives it doesn't
> recognize yet (this project's own CI hit exactly that with an outdated apt package - see
> [CHANGELOG.md](CHANGELOG.md)). Always check generated configuration with the same
> `named-checkconf` version as your production server, if possible.

## Quick start

Example of a minimal configuration:

```python
from bindantic import (
    ARecord,
    NamedConfig,
    NSRecord,
    OptionsBlock,
    SOARecord,
    ZoneBlock,
    ZoneTypeEnum,
)

config = NamedConfig(
    options_block=OptionsBlock(
        directory="/etc/bind",
        recursion=True,
        allow_recursion=["localhost", "localnets"],
        listen_on=["any"],
        listen_on_v6=["any"],
    ),
    zone_blocks=[
        ZoneBlock(
            comment="optional comment",
            name="example.com",
            zone_type=ZoneTypeEnum.PRIMARY,
            file="zones/example.com.zone",
            resource_records=[
                SOARecord(
                    mname="ns1.example.com",
                    rname="admin.example.com",
                    serial=2026010101,
                    refresh=10800,
                    retry=3600,
                    expire=604800,
                    minimum=3600,
                    origin="example.com",
                    ttl=3600,
                ),
                NSRecord(nsdname="ns1.example.com", comment="optional comment"),
                ARecord(name="@", address="192.168.1.1"),
            ],
        )
    ],
)
```

<details>
<summary>Output of <code>config.model_bind_syntax()</code></summary>

```txt
options {
    allow-recursion {
        localhost;
        localnets;
    };
    directory "/etc/bind";
    listen-on {
        any;
    };
    listen-on-v6 {
        any;
    };
    recursion yes;
};

# optional comment
zone example.com. {
    type primary;
    file "zones/example.com.zone";
};
```

</details>

<details>
<summary>Output of <code>config.zone_blocks[0].model_bind_syntax_zone_file()</code></summary>

```txt
$TTL 3600
$ORIGIN example.com.
@                                                IN   SOA ns1.example.com. admin.example.com. (
                                                                 2026010101 ; Serial number (YYYYMMDDNN)
                                                                 10800      ; Refresh time
                                                                 3600       ; Retry time
                                                                 604800     ; Expire time
                                                                 3600       ; Minimum TTL
                                                      )
@                                                IN   NS         ns1.example.com. ; optional comment
@                                                IN   A          192.168.1.1
```

</details>

<details>
<summary>Output of <code>config.generate_files()</code></summary>

```txt
[
    GeneratedFile(
        path=PosixPath("/etc/bind/zones/example.com.zone"),
        content="<CONTENT>",
        type="zone",
    ),
    GeneratedFile(
        path=PosixPath("/etc/bind/named.conf"),
        content="<CONTENT>",
        type="config",
    ),
]
```

</details>

<details>
<summary>Output of <code>config.write_files(base_dir="./my_config")</code></summary>

```txt
my_config/
├── named.conf
└── zones/
    └── example.com.zone
```

`named.conf` (`directory` is rewritten to the `base_dir` you actually pass in):

```txt
# Automatically generated by bindantic - please adjust!

options {
    allow-recursion {
        localhost;
        localnets;
    };
    directory "my_config";
    listen-on {
        any;
    };
    listen-on-v6 {
        any;
    };
    recursion yes;
};

# optional comment
zone example.com. {
    type primary;
    file "zones/example.com.zone";
};
```

`zones/example.com.zone` is identical to the `model_bind_syntax_zone_file()` output above.

</details>

## More examples

Focused, runnable scripts for common real-world setups - see also [Examples](https://DVSAWR.github.io/bindantic/examples/)
in the documentation:

- [`examples/multi_view_split_horizon.py`](./examples/multi_view_split_horizon.py) - split-horizon
  DNS: internal clients see private records, everyone else sees public ones.
- [`examples/secondary_zone_tsig.py`](./examples/secondary_zone_tsig.py) - a primary/secondary zone
  pair with TSIG-authenticated zone transfers.
- [`examples/dnssec_signed_zone.py`](./examples/dnssec_signed_zone.py) - a DNSSEC-signed zone, end
  to end: key-store, `dnssec-policy` (KSK+ZSK), and a zone using that policy.
- [`examples/logging_and_statistics.py`](./examples/logging_and_statistics.py) - structured logging
  plus a statistics channel for monitoring tools to scrape.
- [`examples/controls_and_rndc.py`](./examples/controls_and_rndc.py) - an explicit `rndc` control
  channel secured with its own key.
- [`examples/dns_over_tls_forwarding.py`](./examples/dns_over_tls_forwarding.py) - forwarding to
  upstream resolvers over DNS-over-TLS.
- [`examples/remote_servers_and_trust_anchors.py`](./examples/remote_servers_and_trust_anchors.py) -
  a reusable `remote-servers` list plus DNSSEC trust anchors.
- [`examples/response_policy_zone.py`](./examples/response_policy_zone.py) - blocking/redirecting
  domains with a Response Policy Zone, plus a catalog zone.

## Documentation

Full documentation, including an exhaustive per-field API reference generated from the models
themselves, is at **[DVSAWR.github.io/bindantic](https://DVSAWR.github.io/bindantic/)**.

## Contributing

Contributions are welcome - see [CONTRIBUTING.md](CONTRIBUTING.md) for the development setup and
workflow. Please review the [Code of Conduct](CODE_OF_CONDUCT.md) before participating, and see
[SECURITY.md](SECURITY.md) to report a security issue privately instead of opening a public one.

## Versioning

bindantic follows [Semantic Versioning](https://semver.org/).

- **Public API** - everything importable from the top-level `bindantic` package (models, enums,
  field type aliases) is covered by semver guarantees.
- **Internal** - any module prefixed with `_` (e.g. `bindantic._base_model`,
  `bindantic._base_types_validation`) is an implementation detail and may change without notice.
- **Major** - removing/renaming a public model or field, or a change that makes previously valid
  input invalid, or a change to the generated BIND syntax output.
- **Minor** - new models, new optional fields, support for new BIND directives.
- **Patch** - bug fixes that don't change the public API surface.

bindantic targets the latest stable BIND 9.20.x release; tracking a new BIND directive is treated
as a minor bump unless it conflicts with existing behavior.

See [CHANGELOG.md](CHANGELOG.md) for the release history.

## License

MIT - see [LICENSE](LICENSE).
