Skip to content

kynoci/powerdns-admin

 
 

Repository files navigation

PowerDNS-Admin

A PowerDNS web interface — fork of PowerDNS-Admin/PowerDNS-Admin, re-targeted at bare-metal Debian 13 with a tighter source tree, a strict-typed library layer, and a faster local-dev loop.

PowerDNS-Admin dashboard (v0.5.3)

Originally PowerDNS-Admin by the upstream maintainers. All the hard work on the zone editor, the Knockout zone-record table, the OAuth/SAML/LDAP backends, and the JSON API came from there. This fork is downstream cleanup, URL polish, and operability changes; the underlying product is still PowerDNS-Admin and full credit goes to that project.

Project page: https://github.com/kynoci/powerdns-admin

Compatibility

Tested and developed on the latest Debian — Debian 13 (trixie) with Python 3.13 (the trixie default). Upstream PowerDNS-Admin predates the trixie release and several of its assumptions (distutils, older requests, older Flask) needed updates to run cleanly on the new stack. This fork tracks trixie as its primary target. Newer PowerDNS Authoritative Server releases (4.x and 5.x) are both supported via the configurable pdns_version setting.

Older OS releases — Debian 12, Ubuntu 22.04 etc. — should still work but aren't part of CI; if something is broken on them the fix is welcome but the master branch won't hold for them.


What this fork changes

Operability

  • Three-command local dev flowcd /opt/powerdns-admin && source .venv/bin/activate && ./run.py. run.py auto-applies Alembic migrations on boot, and create_app auto-loads <repo_root>/config.py if present, so no FLASK_APP / FLASK_CONF plumbing is needed for ordinary use.
  • Headless install / provisioning CLIs:
    • flask init-secrets — generates per-install random SECRET_KEY / SALT to <instance>/secrets.py.
    • flask create-admin --username … --email … --password … — create or promote a local Administrator (also resets the password if the user already exists).
    • flask set-server-setting --api-url … --api-key … --pdns-ver … — seed the PowerDNS API connection settings without a human logging in.
  • First-run security gate — the app refuses to serve traffic if SECRET_KEY / SALT / SQLA_DB_PASSWORD still match the source-tree defaults. Bypass for local dev with PDA_ALLOW_INSECURE_DEFAULTS=1 or ALLOW_INSECURE_DEFAULTS = True in config.py (logged loudly when active).
  • Scripted bare-metal installdocs/install-001-powerdns-admin.sh does PDA itself (system deps, venv, frontend assets, db upgrade, admin user, PowerDNS API settings, systemd unit). Pair it with one of the PowerDNS-auth scripts (install-002-pdns-auth-sqlite.sh or install-004-pdns-auth-mysql.sh); add install-003-pdns-recursor.sh if you want a local recursor. All idempotent.

URL refresh

The visible URL surface was reorganised so paths read like the UI labels. Old paths return 404; bookmarks need updating.

Before After
/dashboard /zone/dashboard
/domain/add /zone/create
/domain/remove /zone/remove
/admin/global-search /admin/search
/admin/history /admin/activity
/admin/server/statistics /admin/statistics
/admin/server/configuration /admin/configuration
/admin/manage-account /admin/account
/admin/manage-user /admin/user
/admin/manage-keys /admin/keys
/admin/setting/basic /setting/config
/admin/setting/dns-records /setting/record
/admin/setting/pdns /setting/server
/admin/setting/authentication /setting/auth

UI

  • Sidebar redone as a Bootstrap-4 accordion: the three sections (Zone Management / Administration / Server Settings) auto-collapse each other so only one is open at a time. Section headers are bold; children are visually indented (drops to icon-only mode under the pushmenu toggle).
  • Auto-derived breadcrumbs — every page renders its breadcrumb from the first two URL segments (Section ▸ Page). No more hand-written <ol class="breadcrumb"> per template.
  • HTMX shipped in the asset bundle; the zone-editor changelog navigation already uses hx-boost. Wider HTMX adoption is staged.

Backend

  • Python package renamed powerdnsadminpdnsadmin. Every import and configuration reference moved; the product name ("PowerDNS-Admin") is preserved everywhere it was a label rather than an identifier.
  • Splits: routes/api.py (1270 LOC) split into routes/api/{__init__,zones,apikeys,users,accounts,dnssec,proxy}.py. Settings panes moved off admin_bp into a dedicated setting_bp. Zone CRUD entry points moved off admin/domain into a zone_bp.
  • OAuth/SAML providers stored under current_app.extensions, not module-level globals.
  • Schema layer migrated from lima to marshmallow; the generated OpenAPI spec at /api/v1/openapi.{json,yaml} now publishes typed schemas via apispec.
  • Strict typing on lib/mypy --strict runs in CI on the pure-helper layer (errors, record diff, record builder, the external-provisioning helper, to_idna); the rest of lib/ is in a relaxed list pending follow-up.
  • Setting model: explicit ENV_PINNED_KEYS registry. Keys that belong in app.config only (Flask-Mail, Flask-Session, bootstrap secrets, SAML transport, etc.) skip the DB lookup; the admin UI refuses to write them.
  • Test harness: Makefile + scripts/setup-pdns-test.sh brings up pdns-auth (sqlite backend) for the integration suite. Tests partition cleanly into pytest -m unit (152 pure-function tests, ~0.3 s) and pytest -m integration (56 tests; needs live pdns). Wired into .github/workflows/test.yml.

A more detailed change log is in docs/ROADMAP.md (each commit prefixed T1.X / T2.X / T3.X maps to a tier in that plan).


Quickstart

Local dev (Debian 13)

Bring up PowerDNS authoritative (sqlite backend) and PDA:

sudo ./docs/install-002-pdns-auth-sqlite.sh   # or install-004-pdns-auth-mysql.sh
sudo ./docs/install-001-powerdns-admin.sh

The first script installs PowerDNS auth + the chosen DB backend on port 5300 with the API on 8081. The second installs system deps, creates the venv, builds the frontend, applies migrations, creates a local admin user, and writes the PDNS API connection settings.

Then:

# 1. Stop the running dev server
kill $(pgrep -f 'run.py')

# 2. Wipe the relocated venv
cd /opt/powerdns-admin/
rm -rf .venv

# 3. Recreate venv + install deps (uses the Makefile)
make install

# 4. Activate it
source .venv/bin/activate

# 5. Sanity check — shebang should now point at /opt/powerdns-admin
head -1 .venv/bin/flask

# 6. Create the admin
export FLASK_APP=pdnsadmin
flask create-admin --username admin --email admin@powerdns-admin.com --password adminpass

# 7. Start the dev server
./run.py

Open http://127.0.0.1:9191/login and sign in with the credentials the installer printed (admin / adminpass by default — change them in docs/install-001-powerdns-admin.sh before re-running, or via the admin UI).

Production

docs/install-001-powerdns-admin.sh also installs a systemd unit (pdnsadmin.service) that runs PDA under the venv on port 9191. For a public deployment, terminate TLS at nginx and proxy to that port — see docs/wiki/web-server/Running-PowerDNS-Admin-with-Systemd-Gunicorn-and-Nginx.md for a worked example (the gunicorn launch command should target pdnsadmin:create_app() instead of upstream's powerdnsadmin).


Configuration

config.py at the repo root is your local config (gitignored). It auto-loads after the in-tree defaults and before any FLASK_CONF override. A minimal one for local dev:

import os
basedir = os.path.abspath(os.path.dirname(__file__))

SECRET_KEY = '<run flask init-secrets and paste from instance/secrets.py>'
SALT = '<same>'
BIND_ADDRESS = '0.0.0.0'
PORT = 9191
SESSION_TYPE = 'sqlalchemy'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db')

Or for a one-off local test:

ALLOW_INSECURE_DEFAULTS = True

(Don't ship that to production.)

flask init-secrets writes random SECRET_KEY / SALT to instance/secrets.py, which create_app loads automatically.


Development

make install          # one-shot venv + deps + dev tools
make test             # unit + integration
make test-unit        # pure-function tests (no Flask, no DB, no pdns)
make test-integration # needs live pdns-auth (run `make setup-pdns` once)
make lint             # ruff
make lint-fix         # ruff --fix
make typecheck        # mypy --strict on pdnsadmin/lib/

CI runs ruff + mypy + pytest -m unit on every PR; the integration job spins up pdns-auth via a service container and runs pytest -m integration.


Credits

License

MIT — same as upstream. See LICENSE.

About

A PowerDNS web interface with advanced features

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 52.7%
  • HTML 41.9%
  • JavaScript 3.2%
  • CSS 1.8%
  • Shell 0.2%
  • Makefile 0.2%