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
11 KiB
Runbook: Open Dental — Rock-solid database + images backup (Windows)
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: The Windows machine hosting the Open Dental MySQL/MariaDB database (the "server"). Goal: A fully consistent, restorable backup of both halves of an Open Dental practice — the database and the A-to-Z images folder — following Open Dental and MySQL/MariaDB best practice, including cleanly stopping the database during the copy and restarting it after.
What a complete Open Dental backup is (and isn't)
Per Open Dental's own docs, a full backup is two things — the database and the A-to-Z images folder — and both must be captured together. A database backup without the images folder (or vice-versa) is not a usable restore.
| Component | Default location | Back up? | Holds |
|---|---|---|---|
| MySQL/MariaDB data directory | C:\mysql\data\ (contains the opendental DB) |
Yes — required | All clinical/financial data |
| A-to-Z / OpenDentImages folder | C:\OpenDentImages\ |
Yes — required | Scanned docs, images, attachments |
FreeDentalConfig.xml |
Open Dental install dir | Nice-to-have | Connection / Choose-Database / AtoZ path config |
| Open Dental program files | …\Program Files\Open Dental\ |
No — reinstall | The application itself |
| Other office documents | (varies) | Per your policy | Non-Open-Dental files |
There is no separate "application backup." Open Dental does not back up the program itself — on restore you reinstall the matching Open Dental version (and matching MySQL/MariaDB) and point it at the restored data. So the two "recovery aids" worth grabbing are just: a copy of FreeDentalConfig.xml and a record of the exact versions to reinstall. The script captures both automatically into the backup folder (FreeDentalConfig.xml + MANIFEST.txt) — nothing extra to run.
Placeholders:
| Placeholder | Meaning |
|---|---|
<DB_SERVICE> |
Name of the MySQL/MariaDB Windows service (e.g. MySQL, MySQL57, MariaDB) |
<DATA_DIR> |
MySQL data directory — the folder containing the opendental subfolder (commonly C:\mysql\data) |
<IMAGES_DIR> |
A-to-Z images folder (commonly C:\OpenDentImages) |
<DEST> |
Backup destination — separate physical disk or UNC path, ideally replicated off-site |
<DB_USER> / <DB_PASSWORD> |
A MySQL account for mysqldump (supplemental method) — from the password manager, never committed |
Why "cold copy" is the rock-solid method
Open Dental databases run on MyISAM or InnoDB. For a file-level backup to be consistent:
- A hot copy (copying the data directory while the service runs) can capture InnoDB mid-write → corrupt, unrestorable backup. Open Dental's built-in Backup tool and most "online" file backups cannot even restore InnoDB.
- A cold copy — stop the service so it flushes and closes cleanly, copy, restart — is consistent for both engines. This is the gold-standard local backup.
Two details that make or break a cold copy:
- Copy the entire data directory, not just
data\opendental\. InnoDB's shared tablespace and redo logs (ibdata1,ib_logfile*) live at the root of the data directory. Copy only theopendentalsubfolder and an InnoDB restore will fail. - Verify the service actually stopped before copying. If it won't stop, do not copy — you'd capture a live datadir.
The script below does both, and always restarts the service (even if the copy fails), so the practice is never left down.
1. Pre-flight
- Run on the database server, in an elevated PowerShell, off-hours — the copy causes downtime; Open Dental is unavailable on every workstation while the service is stopped.
- Make sure no one is in Open Dental (fully closed on all workstations).
- Have a
<DEST>on a different physical disk (or UNC share) with enough free space for the data directory + images.
2. Run the cold backup (primary method)
From an elevated PowerShell on the server:
irm rb.godwinsystems.com/scripts/od-db-backup.ps1 | iex
It will:
- Auto-detect the
<DB_SERVICE>,<DATA_DIR>, and<IMAGES_DIR>(prompting to confirm/override). - Report sizes and destination free space, then confirm before doing anything.
- Stop
<DB_SERVICE>and verify it reached Stopped — aborting the copy if it doesn't. robocopythe whole data directory to<DEST>\od-backup-<timestamp>\data, then the images to…\OpenDentImages.- Restart
<DB_SERVICE>in afinallyblock — this runs even if the copy fails or is interrupted. - Capture recovery aids (best-effort, outside the downtime window): a copy of
FreeDentalConfig.xmland the exact Open Dental + MySQL/MariaDB versions. - Write
MANIFEST.txtandbackup.loginto the backup folder.
Manual equivalent (offline / if you can't fetch the script)
Elevated PowerShell, no one in Open Dental:
net stop <DB_SERVICE>
Get-Service <DB_SERVICE> # confirm Status = Stopped BEFORE copying
robocopy "<DATA_DIR>" "<DEST>\od-backup\data" /E /COPY:DAT /R:2 /W:5
robocopy "<IMAGES_DIR>" "<DEST>\od-backup\OpenDentImages" /E /COPY:DAT /R:2 /W:5
net start <DB_SERVICE>
Get-Service <DB_SERVICE> # confirm Status = Running
Copy
<DATA_DIR>itself (the parent ofopendental), not<DATA_DIR>\opendental— you needibdata1/ib_logfile*at the datadir root for InnoDB. Ifnet startfails, start it immediately: the practice cannot work until the DB is back up.
3. Supplemental logical backup (mysqldump)
A cold copy is engine-perfect but physical — a portable logical dump is a valuable second line (survives binary corruption, restores to any server/version). It runs while the service is up, so schedule it separately (e.g. mid-day incremental in addition to the nightly cold copy). Slight slowness while it runs.
mysqldump -u <DB_USER> -p --single-transaction --quick --max-allowed-packet=1024M --default-character-set=utf8 --routines --events opendental > "<DEST>\opendental-<date>.sql"
--single-transactiongives a consistent snapshot of InnoDB without locking the practice out. (For MyISAM, that flag does not guarantee consistency — use the cold copy as the source of truth.)- Compress the
.sqlafterward; it shrinks dramatically. - The dump does not include the images folder — always pair it with an
<IMAGES_DIR>copy.
4. Schedule it (daily minimum)
Open Dental's floor is at least one backup per day; combine an automated nightly job with an off-site copy.
Register the cold backup as a nightly Task Scheduler job (runs off-hours, as SYSTEM/admin). Because irm | iex prompts interactively, schedule a local copy of the script with the paths baked in (keep that filled-in copy in the private tier, not here), e.g.:
schtasks /Create /TN "OD Nightly Cold Backup" /TR "powershell -NoProfile -ExecutionPolicy Bypass -File C:\ops\od-db-backup-local.ps1" /SC DAILY /ST 23:30 /RU SYSTEM /RL HIGHEST
Full scheduling and monitoring (staggering the off-site upload, a daily health check, and a dead-man's-switch heartbeat so a silent failure gets caught): od-backup-schedule.md.
5. Verify — a backup you haven't restored is a guess
- Test-restore to an isolated machine periodically (matching Open Dental + MySQL/MariaDB versions):
net stop, rename the existingopendentalfolder aside, drop in the backup'sdata\contents,net start, launch Open Dental, spot-check patients/images. Full step-by-step with a health checklist:od-backup-verify.md(isolated Hyper-V test-restore). - Never restore over a live production database — data loss is irreversible.
- Confirm the nightly job is actually producing dated folders and that they leave the building (off-site / immutable copy) — ransomware that reaches the server will reach on-line backups too.
6. Retention & off-site (3-2-1)
- 3 copies, 2 media, 1 off-site. The
<DEST>disk alone is not a backup strategy. - Keep several daily generations plus weekly/monthly rollups; prune old
od-backup-<timestamp>folders on a schedule. - These files contain PHI — encrypt at rest and in transit; restrict access. HIPAA applies.
Point off-site tools at <DEST>, not at the live database
Any off-site/cloud replication (Duplicati → Backblaze B2, Veeam, rclone, Wasabi, etc.) must use <DEST> as its source — it backs up this backup. It must not re-run the stop/copy against the live <DATA_DIR>.
- The
od-backup-<timestamp>folders under<DEST>are already a consistent, cold copy produced with the service cleanly stopped. Copying them off-site is a safe file copy — no service stop, no downtime, no consistency risk. - Never let a naive file-sync tool crawl the live
C:\mysql\datadirectly. A hot copy of a running InnoDB datadir is corrupt and unrestorable — the exact failure this runbook exists to avoid. Off-site tools have no idea they need to stop the service first; that's this script's job, done once, up front. - Sequence: this cold backup runs first (nightly, off-hours) → the off-site job runs after it completes, sourcing
<DEST>. Stagger the schedules (e.g. cold backup 23:30, off-site upload 01:00) so the upload never reads a half-writtenod-backup-<timestamp>folder. If your tool supports it, exclude any in-progress/partial folder or upload only completed timestamps. - Let the off-site tool own its own encryption + retention on top — B2/Wasabi object lock or Duplicati's immutability/versioning gives you the ransomware-resistant, off-site copy of the 3-2-1 rule.
Security note
Backup media and dumps hold the entire practice's PHI. Treat them as the crown jewels: encrypt the destination, lock down share permissions, and keep at least one copy off-line/immutable so ransomware can't encrypt your backups along with production. Don't store <DB_PASSWORD> in the scheduled command line — use a MySQL option file / limited account.
Note that the captured FreeDentalConfig.xml stores the MySQL password obfuscated but reversible on direct-connection setups. The backup already contains all PHI so this doesn't change the requirement — but it's one more reason the destination must be encrypted and access-controlled. (Middle Tier setups don't put DB credentials in that file — see od-cfg-persist.md.)
References
- Open Dental manual — Backups (overview): https://opendental.com/manual/backups.html
- Open Dental manual — Manual Backups: https://www.opendental.com/manual/backupsmanual.html
- Open Dental manual — Backup Tool: https://www.opendental.com/manual/backuptool.html
- Open Dental manual — FreeDentalConfig.xml: https://www.opendental.com/manual/freedentalconfig.html
- Open Dental — InnoDB (backup implications): https://www.opendental.com/site/mysqlinnodb.html
- Open Dental manual — MySQL Data Directory Management: https://opendental.com/manual/mysqlmanage.html
- MySQL —
mysqldump,--single-transaction: https://dev.mysql.com/doc/refman/en/mysqldump.html