Docker upgrades keep their licence activation
Fixes
- Recreating the Docker container no longer deactivates the licence — the image has no machine ID of its own, so the server's fingerprint came from the container's hostname, which Docker changes every time a container is recreated. After a pull and run, the server came back as an unactivated machine. It now keeps its fingerprint in the data volume, so every later upgrade keeps it. The upgrade into 1.8.1 is itself a recreate: keep the container's hostname for it (
--hostname, or hostname: in Compose), or re-register the new fingerprint under Admin → License afterwards. The .deb, .rpm, macOS and Windows installers were never affected
Drift monitoring
New
- Drift monitoring (Pro) — ZoneRails now spots DNS changes made outside it as they happen, not only when someone next edits the record. After every cache refresh it compares each monitored zone with a stored baseline and opens a finding for every record set that changed, appeared or disappeared. It reads what the refresh already fetched, so it makes no extra provider calls
- Opt-in per zone, with scope rules — nothing is monitored until a zone is turned on, and its first scan adopts the current state without raising anything. Include and exclude rules by subdomain pattern and record type keep automation such as ACME challenges or preview environments out of it. Protected records are always monitored, and their drift is high severity
- ZoneRails's own changes are never drift — every change made through ZoneRails updates the baseline, and a ZoneRails fix of a drifted record closes its finding. A zone whose records could not be fetched is flagged, never read as "everything was deleted"
- The 05 // DRIFT tab — findings filtered by status and severity, each with its baseline next to the live records and the differences highlighted. Accept a change as the new baseline with a written reason. The Monitoring scope view lists every zone, edits its rules with one-click presets, and previews what the rules catch before you save
- Licence changes reach a running server — a feature added to your licence now arrives with the next heartbeat (every 30 minutes) instead of at the next restart
Fixes
- Protecting and unprotecting a record is in the Activity Log — lifting protection around an edit left no trace. The Activity Log's filter gains Protected and Protection removed
- Active Directory DNS: deleting an MX or SRV record sent without its priority or weight removed every record at that name with that target. It now removes only that record
- SSO on MySQL: several people signing in for the first time at once could fail with a database deadlock. Sign-in now retries it
Deployment approvals, ticket numbers, and a Cloudflare clean-up
New
- Deployment approvals — an Admin setting under
Deployment Policy, off by default. When on, a deployment is submitted for review, an Approver validates it against live DNS and approves or rejects it, and only an approved deployment can run or be scheduled. Every step is in the Activity Log and on the deployment
- Required ticket numbers — optionally make every deployment carry a change ticket before it is submitted, run or scheduled, with an optional format such as
CHG-\d+
- DNSSEC placement in pre-flight — a DNSKEY away from the zone apex, or a DS with no NS record at its name, is flagged before the provider refuses it
Fixes
- CAA records can be created and deployed; they were always refused. NAPTR records with an empty regex are accepted
- Account-owned Cloudflare API tokens are recognised. Their load balancing and tunnel permissions are detected, a DNS-only token no longer hits a false load balancer block, and token inspection shows them as active
- Protected records stay protected when two connections reach the same Cloudflare zone; editing through the second connection went around the protection
- Connectors uses the right token when an account has more than one connection. It could pick a DNS-only token and show "No tunnels found"
- Tunnels: a deleted tunnel's DNS records are reported as orphans; tunnel changes are recorded in the Activity Log; orphan cleanup only removes tunnel records; checks that could not look at tunnels say so instead of reporting all clear
- Large deployments: right after a deployment of 100 or more records, the record list, the cache and the guardrails could miss everything past the first page
- Load balancer pools can be edited or deleted after their monitor starts health-checking; they were blocked as changed
- Deleting a user who has history answers with a clear reason (disable the account instead) rather than a server error
- Pre-flight no longer blocks DNS-only tokens on load balancer checks, reports one finding per cause, and can be run by reviewers. Planners and Approvers can add deployment items
Active Directory DNS fixes
- Deleting CNAME, MX, TXT, NS and SRV records works — the delete reported success and left the record on the DNS server
- Editing a record changes it — editing an MX, TXT, NS or SRV record added the new value and kept the old one. Records are now changed in place, and one that has changed or gone since you loaded it is refused instead of duplicated
- Errors from the DNS server are shown — when Windows refused a change (no permission, a duplicate record, a zone it doesn't host), ZoneRails reported success and wrote it to the Activity Log. You now see the server's own message, and nothing is recorded
- TXT values arrive exactly as typed — a value with a space, such as an SPF record, was split into pieces, and characters like
& and | could be read as commands. Commands are now sent to the DNS server encoded, so values are always plain text
- Typographic quotes can't end a value — PowerShell also reads ‘ ’ ‚ ‛ as a closing quote, so a TXT value or a directory search containing one could run commands on the domain controller. Every quote character is now escaped
- TXT values containing a backslash are refused — Windows reads a backslash as an escape and stores something other than what was typed, and such records can't be reliably removed afterwards. Every delete and update now also reads the server back to confirm it took effect
- Deleting an MX or SRV record removes only that record — two MX records pointing at the same server shared an ID, so deleting one could remove the other
- Re-adding an existing record is refused — it silently changed the existing record's TTL
- Zone delegations use the parameters Windows expects; they could not be created or deleted before. The
_msdcs delegation Active Directory depends on can no longer be deleted from ZoneRails
- Large zones load completely and non-English text is kept intact
- A TTL of a day or more is set correctly
- Editing an AD DNS connection keeps its settings — saving the form replaced the server, port and password with the username, breaking the connection. The form now shows the current settings, and a blank password keeps the current one. Only users who can manage connections see a connection's server and account name
- An unreachable domain controller is reported as such instead of as "database unavailable", and a refused sign-in says so instead of "could not reach the server"
- Verify after deploy confirms AD changes when the domain controller is known only through the hosts file, and rolling back an AD update from the Activity Log works
Changes
- Cloudflare refusals are told apart — when Cloudflare refuses the connection's API token, the server answers
502 with code CLOUDFLARE_REFUSED, not a 403 that looked like a missing role
- Verify after deploy shows a record that has not propagated yet as pending, and only an unexpected answer as a warning
- The status bar counts each zone and record once, even when two connections reach the same zone
Intel Macs, and a new home for the Docker image
Fixes
- ZoneRails Server runs on Intel Macs — the 1.6.0 Intel installer completed, but the server crashed as soon as it started, so nothing answered on port 3000. If you installed 1.6.0 on an Intel Mac, install 1.6.1 over it; your data is kept
Changes
- The Docker image moves to
cr.getzonerails.com/zonerails-server — ghcr.io/aholdingscompany/zonerails-server keeps getting every release as a mirror, so existing docker pull commands and compose files go on working
- Every installer is tested as an upgrade — before a release ships, each installer and the Docker image are installed over the previous release, and must keep its users, its settings and its sign-in keys
A calendar for everything deployed and everything scheduled
New
- Deployment calendar — a new tab,
04 // CALENDAR, showing what ran and what is scheduled to run. The agenda lists the weeks around a month day by day, with the Now divider between past and upcoming; the month view is a grid. Opening a deployment shows its zones, connections, results and every change it makes. A Pro feature, needing deploy:read
- Rescheduling — a scheduled deployment can be moved from its row, where you pick the new date and time, or dragged to another day in the month view, where it keeps its time of day. Either way the move is written to the activity log with the time it used to hold. Rescheduling makes you its scheduler, so it runs with your permissions: you need Execute deployments as well, or it is refused when it comes due
- Every name and time on it is read from the database — who created a deployment, who scheduled it, when it ran, what it changed. A deployment still running is placed at the last time it was written to
- The app lays out at the device's width on phones — it was rendering as a desktop page and being scaled down to fit
Fixes
- One deleted deployment no longer holds up the rest — a scheduled deployment deleted just as the scheduler reached it ended that minute's whole run, leaving every other deployment due at the same time to wait for the next one
- A date that does not exist is refused — scheduling for 30 February silently became 2 March, and ran then. It is now rejected when you ask for it
- A Pro tab loads when its licence gate lifts — a tab that was locked when the page opened now loads in place once the licence carries it, instead of staying empty until a reload
- Load Balancing's "Return to deployment" — it opened before the DNS list had finished saving, so it landed on nothing, or behind the list
- Scheduled times on SQL Server — times are stored and read back as the instant they name, without the workarounds the driver used to need
Deleting a deployment, and the keyboard on the sign-in screen
Fixes
- Deleting a deployment works — the delete button always failed with "Deployment not found". The row vanished from the list, the error appeared, and the deployment came back on the next refresh
- The sign-in screen takes the keyboard — focus lands on the sign-in card when the page opens, and Tab moves through its controls; signing in locally, that is username to password to Sign In. The first-run agreement dialog was opening behind the card, invisible, and holding both the focus and the Tab key
Installers for every platform, a setup wizard, HTTPS, and PostgreSQL, MySQL and SQL Server
Installers
- Install from a package — DEB and RPM for Linux (x86_64 and arm64), an installer for Windows (x64), a pkg for macOS (Apple silicon and Intel), and a Docker image. Each carries its own Node.js runtime and keeps
config.json, the database and the logs in a data directory apart from the program, so an upgrade leaves your data alone
- Signed installers — the Windows installer and its uninstaller are signed by A Holdings Company LLC through Microsoft's Artifact Signing, so Windows names the publisher. The macOS pkg is signed with a Developer ID, notarized by Apple and stapled, so it opens with a double-click, even offline
- Runs as a service — a systemd unit on Linux, the ZoneRails Server service running as LocalService on Windows, and a launchd daemon on macOS
- Checksums — every release carries a
SHA256SUMS file covering all of its packages
Setup wizard
- First-run setup wizard — the first sign-in walks the administrator from a bare install to a working one: replace the installed password, then the licence, the sign-in method (local, Active Directory or OpenID Connect), the first DNS connection, and the port, bind address and HTTPS. Everything after the password and the agreements can be skipped and finished later
- Change your own password — any user can now change their own password
HTTPS
- The server can terminate TLS itself — paste the certificate and key into the wizard and the server checks them, then writes them into its data directory with
0600 on the key. A key that does not match the certificate, an encrypted key, or a chain in the wrong order is refused with the reason; an expired or self-signed certificate is accepted with a warning
- Settable from the environment —
ZR_HTTPS_ENABLED, ZR_HTTPS_CERT and ZR_HTTPS_KEY, for containers
- Checked at startup — material that cannot serve TLS stops the server with exit code 78 and a message naming both paths, and
/api/health reports the certificate being served, daysRemaining included
- Port 443 on Linux without root — the systemd unit grants
CAP_NET_BIND_SERVICE
Secrets at rest
- Rotate the keys —
node server.js --rekey re-encrypts every stored credential under a new encryption key, rotates the JWT secret and signs everyone out, in one transaction. For when config.json or a backup of it may have been read
- The server refuses to start on an encryption key that cannot read what is stored
- The Windows data directory is locked down — only SYSTEM, Administrators and the service account can read
%ProgramData%\ZoneRails. The uninstaller asks whether to delete it (No by default), and a kept directory is locked to SYSTEM and Administrators
- The LDAP bind password is no longer destroyed when its stored copy will not decrypt
Activity Log
- Load balancing changes roll back from the Activity Log — undoing a create deletes the monitor, pool or load balancer, undoing an update restores its previous settings on the same id, and undoing a delete recreates it from its snapshot. The reversal clears the same guardrails as a direct change, and resources still pointing at a recreated pool or monitor are repointed
Database engines
- Run ZoneRails Server on your own database — PostgreSQL 13+, MySQL 8 (and MariaDB 10.6+) and SQL Server 2019+ join the bundled SQLite. Set
database.client and the host fields, or a single database.url connection string
- Every database field is settable from the environment —
ZR_DB_CLIENT, ZR_DB_URL, ZR_DB_HOST, ZR_DB_PORT, ZR_DB_NAME, ZR_DB_USER, ZR_DB_PASSWORD, ZR_DB_SSL and the existing ZR_DB_PATH, so a container needs no config file to reach a managed engine
- Move an existing installation with one command —
node server.js --copy-database --from ./data/zonerails.db copies every table into the configured engine inside a single transaction, with the SQLite file opened read-only. A failed copy leaves the target exactly as it found it, so a retry is always possible; --replace re-runs it deliberately
- The copy refuses rather than half-succeeds — a target that already holds rows, an encryption key that cannot read the source's stored credentials, or a value too long for the target's columns each stop the run with a named reason and its own exit code, before anything is written
- The server refuses to start on a database block it cannot act on — an unknown engine, or a network engine with neither host nor connection string, exits with
DB_CONFIG_INVALID and names the missing field. Passwords never appear in a log line
database.type is deprecated — it is still read as an alias for database.client and warns once at startup
Operations
/api/health now measures the database — it reports database.client, connected and latencyMs from a real round trip, and answers 503 while the database is unreachable, instead of always claiming to be connected
- A database outage is a 503, not a 401 — sign-in, token refresh and the SSO exchange answer
DB_UNAVAILABLE during an outage rather than reporting bad credentials, and no SQL ever reaches the response body
- An unreachable directory is a 503, not a 401 — a refused LDAP connection, an expired certificate, a timeout, or a service-account password that has been rotated now answers
AUTH_BACKEND_UNAVAILABLE (503) instead of telling users their password is wrong. Only the caller's own bind being refused is still a 401. The directory's address, port and result codes stay in the server log
- Sign-in no longer says whether an account exists — an Active Directory account the directory does not hold and a password it refused answer the same fixed
Invalid credentials, so the login form cannot be used to enumerate accounts. Which of the two it was is written to the server log
- Schema upgrades can be run ahead of a restart —
node server.js --migrate-only migrates, seeds and exits without opening a port. --migrate migrates only, and seeds nothing unless --seed is given as well
Single sign-on with Microsoft Entra ID and OpenID Connect
Single sign-on (Pro)
- Microsoft Entra ID and generic OpenID Connect — sign in with an identity provider instead of, or alongside, a local password
- New admin Authentication section — configure local sign-in policy and SSO from one place, with a "Test Discovery" check against the provider before saving
- Group → role mappings — map identity provider groups to ZoneRails roles; a user in several mapped groups gets the union of every matched role, re-derived on every sign-in
- Local sign-in restriction — limit local username/password sign-in to administrators, with the seeded administrator always able to sign in locally as a break-glass path
- Sign out control — a visible sign-out button next to the active connection, ending only the ZoneRails session
Web console
- The login screen now offers whichever sign-in methods the server has configured — local, Active Directory, and single sign-on — instead of assuming a username/password form
Load balancing through the deployment pipeline
Load Balancing (Pro)
- New 03 // LOAD BALANCING tab — manage Cloudflare monitors, pools, and load balancers directly or as deployment items. Hidden for AD DNS connections
- Same guardrails as DNS — name collisions in both directions, protected-name shadowing, dangling and referenced-by checks, drift, and plan-time warnings for irreversible deletes, all overridable with a written reason
- Dependency-ordered execution — items may reference resources the same deployment creates; the real id is substituted at execute time and rollback walks the order backwards
- Honest rollback — deleted pools and monitors come back with a new id, so referencing load balancers are repointed and audited one by one, and anything left dangling is reported as partial
- Permissions — new
lb:read/create/update/delete group; system roles updated, custom roles need lb:read granted
- Plan quotas — a pool or load balancer refused by your Cloudflare plan is reported as a quota problem, not a token problem
Fixes
- The deployment duplicate guard now fires for account-scoped items (it compared a NULL zone with
=)
- The Activity Log resource-type filter works; the column it read was never created by a migration
- An unreachable server is reported as such instead of the browser's bare "Failed to fetch"
Web console
- Every control is reachable from the keyboard: result rows open on Enter, dialogs trap and return focus, tablists use arrow keys, recent searches are a combobox
- Measured contrast on every badge and surface, one focus ring, one hide idiom, no information left only in tooltips
- Admin password reset uses an in-app dialog instead of the browser prompt
AD DNS delegation from search, per-zone proxy/TTL
AD DNS Improvements
- Create delegation from search — when searching for a record on an AD DNS zone, a
DELEGATE row appears alongside NEW. Double-click to open the delegation modal pre-filled with the child name
- "Create Delegation Instead" button — visible in the create form footer when the zone is AD DNS
- Per-zone proxy toggle — proxy status hidden for AD DNS zones even in multi-provider server mode (previously only checked global connection type)
- AD DNS default TTL — new records default to 300 seconds (5 minutes) instead of Auto
- Delegation child name read-only when opened from search or create form (editable when opened manually from the Delegations button)
Deployment validation, rollback, real-time progress, and UX improvements
Deployment Validation (Dry Run)
- "Validate" button — runs 8 pre-flight checks per item without making changes: connection, zone, record existence, drift, content, CNAME conflicts, duplicates, snapshot completeness
- Results modal with per-item pass/warning/error badges and expandable check details including drift current vs expected values
- Works in both server-connected and standalone modes
Deployment Rollback
- "Rollback Deployment" button on deployed/partial deployments — reverses all successful changes in reverse order
- Impact analysis modal — shows per-item risk (Safe, Modified, downstream deps, No snapshot) with clickable expandable details showing before/current/deployed state
- Downstream detection — warns if later deployments modified the same records
- ROLLED BACK / PARTIAL ROLLBACK status badges in deployment list and results view
- Atomic status guard prevents double-rollback race conditions
- Record IDs backfilled during execute so rollback can find created records
Progress & Verification
- Real-time progress bar — 3-phase: 0-50% execution (per-item), 50-60% status review (3s pause), 60-100% verification
- Cloudflare verification simplified to name+type via API (eliminates MX/SRV/CNAME content format mismatches)
- Verification timing: 6 attempts at 10 seconds (60 seconds total)
- Fix: Status cells stuck on "Pending" (DOM ID mismatch with server item IDs)
- Fix: "All records verified" false positive when count was zero
Search & UX
- Recent search history — last 10 terms cached in localStorage, shared between main and deploy search, with dropdown on focus and clear button
- Multi-provider create — when a zone exists on both Cloudflare and AD DNS, search shows one NEW row per provider
- Duplicate prevention — "In Deployment" badge on search results + blocks adding same record twice within a deployment
- Deployment items full-width table layout
- FQDN consistency — record names always stored as FQDN in deployment items
- Fix: deployment delete crashed (
deployEmpty DOM element destroyed by innerHTML)
AD DNS zone delegations — full lifecycle support
New Feature
- Zone delegations for Active Directory DNS — carve a subdomain out of a parent AD DNS zone and delegate it to external nameservers, with optional glue records. Fully supported in both standalone mode (desktop → AD DNS via WinRM) and server-connected mode (desktop → ZoneRails Server → AD DNS)
- Create / list / delete — all three operations exposed via a dedicated "Delegations" button on the DNS tab (visible only when an AD DNS connection is active)
- Create modal — repeating-row editor for nameservers, each with optional glue IP, TTL, and change-note fields; client-side validation for DNS labels, hostnames, and IPv4/IPv6 glue addresses
- List view — shows all delegations for the selected zone with their nameservers, glue IPs, and TTL; one-click delete with confirmation
Backend
- AdDnsProvider extended with
createDelegation(), listDelegations(), deleteDelegation() wrapping Add-DnsServerZoneDelegation, Get-DnsServerZoneDelegation, and Remove-DnsServerZoneDelegation over WinRM
- New server endpoints —
GET/POST/DELETE /api/dns/delegations, reusing existing dns:read/dns:create/dns:delete permissions
- Audit trail — delegation mutations logged to
audit_log with record_type = DELEGATION, preserving full before/after snapshots
- WebSocket broadcast —
delegation:changed event pushed to all connected clients
- No new tables, no migration, no new permissions — the feature slots into existing infrastructure
Full Cloudflare DNS record-type parity — 12 advanced types added
New Record Types
- 12 advanced record types added end-to-end (client + server + deployments + cache):
CERT, DNSKEY, DS, HTTPS, LOC, NAPTR, OPENPGPKEY, SMIMEA, SSHFP, SVCB, TLSA, URI
- Structured-data editor — One input per subfield for each advanced type (e.g. TLSA's
usage / selector / matching_type / certificate), with a collapsible "Edit as JSON" textarea as an escape hatch for power users
- Cloudflare docs deep-link per type beside the editor header
- Two-tier type selector —
Common and Advanced (RFC) optgroups in both the create/edit form and the deployment add-item form
Shared Registry
- Single source of truth — New
shared/record-types.js (mirrored to zonerails-server/lib/) defines every supported type's display metadata, Cloudflare data shape, DoH type number, validator, and form subfield schema
- Consumed by renderer (form + validation), main process (
buildRecordBody), server routes (buildCfBody, dohTypeNum), and deployment execution
- Removes duplication previously scattered across 6 files (HTML option lists, JS type switches, server type-number maps, docs)
Server
- Server-side validation —
POST/PUT /api/dns/records and POST /api/deployments/:id/items now validate record type and payload through the shared registry. Rejects unknown types, missing required subfields, and out-of-range numerics with HTTP 400 + structured error (previously zero validation — third-party callers had no safety net)
- Migration 004 — Adds
data_json column to deployment_items and dns_cache so advanced types roundtrip through draft → scheduled → execute and cache reads return the full Cloudflare data shape
- Deployment verification skips DoH text-match for advanced types (marks verified-by-provider instead, since Cloudflare's response is the source of truth for structured records)
- AD DNS guard — Returns HTTP 400 with a clear message if an advanced type is attempted on an Active Directory DNS connection (at the route, at deployment execution, and in the client)
Bug Fixes
- Fixed latent CAA bug in server mode — Server
buildCfBody previously dropped CAA's data object silently, breaking CAA records in server mode. Now handled through the registry end-to-end
Server-side caching, WebSocket push, deployment overhaul, hybrid auth
Server
- DNS cache service — Server caches all zones and records in SQLite with configurable background refresh (default 10 min). Clients load from cache in a single request instead of hitting Cloudflare API on every startup
- WebSocket real-time push — Server broadcasts DNS changes, deployment status transitions, and cache refreshes to all connected clients via
/ws endpoint with JWT auth
- Deployment duplicate prevention — Server rejects adding a record to a deployment if the same record is already in another active (draft/scheduled) deployment. Returns 409 with conflict details
- Locked records endpoint —
GET /api/deployments/locked-records returns all records in active deployments so clients can show scheduled/draft badges
- Hybrid authentication — Local accounts always work, AD login used as fallback when configured. Local admin can still log in even with
auth.type: "active-directory"
- User CRUD endpoints — Create local users, change passwords, delete users via
POST/PUT/DELETE /api/admin/users
- Connected client tracking —
ws_clients table tracks all WebSocket connections with user, IP, and heartbeat. Viewable via GET /api/admin/ws-clients
- DNS verification via Cloudflare DoH — Proxied records verified via Cloudflare API (since DNS resolves to proxy IPs). Non-proxied records verified via DNS-over-HTTPS at
cloudflare-dns.com
- AD DNS cache filtering — System zones (
_msdcs, TrustAnchors, RootDNSServers) excluded from cache
- Server web UI — User management (create, delete, reset password, enable/disable), AD group-to-role mapping, all in the admin dashboard
Desktop Client
- Server-mode caching —
load-data uses single GET /api/dns/cached/all request instead of multi-connection API loop. Local cache fallback when server unreachable
- WebSocket event handling — Auto-reloads data on
cache:refreshed, record:changed, and deployment:status events
- Provider column + filter — Search results show CF/AD badge per record. Provider dropdown filter (hidden when single provider)
- Scheduled/draft lock badges — Records in active deployments show "Scheduled" or "In Draft" badge in search results
- Deploy search parity — Deploy tab search now has provider filter, pagination, tunnel badges, and lock badges matching Tab 1
- Deployment status filter — Dropdown to filter deployment list by status (draft, scheduled, deployed, failed, etc.)
- Deployment results view — Click a deployed/failed deployment to see per-item results and verification status
- Auto-verify after deployment — DNS propagation verification runs automatically (5 attempts over ~45 seconds) after execution
- Deployment note field — Optional note on the deployment itself (alongside name and ticket number)
- CAA record support in deploy form — Flags, tag, and CA domain fields for CAA records in the deployment add-item form
- Record validation in deploy form —
validateRecord() checks content format per record type before adding to deployment
- Refresh button on deploy tab — Reloads all zones and records from the deploy list view
- Cart-to-deployment rename — All internal references renamed from "cart" to "deployment" across client codebase
- Admin tab removed from client — User management moved to server web UI
Bug Fixes
- Fixed cart ID overwrite in
openDeploymentDetail causing "Add Edit/Delete to Deployment" buttons to do nothing
- Fixed
beforeSnapshot double-serialization (JSON.stringify in main.js + server route)
- Fixed foreign key constraint on
connection_id when adding deployment items in server mode
- Fixed deployment name not updating when clicking "Back to Deployments"
- Fixed deployment items table floating in middle of page (
justify-content: center on column flex)
- Fixed
Object.assign(dep, result.deployment) overwriting client-side deployment ID, breaking verify button
- Fixed foreign key constraint on deployment delete (audit_log references)
- Fixed verify endpoint using system DNS resolver for Cloudflare records (now uses DoH/API)
Connectors tab, in-app Pro modal, UI improvements
- Connectors tab — Full Cloudflare Tunnel management: create/delete tunnels, view connectors, manage ingress rules, routes, and virtual networks
- In-app Pro upgrade modal — Feature-gated UI with upgrade prompts for Pro-only features
- License activation flow — In-app license key entry, activation, and deactivation
- UI polish and layout improvements across all tabs
ZoneRails Server, multi-user support, Active Directory DNS
ZoneRails Server
- Multi-user DNS management backend — Node.js + Express + SQLite server with JWT authentication
- Role-based access control — Admin, Operator, Deployer, Viewer roles with granular permissions
- Active Directory integration — LDAP authentication with automatic group-to-role mapping
- Deployment scheduling — Server-side cron executes deployments at scheduled times
- Admin web dashboard — Web UI for user management, roles, connections, and license management
- Server licensing — Server-level license with client seat limits and activation workflow
- Installation packages — Linux (systemd), Docker (compose), and Windows (service) deployment options
Desktop Client
- Server mode — Connect desktop client to ZoneRails Server for shared DNS management
- Active Directory DNS provider — Manage Windows DNS servers via WinRM alongside Cloudflare
- Deploy tab — Batch DNS change planning, execution, and verification
- Auto-reconnect — Saved server credentials encrypted and restored on app restart
Cloudflare DNS management with ZoneOps guardrails
- DNS record management — Search, create, edit, delete across all Cloudflare zones
- 9 record types — A, AAAA, CNAME, MX, TXT, NS, SRV, CAA, PTR
- Multi-token support — Store and switch between multiple Cloudflare API tokens
- ZoneOps guardrails — Record protection, conflict detection, drift detection, propagation verification
- Tunnel guardrails — Orphaned DNS record detection, tunnel health mapping, ingress conflict prevention
- Activity log — Full audit trail with before/after snapshots and one-click rollback
- Audit auto-archive — Oldest entries archived to JSON when log exceeds limit
- Token inspection — View token permissions, expiration, and account details
- Cross-platform — macOS (Intel + Apple Silicon) and Windows (x64)
- Secure storage — API tokens encrypted via OS keychain (macOS Keychain / Windows Credential Manager)