Skip to main content
Version: 0.9.17-patch2

Config versioning & backups

Path: /admin-settingsBackups tab, and the per-row History action on each config table.

Once a SuperAdmin saves a change to a config row, the previous state is normally gone. Config versioning adds three recovery capabilities on top of the live editors, all scoped to the platform-wide config domains:

  1. Per-row version history — snapshot any single config row on demand, list prior versions, and restore one as the live state.
  2. Whole-platform backups — one-click snapshot of every config domain into a timestamped row, restorable per-domain or all-at-once.
  3. JSON export / import — bundle the current config into a single JSON document to hand to an OpCo or attach to a ticket, and re-apply it (optionally a subset of domains).

Covered domains

DomainPer-row historyIn backupNotes
SIP trunksHistory action on the table; Save snapshot in the editor.
Egress trunksIncludes base-number join state.
Base numbersIncludes team / header-manipulation / egress-trunk join state.
Cron schedulesHistory action on the table.
Platform settings✅ (per key)All nine setting keys are captured — unset keys resolve to their schema default, not omitted.
Platform secretsRegistry only (see below).
note

A platform backup and an export produce the same JSON shape. A backup persists it as a ConfigBackup row; an export hands the same document straight to the browser as a download.

Per-row version history

Each of the four config tables — SIP trunks, egress trunks, base numbers, and cron schedules — has a History action on its rows that opens a slide-over sidebar. Platform settings expose a per-key Save snapshot button instead.

ActionDescription
Save snapshotCaptures the current live row and increments its version number. Optional note (≤ 500 chars) labels the checkpoint.
RestoreRe-applies a chosen snapshot to the live row through the same write path the editor uses. The pre-restore state is auto-captured as a new version, so the rollback itself is auditable.
Diff with currentCompares a snapshot against the current live row (or another version) and lists added / removed / changed fields.

Versions are listed newest-first with the version number, optional note, the capturing user, and a relative timestamp.

warning

Restore overwrites the live row. The previous live state is preserved as a new version entry — recover it with another restore if needed.

Whole-platform backups

The Backups tab lists timestamped ConfigBackup rows. Each row bundles all six domains into a single JSONB payload.

ActionDescription
Create backupSnapshots every config domain into a new ConfigBackup row. Optional note.
DownloadExports the backup payload as a JSON file.
Restore allRe-applies every domain from the backup to the live platform. Each modified row produces a new version entry (except platform secrets, which have no per-row history).
Restore domainRe-applies a single chosen category from the backup.

Atomicity and concurrency

Restore all runs inside a single serializable transaction that takes ACCESS EXCLUSIVE locks on all six config tables in a fixed order before any per-row work. This means:

  • A concurrent backup or a parallel restore queues behind the lock — it sees either the full pre-restore or the full post-restore state, never a mix.
  • An editor save running while a restore is in flight blocks briefly (typically 1–3 s for tens of rows) until the restore commits, then proceeds.
  • The lock order is deterministic, so two concurrent restores cannot deadlock.

Rows that fail during a full restore are not re-thrown: each is reported as failed in the result (with the error message) and skipped, while the remaining rows still commit together inside the one transaction.

Platform secrets in backups

The Platform secrets registry is included in backups and full restores for completeness, but individual secret slots have no per-row history (no History action, no per-row version entries).

  • Only registry metadata is captured — key, purpose, description, timestamps.
  • Secret values live in AWS Secrets Manager and are intentionally never part of the bundle.
  • During a full restore, the secrets registry is restored before SIP and egress trunks, because trunks reference secret slots by id (tlsCaCertMaterialId, tlsClientCertMaterialId, tlsClientKeyMaterialId). Recreated slots keep their original ids so those foreign keys stay valid.

JSON export / import

ActionDescription
ExportDownloads the current platform config as a schema-versioned JSON document — the same shape a backup stores, but without writing a ConfigBackup row.
ImportTwo-step: Preview validates the payload and returns a per-domain diff summary (live count, bundle count, only-in-live, only-in-bundle); Apply re-applies a chosen subset of categories.

Import applies only the categories you select; unselected domains are left untouched. Each modified row produces a new version entry, except platform secrets.

note

deleteMissing is wired through the import input but is a no-op today — rows present in live config but absent from the bundle are not deleted. This is deliberate: import is additive/restorative, not destructive.

Audit trail

Every versioning and restore operation writes to the Access logs:

OperationAudit categoryActionNotable details fields
Save snapshotCONFIG_VERSIONUPDATEcaptured: true, version, configCategory
Restore a versionthe row's domainUPDATErestoredFrom, restoredFromVersion, old, new
Restore all / domainthe row's domainCREATE / UPDATErestoredFromBackup, recreated (when the live row was missing), old, new
Row failed during restoreCONFIG_RESTORE_FAILUREUPDATEerror, configCategory, backupId

A row whose live counterpart was deleted before a restore is recreated from the snapshot (audit action CREATE, recreated: true) rather than skipped — "restore from backup" brings back what was lost.

See also