Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .docker/db/pdns-db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
-- PowerDNS database for the docker compose profile "powerdns".
--
-- Run by the pdns-db-init service on every start:
-- psql -v ON_ERROR_STOP=1 -v pdns_db=... -v pdns_user=... -v pdns_password=...
-- -h db -U $POSTGRES_USER -d postgres -f /pdns-db.sql
-- Everything is idempotent, so it also works on an existing postgres-data
-- volume (Postgres initdb scripts would only run on an empty one).

SELECT format('CREATE ROLE %I LOGIN PASSWORD %L', :'pdns_user', :'pdns_password')
WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = :'pdns_user') \gexec

SELECT format('CREATE DATABASE %I OWNER %I', :'pdns_db', :'pdns_user')
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = :'pdns_db') \gexec

\connect :pdns_db

-- Create the tables as the PowerDNS role so it owns them and needs no grants.
SET ROLE :"pdns_user";

-- Schema of the generic PostgreSQL backend, PowerDNS Authoritative 5.0.x:
-- https://github.com/PowerDNS/pdns/blob/rel/auth-5.0.x/modules/gpgsqlbackend/schema.pgsql.sql
-- Verbatim except for the added IF NOT EXISTS clauses.

CREATE TABLE IF NOT EXISTS domains (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type TEXT NOT NULL,
notified_serial BIGINT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
options TEXT DEFAULT NULL,
catalog TEXT DEFAULT NULL,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE UNIQUE INDEX IF NOT EXISTS name_index ON domains(name);
CREATE INDEX IF NOT EXISTS catalog_idx ON domains(catalog);


CREATE TABLE IF NOT EXISTS records (
id BIGSERIAL PRIMARY KEY,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(65535) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
disabled BOOL DEFAULT 'f',
ordername VARCHAR(255),
auth BOOL DEFAULT 't',
CONSTRAINT domain_exists
FOREIGN KEY(domain_id) REFERENCES domains(id)
ON DELETE CASCADE,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE INDEX IF NOT EXISTS rec_name_index ON records(name);
CREATE INDEX IF NOT EXISTS nametype_index ON records(name,type);
CREATE INDEX IF NOT EXISTS domain_id ON records(domain_id);
CREATE INDEX IF NOT EXISTS recordorder ON records (domain_id, ordername text_pattern_ops);


CREATE TABLE IF NOT EXISTS supermasters (
ip INET NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY(ip, nameserver)
);


CREATE TABLE IF NOT EXISTS comments (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) DEFAULT NULL,
comment VARCHAR(65535) NOT NULL,
CONSTRAINT domain_exists
FOREIGN KEY(domain_id) REFERENCES domains(id)
ON DELETE CASCADE,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE INDEX IF NOT EXISTS comments_domain_id_idx ON comments (domain_id);
CREATE INDEX IF NOT EXISTS comments_name_type_idx ON comments (name, type);
CREATE INDEX IF NOT EXISTS comments_order_idx ON comments (domain_id, modified_at);


CREATE TABLE IF NOT EXISTS domainmetadata (
id SERIAL PRIMARY KEY,
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
kind VARCHAR(32),
content TEXT
);

CREATE INDEX IF NOT EXISTS domainidmetaindex ON domainmetadata(domain_id);


CREATE TABLE IF NOT EXISTS cryptokeys (
id SERIAL PRIMARY KEY,
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
flags INT NOT NULL,
active BOOL,
published BOOL DEFAULT TRUE,
content TEXT
);

CREATE INDEX IF NOT EXISTS domainidindex ON cryptokeys(domain_id);


CREATE TABLE IF NOT EXISTS tsigkeys (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255),
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);

CREATE UNIQUE INDEX IF NOT EXISTS namealgoindex ON tsigkeys(name, algorithm);
11 changes: 11 additions & 0 deletions .docker/pdns-auth/gpgsql.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# gpgsql connection settings for the pdns-auth service.
#
# Rendered at container start by the image entrypoint (TEMPLATE_FILES=gpgsql)
# from the container environment into /etc/powerdns/pdns.d/gpgsql.conf, so
# pdns_server and pdnsutil use the same settings. Values come from .env via
# docker-compose.yml.
gpgsql-host={{ GPGSQL_HOST }}
gpgsql-port={{ GPGSQL_PORT }}
gpgsql-dbname={{ GPGSQL_DBNAME }}
gpgsql-user={{ GPGSQL_USER }}
gpgsql-password={{ GPGSQL_PASSWORD }}
22 changes: 22 additions & 0 deletions .docker/pdns-auth/pdns.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# PowerDNS Authoritative Server for the local docker compose setup
# (profile "powerdns"), see https://doc.powerdns.com/authoritative/settings.html
#
# The gpgsql-* connection settings are rendered from the environment into
# pdns.d/gpgsql.conf at container start (see gpgsql.conf.j2), so pdnsutil
# sees them too.
launch=gpgsql
gpgsql-dnssec=no

local-address=0.0.0.0,::
include-dir=/etc/powerdns/pdns.d

# HTTP API used by serveradmin_powerdns (POWERDNS_API_ENDPOINT / _SECRET_KEY)
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=0.0.0.0/0,::/0
api=yes
api-key=0815passwd

# 7 logs every query, useful when debugging the sync; 6 is the normal level.
loglevel=7
9 changes: 9 additions & 0 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ POSTGRES_PASSWORD=serveradmin
POSTGRES_HOST=db
POSTGRES_PORT=5432

# Optional PowerDNS dev service (docker compose profile "powerdns", see docs/source/extending.rst).
# Enable it with:
# COMPOSE_PROFILES=powerdns
POSTGRES_POWERDNS_DB=powerdns
POSTGRES_POWERDNS_USER=powerdns
POSTGRES_POWERDNS_PASSWORD=powerdns
POSTGRES_POWERDNS_HOST=db
POSTGRES_POWERDNS_PORT=5432

# Credentials for default super user (requires Django >= 3.x)
DJANGO_SUPERUSER_USERNAME=serveradmin
DJANGO_SUPERUSER_EMAIL=serveradmin@example.com
Expand Down
67 changes: 67 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ services:
ports:
- "127.0.0.1:5432:5432"
command: "-c config_file=/etc/postgresql/postgres.conf"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 3s
retries: 12

web:
build: .docker/web
Expand All @@ -25,5 +30,67 @@ services:
depends_on:
- db

# ---------------------------------------------------------------------------
# Optional: PowerDNS Authoritative Server for developing DNS integrations
# (e.g. the serveradmin_powerdns app). Not started by default; enable with
# COMPOSE_PROFILES=powerdns in .env or `docker compose --profile powerdns up`.
# See docs/source/extending.rst.

# Creates the separate PowerDNS database, role and gpgsql schema inside the
# db service. Idempotent, runs on every start, works with existing volumes.
pdns-db-init:
image: "postgres:17"
profiles: ["powerdns"]
environment:
- PGPASSWORD=${POSTGRES_PASSWORD}
volumes:
- ".docker/db/pdns-db.sql:/pdns-db.sql:ro"
command:
- psql
- -v
- ON_ERROR_STOP=1
- -v
- pdns_db=${POSTGRES_POWERDNS_DB:-powerdns}
- -v
- pdns_user=${POSTGRES_POWERDNS_USER:-powerdns}
- -v
- pdns_password=${POSTGRES_POWERDNS_PASSWORD:-powerdns}
- -h
- ${POSTGRES_HOST:-db}
- -U
- ${POSTGRES_USER}
- -d
- postgres
- -f
- /pdns-db.sql
depends_on:
db:
condition: service_healthy

pdns-auth:
image: "powerdns/pdns-auth-50:latest"
profiles: ["powerdns"]
volumes:
- ".docker/pdns-auth/pdns.conf:/etc/powerdns/pdns.conf:ro"
- ".docker/pdns-auth/gpgsql.conf.j2:/etc/powerdns/templates.d/gpgsql.j2:ro"
# The image entrypoint renders templates.d/gpgsql.j2 with these variables
# into pdns.d/gpgsql.conf (read by pdns_server and pdnsutil alike).
environment:
- TEMPLATE_FILES=gpgsql
- GPGSQL_HOST=${POSTGRES_POWERDNS_HOST:-db}
- GPGSQL_PORT=${POSTGRES_POWERDNS_PORT:-5432}
- GPGSQL_DBNAME=${POSTGRES_POWERDNS_DB:-powerdns}
- GPGSQL_USER=${POSTGRES_POWERDNS_USER:-powerdns}
- GPGSQL_PASSWORD=${POSTGRES_POWERDNS_PASSWORD:-powerdns}
ports:
- "127.0.0.1:1053:53"
- "127.0.0.1:1053:53/udp"
- "127.0.0.1:8081:8081" # HTTP API, key in .docker/pdns-auth/pdns.conf
depends_on:
db:
condition: service_healthy
pdns-db-init:
condition: service_completed_successfully

volumes:
postgres-data:
33 changes: 33 additions & 0 deletions docs/source/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ host machines and run uv sync to have all modules available for your
IDEs auto completion etc.


Optional: PowerDNS
^^^^^^^^^^^^^^^^^^

For developing DNS integrations (for example the ``serveradmin_powerdns``
app) the compose file contains a PowerDNS Authoritative Server with a
PostgreSQL backend under the compose profile ``powerdns``. It is not started
by default. Enable it permanently in your ``.env``::

COMPOSE_PROFILES=powerdns

or for a single run::

docker compose --profile powerdns up

The profile adds two services:

* ``pdns-db-init`` creates the database, role and schema for PowerDNS inside
the ``db`` service and exits. It is idempotent and also works on an
existing ``postgres-data`` volume.
* ``pdns-auth`` runs ``powerdns/pdns-auth-50`` with the gpgsql backend. The
HTTP API listens on http://127.0.0.1:8081 (API key ``0815passwd``, see
``.docker/pdns-auth/pdns.conf``), DNS on ``127.0.0.1:1053``. Inside the
compose network the API is reachable as ``http://pdns-auth:8081``.

The database connection is configured by the ``POSTGRES_POWERDNS_*`` variables
in ``.env`` (see ``.env.dist``). Useful commands::

alias pdns='docker compose exec pdns-auth pdnsutil'
pdns list-all-zones
curl -H 'X-API-Key: 0815passwd' http://127.0.0.1:8081/api/v1/servers/localhost/zones
dig @127.0.0.1 -p 1053 example.com SOA


Database Dump
-------------

Expand Down
Loading