Google Workspace
Cloud-to-cloud backup and restore of Google Workspace accounts. Pure Rust — one FD plugin (gworkspace-fd.so) plus one backend binary, no JVM, no Python, no agents inside Google.
Capabilities
Section titled “Capabilities”| Workload | Backup | Incremental | Restore |
|---|---|---|---|
| Gmail | .eml full-fidelity (RFC822, labels preserved) | history API | re-insert with labels recreated |
| Google Drive | binaries native + Docs/Sheets/Slides exported (docx/xlsx/pptx, configurable) | changes API | upload under NGBackup Restore <date>/ |
| Google Calendar | JSON per event | full walk | events.import |
| Google Contacts | JSON per contact | full walk | people:createContact |
All workloads support restore to origin and cross-user restore (restore_user=). Filesystem download of virtual files (where=/path) is currently limited (see Restore below). Shared Drives, Gmail settings and deletion tombstones are on the roadmap.
- Service account + Domain-Wide Delegation (OAuth 2.0, RS256 JWT) — one key protects the whole domain; per-user impersonation, no user passwords.
- New users included automatically: domain jobs enumerate active users via the Admin SDK Directory API on every run.
- Takeout-compatible Gmail format: each message is a standalone
.emlwith anX-Gmail-Labels:header — the self-contained format opens in any mail client after a restore-to-origin export. - Quota-aware: truncated exponential backoff on 429 / 403 rate-limit / 5xx honoring
Retry-After(Gmail quota model: 6,000 units/min/user). - Secrets hygiene: inline keys are rejected at parse time; the service-account key lives in a 0600 file (
sa_key_file=).
Requirements
Section titled “Requirements”- Google Cloud Console: create a project (or reuse the org’s backup project), enable the Gmail API, Google Drive API, Google Calendar API, People API and Admin SDK API, create a service account and download its JSON key.
- Copy the service account’s numeric OAuth2 Client ID (on the SA details page).
- Workspace Admin console (admin.google.com): Security → API Controls → Domain-wide Delegation → add the numeric client ID with exactly these scopes (propagation can take up to 24 h, usually minutes):
https://www.googleapis.com/auth/gmail.readonlyhttps://www.googleapis.com/auth/drive.readonlyhttps://www.googleapis.com/auth/calendar.readonlyhttps://www.googleapis.com/auth/contacts.readonlyhttps://www.googleapis.com/auth/admin.directory.user.readonlyhttps://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/drivehttps://www.googleapis.com/auth/calendar.eventshttps://www.googleapis.com/auth/contactsInstallation
Section titled “Installation”Install the ngbackup-gworkspace-plugin package (deb/rpm) on the client host that will run the jobs, then place the key and configuration:
install -o bacula -g bacula -m 0600 sa-key.json /opt/ngbackup/etc/gworkspace/sa-key.jsoncp gworkspace.conf.example /opt/ngbackup/etc/gworkspace/gworkspace.conf # edit domain/admin_userPre-flight check (exit code 0 = all green):
sudo -u bacula gworkspace-backend mode=verify \ config_file=/opt/ngbackup/etc/gworkspace/gworkspace.conf user=someone@yourdomainConfiguration
Section titled “Configuration”gworkspace.conf
Section titled “gworkspace.conf”key = value format; keys on the Plugin = line override the file.
| Key | Meaning |
|---|---|
domain | Workspace primary domain (virtual paths and Directory API queries) |
sa_key_file | Service-account JSON key with DWD enabled — mode 0600 |
admin_user | Admin impersonated for Directory API user enumeration (domain-wide jobs and list pluginobjects); not needed when jobs pass explicit user= |
working_dir | Checkpoint directory (Gmail historyId / Drive pageToken), default /opt/ngbackup/working |
log_level | Optional; debug for verbose runs |
page_size | Optional; API page size (e.g. 500) |
export_docs | Docs export format: docx | odt | pdf | txt | html | md |
export_sheets | Sheets export format: xlsx | ods | pdf | csv | tsv |
export_slides | Slides export format: pptx | odp | pdf | txt |
max_file_mb | Per-file Drive cap in MiB, 0 = unlimited |
include_trash | Back up trashed items (no by default) |
Plugin= line
Section titled “Plugin= line”Prefix gworkspace: routes to the plugin; everything after it is space-separated key=value pairs. mode= values: account (all four services) | gmail | drive | calendar | contacts | verify. Domain jobs (mode=account without user=) re-enumerate active users on every run — new employees are protected automatically.
Enumerate domain users from the console with ready-to-paste Plugin = lines:
list pluginobjects client=<fd> plugin="gworkspace: config_file=/opt/ngbackup/etc/gworkspace/gworkspace.conf"FileSet, Schedule and Job examples
Section titled “FileSet, Schedule and Job examples”# Whole-domain, all services (Gmail + Drive + Calendar + Contacts).FileSet { Name = "FS-GWorkspace-Domain" Include { Options { Signature = MD5; Compression = GZIP } Plugin = "gworkspace: mode=account \ config_file=/opt/ngbackup/etc/gworkspace/gworkspace.conf" }}
# Gmail only, two explicit users, excluding a label.FileSet { Name = "FS-GWorkspace-Gmail-Pilot" Include { Options { Signature = MD5; Compression = GZIP } Plugin = "gworkspace: mode=gmail \ config_file=/opt/ngbackup/etc/gworkspace/gworkspace.conf \ labels_exclude=Newsletters" }}
# Drive only, PDFs kept native, Google Docs exported as ODT.FileSet { Name = "FS-GWorkspace-Drive" Include { Options { Signature = MD5; Compression = GZIP } Plugin = "gworkspace: mode=drive \ config_file=/opt/ngbackup/etc/gworkspace/gworkspace.conf \ export_docs=odt" }}
# 2x/day: Full weekly + Incremental at 07:00 and 19:00.# Gmail incrementals use history.list, Drive uses changes.list.Schedule { Name = "Sched-GWorkspace-2xDay" Run = Level=Full sun at 01:00 Run = Level=Incremental mon-sat at 07:00 Run = Level=Incremental mon-sat at 19:00}
Job { Name = "BackupGoogleWorkspace" Type = Backup Level = Incremental Client = ngbackup-fd # FD where gworkspace-fd.so is installed FileSet = "FS-GWorkspace-Domain" Schedule = "Sched-GWorkspace-2xDay" Storage = File1 Pool = Default Messages = Standard Accurate = no}Restore
Section titled “Restore”Back to the same account (recommended)
Section titled “Back to the same account (recommended)”restore in bconsole → mark items → where=/. Gmail messages are re-inserted with their labels (missing custom labels are recreated); Drive files land under NGBackup Restore <date>/ (never overwrites live files); Calendar events are re-imported; Contacts recreated.
To another user (departed employee, migration)
Section titled “To another user (departed employee, migration)”Add restore_user=target@domain to the plugin options string at restore time. All selected objects land in the target account (Drive under the restore folder, Gmail with labels, Calendar into primary).
Download to filesystem (where=/path) — limitation
Section titled “Download to filesystem (where=/path) — limitation”Restoring the plugin’s virtual files to an alternate filesystem location currently produces 0-byte files: the File Daemon routes alternate-location restores to its core extractor, which does not invoke the plugin and cannot materialise a plugin data stream as plain file content. This is a shared limitation across NGBackup’s virtual-file plugins, not specific to Google Workspace. To obtain files on disk, restore to origin (into the original or a scratch account) and export from Google. Routing where= restores through the plugin is on the roadmap.
See also: Restore, Instant Recovery & V2V.
Troubleshooting
Section titled “Troubleshooting”Logs: /opt/ngbackup/working/gworkspace-backend.log. Checkpoints: /opt/ngbackup/working/gworkspace/<domain>/state.json — deleting the file forces a full resync on the next Incremental (safe; never deletes backed-up data).
| Symptom | Likely cause | Action |
|---|---|---|
token exchange HTTP 401 invalid_grant | DWD not authorized / wrong client ID / clock skew | Re-check the delegation step; timedatectl NTP on |
token exchange HTTP 400 + unauthorized_client | Scope list in Admin console ≠ scopes requested | Paste the exact 9-scope list |
Directory users.list HTTP 403 | admin_user= is not a Workspace admin | Use a super-admin (or delegated admin with Users→Read) |
Gmail 403 rateLimitExceeded bursts | >6,000 quota units/min/user | The plugin backs off automatically; for 1,500+ accounts split jobs by user ranges |
history.list 404 on every Incremental | historyId expired (>1 week gap) | Expected — the run auto-falls back to a full walk and re-checkpoints |
Drive file skipped exceeds the 10 MB export cap | Google-native file too large for files.export | Export it manually or accept the skip (fix on the roadmap) |
Job fails backend completed without producing any virtual files | Bad credentials / empty selection | Run mode=verify; check FileSet selectors |
inline secret ... is forbidden (ADR-008) | sa_key=/password= in Plugin= line | Use sa_key_file= (0600) |