Files
rb/od-backup-verify.md
T
igodwin f2a979f047 Add MIT LICENSE and a per-file as-is notice
The repo is public and files are fetched by raw URL, so a reader who lands
on one runbook never sees the README -- the repo's context does not travel
with the file. Each .md now carries two lines under the title, each .ps1 the
equivalent at the end of its .NOTES block.

Deliberately two lines, not a paragraph. These files are read through `| more`
on a client console mid-incident, and the top of the file is where the
procedure-specific warnings live -- never a live chart, stop the service
before copying, confirm authorization before acting. A legal preamble above
those competes with them and trains people to skip past.

Wording aims at a stranger who found the repo, not at the quality of the
procedure: these double as documented-procedure evidence for E&O, and
language implying the content is unreliable works against that.

MIT rather than no license: the warranty and liability disclaimer is the part
that does the work, and leaving it unlicensed makes reuse ambiguous rather
than disclaimed.

Also fixes 5 stale ops/rb URLs in scripts/*.ps1 that the previous commit
missed -- it only swept the .md files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HwcG1jLs1T425QRMxtjxP7
2026-09-02 23:06:44 -07:00

120 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Runbook: Open Dental — Verify a backup by test-restoring into an isolated Hyper-V VM
> For qualified IT professionals, on systems they are authorized to administer.
> Provided as-is, without warranty — verify it fits your environment. See LICENSE.
**Applies to:** Any Open Dental cold backup produced by [`od-db-backup.md`](od-db-backup.md) (an `od-backup-<timestamp>` folder holding `data\`, `OpenDentImages\`, `FreeDentalConfig.xml`, `MANIFEST.txt`).
**Goal:** Prove a backup is actually restorable and complete by restoring it into a **throwaway, network-isolated Hyper-V virtual machine** and running a short health checklist. A backup you have never restored is a guess.
> [!IMPORTANT]
> **Never restore over a live production database** — Open Dental warns this can cause irreversible data loss. The whole point of this runbook is to restore somewhere that *cannot* touch production: an isolated VM, off the office network. ([Open Dental — Backups](https://opendental.com/manual/backups.html))
**Cadence:** Test-restore **at least monthly**, and any time you change backup jobs, versions, or hardware. Open Dental recommends restoring to a machine **not connected to the office network** to verify quality. ([Open Dental — Manual Backups](https://www.opendental.com/manual/backupsmanual.html))
**Why Hyper-V:** it gives you that isolated machine on demand, with a **checkpoint** you revert after every test — so each verification starts from a known-clean baseline and the PHI-laden copy is destroyed when you're done. Any spare offline PC or a doctor's disconnected laptop works too; the steps are identical inside.
**Placeholders:**
| Placeholder | Meaning |
|---|---|
| `<BACKUP_FOLDER>` | The `od-backup-<timestamp>` folder being verified |
| `<OD_VERSION>` | Open Dental version from `MANIFEST.txt` — must match on the VM |
| `<DB_VERSION>` | MySQL/MariaDB version from `MANIFEST.txt` — must match on the VM |
| `<DB_SERVICE>` | The MySQL/MariaDB service name **inside the VM** |
| `<VM_DATA_DIR>` | The VM's MySQL data directory (e.g. `C:\mysql\data`) |
| `<VM_IMAGES_DIR>` | The VM's A-to-Z images folder (e.g. `C:\OpenDentImages`) |
---
## Phase 0 — Read the manifest first
Open `<BACKUP_FOLDER>\MANIFEST.txt`. Note **`Open Dental ver`** (`<OD_VERSION>`) and **`DB engine ver`** (`<DB_VERSION>`). You must install **matching** versions in the VM — a version mismatch either refuses to open or silently converts the database. ([Open Dental — Manual Backups](https://www.opendental.com/manual/backupsmanual.html))
## Phase 1 — Build the isolated verification VM (once, then reuse)
Do this one time; you'll revert to a checkpoint for every future test.
1. **Enable Hyper-V** on a Windows Pro/Enterprise/Server host (elevated PowerShell; reboots):
```
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
```
2. **Create an isolated network** so the VM can never reach the office network or the live database. In Hyper-V Manager → *Virtual Switch Manager*, create a **Private** (or **Internal**) switch — **not** External. Or leave the VM with **no network adapter** at all.
> This isolation is the safety guarantee. An "External" switch would put the test VM on the practice LAN, where a mistake could reach production shares or the live DB. Don't.
3. **Create the VM** (Gen 2, enough RAM/disk for the database + images), install a supported **Windows** guest.
4. Inside the VM, install **MySQL/MariaDB `<DB_VERSION>`** and **Open Dental `<OD_VERSION>`** (matching the manifest). Do a normal Open Dental install so `<DB_SERVICE>`, `<VM_DATA_DIR>`, and a local `opendental` database exist.
5. Shut the VM down cleanly and take a **checkpoint** named `clean-baseline`. This is your reset point.
## Phase 2 — Get the backup into the VM (keep it offline)
Copy `<BACKUP_FOLDER>` into the VM **without** using the office network — e.g. Hyper-V Enhanced Session copy/paste, or attach the backup as a VHD/passthrough disk, or a scratch VHDX. The VM stays isolated the entire time.
## Phase 3 — Restore into the VM
Run these **inside the VM**, elevated. This mirrors Open Dental's documented restore, adapted for our **whole-datadir** cold copy.
1. **Stop the database:**
```
net stop <DB_SERVICE>
Get-Service <DB_SERVICE> # confirm Status = Stopped before touching files
```
2. **Move the VM's existing data aside** (don't delete — lets you retry):
```
Rename-Item "<VM_DATA_DIR>" "<VM_DATA_DIR>-old"
New-Item -ItemType Directory "<VM_DATA_DIR>" | Out-Null
```
3. **Restore the whole data directory** from the backup — copy the **entire contents of the backup's `data\` folder**, not just `data\opendental\`:
```
robocopy "<BACKUP_FOLDER>\data" "<VM_DATA_DIR>" /E /COPY:DAT /R:2 /W:5
```
> For **InnoDB** you must restore the shared tablespace and logs (`ibdata1`, `ib_logfile*`) that live at the datadir root alongside the `opendental` folder — that's why we restore the whole `data\`, matching how [`od-db-backup.md`](od-db-backup.md) captured it. Restoring only the `opendental` subfolder yields a database that won't start.
4. **Restore the images:**
```
robocopy "<BACKUP_FOLDER>\OpenDentImages" "<VM_IMAGES_DIR>" /E /COPY:DAT /R:2 /W:5
```
5. **Start the database:**
```
net start <DB_SERVICE>
Get-Service <DB_SERVICE> # confirm Running
```
6. **Launch Open Dental in the VM**, connecting to the **local** `opendental` database (localhost). Configure a fresh local connection — do **not** reuse the production `FreeDentalConfig.xml` connection string (it points at the real server). The captured `FreeDentalConfig.xml` is only a **reference** of production settings (e.g. the A-to-Z path).
7. If Open Dental reports it **can't find the images**, point the image path at `<VM_IMAGES_DIR>`: *Setup → Imaging / Image path* (the A-to-Z path stored in the database is production's; update it to the VM's local folder so restored images resolve).
## Phase 4 — Health checklist: is this backup good?
Work top to bottom. **All must pass** for the backup to count as verified.
- [ ] **Connects & opens** — Open Dental launches against the restored DB with **no connection or startup errors**.
- [ ] **Version matches** — *Help → About* shows `<OD_VERSION>` (no unexpected DB-conversion prompt).
- [ ] **Database Maintenance is clean** — *Main Menu → Tools → Database Maintenance*, run **Check** (not Fix). It "checks the database for improper settings, inconsistencies, or corruption." Review the results for **unexpected** corruption/integrity errors. ([Open Dental — Database Maintenance](https://www.opendental.com/manual/databasemaintenance.html))
- [ ] **Recent data is present and current** — open the **Appointments** module on a recent production date; open 2–3 known patients; check a recent **payment/ledger** entry. Confirm the newest data lines up with production **as of the backup time** (proves the backup is current, not stale).
- [ ] **Images actually open** — pick a patient known to have scanned docs/X-rays in the Imaging module and **open the files**. This proves the A-to-Z folder restored *and* the image path resolves — a DB-only restore silently loses every document.
- [ ] **No missing modules/tables** — click through Chart, Family, Account, Imaging; watch for "table doesn't exist" / read-only errors.
- [ ] *(Optional, deeper)* **Engine-level integrity** — from the VM's MySQL shell, `mysqlcheck -u root -p --check opendental` (or `CHECK TABLE`) to confirm no table-level corruption.
## Phase 5 — Record the result (keep as evidence)
Log, outside the VM: **date verified**, `<BACKUP_FOLDER>` timestamp, `<OD_VERSION>`/`<DB_VERSION>`, each checklist item **pass/fail**, who ran it, and any Database Maintenance findings. This is your DR evidence for E&O / cyber insurance and HIPAA contingency-plan testing.
> A **fail** on any item means the backup is not trustworthy — fix the backup job (not just this restore) and re-verify. Common causes: images folder never included; only `data\opendental\` copied (InnoDB won't start); backup taken hot without stopping the service; version drift.
## Phase 6 — Tear down / reset
1. In Open Dental, close out; **stop the MySQL service** in the VM before shutting down.
> Open Dental warns: if a machine used to verify backups **sleeps while MySQL is running, it can corrupt the database.** Stop the service first. ([Open Dental — Manual Backups](https://www.opendental.com/manual/backupsmanual.html))
2. **Revert the VM to the `clean-baseline` checkpoint.** This resets to a known-clean state for next time **and destroys the restored PHI copy**.
3. Never reconnect this VM to the office network with a restored database attached.
---
## Security note
The moment you restore, the VM's virtual disk holds the **entire practice's PHI** — treat it exactly like production. Keep the VM **isolated** (Private/Internal switch or no NIC), keep the host and VHDX **encrypted** (BitLocker / encrypted VHDX), and **revert/destroy** the restored state when done so PHI doesn't linger on a test box. HIPAA applies to the copy just as much as to production. See the security note in [`od-db-backup.md`](od-db-backup.md) — on direct-connection setups the captured `FreeDentalConfig.xml` also holds the (reversibly obfuscated) DB password.
## References
- Open Dental manual — Backups (overview; test-restore off-network): <https://opendental.com/manual/backups.html>
- Open Dental manual — Manual Backups (restore steps; verify on disconnected machine; sleep/corruption warning): <https://www.opendental.com/manual/backupsmanual.html>
- Open Dental manual — Database Maintenance (Check/Fix health tool): <https://www.opendental.com/manual/databasemaintenance.html>
- Open Dental manual — Backup Tool: <https://www.opendental.com/manual/backuptool.html>
- Companion runbook — producing the backup: [`od-db-backup.md`](od-db-backup.md)