Operations & migration
How to know a SuperDRM install is healthy, what to back up, how to prove it still works, and how to move it to another host without downtime.
#Health
GET /healthon the api:200when the vault and database answer,503otherwise. The body names the vault provider, the database, the FairPlay KSM state, the Widevine environment and — on-prem — the install licence.GET /healthzon docs and live.- The live console streams every licence event and shows the whole system as a node canvas.
curl -s https://api.<domain>/health | jq '{ok, vault, db, fairplay_ksm, widevine, install}'#Monitoring
/healthevery minute from each region you serve.- A canary licence every 5 minutes: issue a key, request a Clear Key licence for it, and (for Widevine) a service-certificate request. Alert on any non-200.
- Licence failures by
errorcode: a spike inhw_requiredortoken_invalidis a client regression; a spike inupstreamis Google or the KSM. - KEK health, and expiry of partner certificates (the FairPlay application certificate is annual).
#Reading the meter
- Healthy:
widevine kind=license ok=true security=L3|L1,fairplay … security=hw-main. - Files packaged before a vendor switch:
ok=false error=no_keyin a couple of milliseconds, then your previous vendor serves the licence. These rows are the fallback working, not an outage; they fade as old files are re-packaged or expire. hw_required: a client policy regression (software CDM where hardware was required).upstream: Google or the KSM; check/healthand the provider's status page.
superdrm licences --limit 20
superdrm usage --tenant acme --from 2026-09-01
superdrm audit --limit 50#Keys and backups
- The KEK is the one thing that cannot be recreated. Under
localit is a file (secrets/kek.hexin Compose); back it up offline now — losing it loses every sealed key. Underpkcs11oraws-kmsback it up with the token's own mechanism (Hardware key vault). - The database holds the sealed rows:
pg_dumpnightly, kept separately from the KEK. - Restore drill: new stack →
superdrm import(orpsql < backup) →superdrm verify-kek.
#Upgrades
Images are tagged by git SHA and by version. Pull the new tag, restart api first, then console, docs and live. Migrations are additive and idempotent and apply at api boot (or via the Helm hook). Roll back by pointing the tag back; schema changes never drop columns a previous version still reads.
#Re-runnable proofs
| Proof | Command | Expect |
|---|---|---|
| Unit + API | npm test | all pass |
| Live Widevine key registration | SUPERDRM_TEST_LIVE_WIDEVINE=1 npm test | the LIVE: test prints a UAT key and PSSH |
| A browser plays SuperDRM-keyed content with a SuperDRM licence | node scripts/prove-widevine.mjs | PROOF OK, a widevine … ok=true row |
| Bring-your-own-key licence with our own PSSH | npx tsx scripts/prove-byok.mts | BYOK PROOF OK |
| PKCS#11 vault against a real token | npm run test:pkcs11 | PASS: pkcs11 vault proven against SoftHSM2 |
| End to end from your packager | Package a file, view it, then superdrm licences --limit 5 | an ok=true row for the new content id |
#Server migration
SuperDRM keeps two things you must carry: the database and the KEK. Every content key, tenant token secret and partner credential in the database is sealed under the KEK; without it the export is ciphertext. Everything else (images, env) is reproducible.
#The tools
superdrm export superdrm-2026-09-11.json.gz # from the env the CLI points at (DATABASE_URL or PGlite dir)
superdrm import superdrm-2026-09-11.json.gz # into DATABASE_URL (runs migrations, upserts, idempotent)
superdrm verify-kek [content id] # opens one sealed key with the configured vault
scripts/migrate-server.sh export|import|verify … # wraps the three with the KEK warning and optional --reseal-toThe bundle is gzipped JSON: every table (tenants, api_keys, tenant_drm, partner_creds, content_keys, licences, usage_daily, audit_log, plans, onprem_*, superdrm_migrations) with bytea as base64, plus the source KEK id. Import is an upsert on each table's primary key, so re-running it is safe, and identity sequences are advanced past the imported ids.
#Downtime-free cutover
- Stand up the target (Compose or Helm) with the same KEK: copy
kek.hex, or point at the same HSM/KMS key. Start it on the target's private address; do not expose it yet. - Freeze packaging on the source tenants (pause the packager worker). Licences keep flowing from the source.
- Export from the source and import into the target. Run
verifyon the target: row counts match and one sealed key opens. - Switch the edge: point the tunnel route, DNS or load balancer for
api.<domain>at the target. Players' next licence request lands on the target; nothing in the client changes. - Unfreeze packaging with the packager's
SUPERDRM_URLpointing at the target. - Catch up: export/import once more to carry the licence rows issued on the source between steps 3 and 4 (upserts, so it is safe).
- Retire the source after a day of clean metering on the target.
If the target uses a different vault (say, from a software KEK to AWS KMS): import with the source KEK available as SUPERDRM_OLD_* and run --reseal-to aws-kms; the re-seal opens each blob with the old vault and seals it with the new one, in batches, verifying every row and updating kek_id.
#What a rehearsal looks like
A migration from a single-node PGlite install into a Compose stack on Postgres 16, performed on real data: a 6.6 KB bundle (plans, tenants, API keys, content keys, licences, daily usage, audit rows, migrations) imported with kek_match: true and identical counts, idempotent on re-run; verify-kek opened an existing content key on the target; the target then served a Clear Key licence for an existing content id and issued a new Widevine key against Google's UAT service; api, console, docs and live all healthy. Re-run it any time on a scratch host.
Updated September 2026