Database and Keys
Which database for what
Section titled “Which database for what”| Storage | Configuration | Suitable for |
|---|---|---|
| SQLite | DNSE_DB=./dnseditor.db (default) | Single instance, desktop, testing |
| PostgreSQL | DNSE_DB_DSN=postgres://… | Production use, backups via pg_dump |
| YugabyteDB | DNSE_DB_DSN=… and DNSE_DB_YB=on | Multiple nodes, distributed operation |
YugabyteDB speaks the PostgreSQL protocol but needs its own driver. That is
why the DSN alone is not enough — without DNSE_DB_YB=on the connection is
treated as ordinary PostgreSQL.
The schema is migrated automatically at startup. When multiple nodes run, the first one to start performs the migration; the others wait until the schema matches.
The encryption key
Section titled “The encryption key”DNSE_ENCRYPTION_KEY protects everything that must not be stored in
plaintext: provider credentials, private certificate keys, the SMTP
credentials (username and password), 2FA secrets, the password hashes
of the user accounts — and the personal data of the accounts
themselves: username, e-mail address, display name and the ACME account
addresses. The database contains only encrypted forms of these; API,
agent and DynDNS tokens are additionally stored only as SHA-256 hashes,
never in plaintext.
To keep logins working despite the encryption, the DNS-Editor maintains
a blind index for username and e-mail: a deterministic HMAC over the
normalized value (lower-cased, trimmed) whose key is derived from
DNSE_ENCRYPTION_KEY. The database therefore sees neither names nor
addresses — yet equality lookups and account uniqueness work unchanged,
and logins remain case-insensitive as before.
Three rules follow:
- The key does not belong in the same backup as the database. Whoever captures both together has everything.
- A backup without the key is worthless. Back it up before the first production start and document where it is stored.
- All nodes need the same key. Otherwise node 2 cannot decrypt what node 1 has written.
Backup
Section titled “Backup”For PostgreSQL, the usual approach is enough:
pg_dump -Fc dnseditor > dnseditor-$(date +%F).dumpFor SQLite, back up the file while the service is stopped, or via
sqlite3 dnseditor.db ".backup dnseditor-backup.db".
For restore drills, the same applies as everywhere: a backup that has never been restored is not a backup.
The organisation backup
Section titled “The organisation backup”With DNSE_BACKUP_DIR set, the service also writes a nightly logical export
of the organisation (Administration → Backup). That export decrypts the
database fields so it can be restored — and is therefore sealed again as it is
written: AES-256-GCM with the same DNSE_ENCRYPTION_KEY. New points are named
…jsonl.enc, older ones …jsonl; both stay restorable, and a download
delivers cleartext.
So the same rule applies to these files as to the database: without the key they cannot be restored. Back the key up separately, and actually back it up.
Older backups still lying in the clear are sealed by a one-off run — no database, no running service, repeatable:
DNSE_BACKUP_DIR=… DNSE_ENCRYPTION_KEY=… dnseditor backup-sealWhether the backups are actually readable is answered by:
DNSE_BACKUP_DIR=… DNSE_ENCRYPTION_KEY=… dnseditor backup-verifyThe run opens every point and reads it in full — a damaged frame or a truncated file surfaces there and not first during a restore.