Skip to content

Commit efc3df2

Browse files
committed
feat(backup): expand google backup coverage
1 parent 8e48087 commit efc3df2

10 files changed

Lines changed: 997 additions & 21 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66
- Backup: add `gog backup` with age-encrypted Git shards, Gmail labels/raw message export, Calendar/Contacts/Tasks/Drive metadata adapters, manifest status, full decrypt-and-verify, shard `cat`, local plaintext export, docs, and security-focused regression coverage.
77
- Backup: expand `gog backup push --services all` with Drive content export/download, Gmail settings, native Workspace Docs/Sheets/Slides/Form data, Apps Script projects, Chat, Classroom, best-effort optional service error shards, and plaintext Drive file export.
8+
- Backup: extend `--services all` with Drive permissions/comments/revisions, Calendar ACL/settings/colors, contact groups, Cloud Identity groups, Workspace Admin Directory users/groups/members, Keep notes, and local Gmail message caching for resumable full-mailbox fetches.
89

910
### Fixed
1011
- Gmail: auto-fill draft reply subjects from the original message when `gmail drafts create --reply-to-message-id` omits `--subject`. (#488) — thanks @jbowerbir.

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -747,10 +747,13 @@ private age identity locally at `~/.gog/age.key`; GitHub only receives public
747747
The private `AGE-SECRET-KEY-...` value must stay local or in a password manager.
748748

749749
Supported backup services are `gmail`, `gmail-settings`, `calendar`,
750-
`contacts`, `tasks`, `drive`, `workspace`, `appscript`, `chat`, and
751-
`classroom`; `all` expands to those services. Drive now stores metadata plus
752-
exported Google-native file content by default. Non-Google binary Drive files
753-
are metadata-only unless `--drive-binary-contents` is set. Workspace inventories
750+
`contacts`, `tasks`, `drive`, `workspace`, `appscript`, `chat`, `classroom`,
751+
`groups`, `admin`, and `keep`; `all` expands to those services. Drive stores
752+
metadata, permissions, comments, revisions, and exported Google-native file
753+
content by default. Non-Google binary Drive files are metadata-only unless
754+
`--drive-binary-contents` is set. Gmail raw-message fetches use a local cache by
755+
default so interrupted full-mailbox backups can resume; use
756+
`--gmail-refresh-cache` to force a refetch. Workspace inventories
754757
Docs/Sheets/Slides and backs up Forms/responses discovered through Drive; add
755758
`--workspace-native` for full native Docs/Sheets/Slides API JSON.
756759
Optional Workspace-only services use `--best-effort` by default, recording

docs/backup.md

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,23 @@ remote.
7171

7272
Supported services:
7373

74-
- `gmail`: labels and raw MIME messages.
74+
- `gmail`: labels and raw MIME messages. Fetched raw messages are cached under
75+
the local user cache by default so interrupted full-mailbox runs can resume
76+
the expensive message download phase; use `--no-gmail-cache` or
77+
`--gmail-refresh-cache` to bypass it.
7578
- `gmail-settings`: filters, forwarding addresses, auto-forwarding, send-as
7679
aliases, vacation responder, delegate visibility, POP, IMAP, and language
7780
settings.
78-
- `calendar`: calendar list entries and all events, including deleted events.
79-
- `contacts`: People API contacts and other contacts.
81+
- `calendar`: calendar list entries, ACL rules, Calendar settings/colors, and
82+
all events, including deleted events.
83+
- `contacts`: People API contacts, other contacts, and contact groups.
8084
- `tasks`: task lists and tasks, including completed, deleted, hidden, and
8185
assigned tasks.
82-
- `drive`: shared drives, Drive file metadata, and downloaded/exported file
83-
content. Google Docs export as `.docx` and Markdown, Sheets as `.xlsx`,
84-
Slides as `.pptx` and PDF, Drawings as PNG and PDF, and binary files as
85-
metadata-only unless `--drive-binary-contents` is set.
86+
- `drive`: shared drives, Drive file metadata, permissions, comments, revision
87+
metadata, and downloaded/exported file content. Google Docs export as `.docx`
88+
and Markdown, Sheets as `.xlsx`, Slides as `.pptx` and PDF, Drawings as PNG
89+
and PDF, and binary files as metadata-only unless `--drive-binary-contents`
90+
is set.
8691
- `workspace`: Docs/Sheets/Slides inventory plus Forms and form responses
8792
discovered through Drive. Add `--workspace-native` to fetch full native
8893
Docs/Sheets/Slides API JSON.
@@ -92,21 +97,31 @@ Supported services:
9297
access.
9398
- `classroom`: courses, topics, announcements, coursework, materials, and
9499
submissions visible to the authenticated account.
100+
- `groups`: Cloud Identity groups the account belongs to, plus member lists
101+
when the API permits them.
102+
- `admin`: Workspace Admin Directory users, groups, and group members. This is
103+
Workspace-only and requires the existing Admin SDK/domain-wide delegation
104+
setup.
105+
- `keep`: Google Keep notes. This is Workspace-only and requires the existing
106+
Keep service-account setup.
95107

96108
`all` expands to every supported service. Pushing a subset updates that subset
97109
and preserves existing shards for services that were not selected, as long as
98110
the age recipients are unchanged.
99111

100-
`gog backup push` enables `--drive-contents` and `--best-effort` by default.
101-
Use `--no-drive-contents` for metadata-only Drive runs, or
112+
`gog backup push` enables `--drive-contents`, `--drive-collaboration`,
113+
`--gmail-cache`, and `--best-effort` by default. Use `--no-drive-contents` for
114+
metadata-only Drive runs, `--no-drive-collaboration` to skip per-file Drive
115+
permissions/comments/revisions, or
102116
`--drive-content-max-bytes <bytes>` to skip individual large Drive downloads.
103117
Drive content exports Google-native files by default; set
104118
`--drive-binary-contents` only when you intentionally want non-Google binary
105119
file bytes in Git shards. Use `--workspace-native` only when you want the
106120
heavier native API JSON in addition to readable Drive exports;
107121
`--workspace-max-files` bounds that native fetch per file type for smoke tests.
108122
Best-effort optional services record encrypted `errors` shards and let the rest
109-
of the backup finish.
123+
of the backup finish. The Gmail cache is only a local acceleration/resume cache;
124+
encrypted backup shards remain the source of truth once a push completes.
110125

111126
## Files
112127

@@ -127,6 +142,9 @@ data/gmail/<account-hash>/messages/YYYY/MM/part-0001.jsonl.gz.age
127142
data/calendar/<account-hash>/...
128143
data/contacts/<account-hash>/...
129144
data/drive/<account-hash>/...
145+
data/groups/<account-hash>/...
146+
data/admin/<account-hash>/...
147+
data/keep/<account-hash>/...
130148
data/tasks/<account-hash>/...
131149
```
132150

@@ -238,17 +256,26 @@ Raw message payloads stay base64url encoded inside encrypted JSONL. This
238256
preserves the RFC 2822 message content while keeping the shard format text
239257
friendly.
240258

259+
By default, each fetched raw message is also cached locally under the OS user
260+
cache directory (`gogcli/backup/gmail/<account-hash>/raw-v1/`). The cache stores
261+
the same raw message row that will be encrypted into shards and is keyed by a
262+
SHA-256 of the Gmail message ID, so rerunning after an interruption can reuse
263+
already fetched messages. `--gmail-refresh-cache` forces a refetch. The cache is
264+
plaintext local data; clear it if the machine should not retain local mail
265+
copies outside the encrypted backup/export locations.
266+
241267
`--include-spam-trash` defaults to true. Use `--query` and `--max` for bounded
242268
test exports; omit them for a full mailbox scan.
243269

244270
The Gmail settings adapter backs up account configuration through read-only
245271
settings endpoints. Some settings, such as delegates, can be forbidden for
246272
consumer accounts; those errors are kept inside the encrypted settings shard.
247273

248-
The Calendar adapter backs up calendar list entries and all events from each
249-
calendar. The Contacts adapter backs up contacts and other contacts. The Tasks
250-
adapter backs up task lists and tasks. The Drive adapter backs up shared drives,
251-
file metadata, and Google-native file exports by default. Content rows store
274+
The Calendar adapter backs up calendar list entries, ACLs, settings, colors,
275+
and all events from each calendar. The Contacts adapter backs up contacts, other
276+
contacts, and contact groups. The Tasks adapter backs up task lists and tasks.
277+
The Drive adapter backs up shared drives, file metadata, permissions, comments,
278+
revision metadata, and Google-native file exports by default. Content rows store
252279
base64 bytes inside encrypted JSONL so Git only sees ciphertext; plaintext
253280
export decodes them back into regular files. Non-Google binary Drive bytes are
254281
opt-in because personal Drives can easily contain tens of gigabytes.

internal/cmd/backup.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const (
3939
backupServiceDrive = "drive"
4040
backupServiceGmail = "gmail"
4141
backupServiceGmailSettings = "gmail-settings"
42+
backupServiceGroups = "groups"
43+
backupServiceAdmin = "admin"
44+
backupServiceKeep = "keep"
4245
backupServiceTasks = "tasks"
4346
backupServiceWorkspace = "workspace"
4447
)
@@ -115,8 +118,11 @@ type BackupPushCmd struct {
115118
DriveContents bool `name:"drive-contents" help:"Download/export Drive file contents into encrypted shards" default:"true" negatable:""`
116119
DriveBinaryContents bool `name:"drive-binary-contents" help:"Include non-Google Drive binary file bytes in encrypted shards"`
117120
DriveContentMaxBytes int64 `name:"drive-content-max-bytes" help:"Skip individual Drive content exports larger than this many bytes; 0 means unlimited" default:"0"`
121+
DriveCollaboration bool `name:"drive-collaboration" help:"Back up Drive permissions, comments, and revision metadata" default:"true" negatable:""`
118122
WorkspaceNative bool `name:"workspace-native" help:"Fetch full native Docs/Sheets/Slides API JSON in addition to Drive exports"`
119123
WorkspaceMaxFiles int `name:"workspace-max-files" help:"Max Docs/Sheets/Slides files per type for native Workspace metadata; 0 means all" default:"0"`
124+
GmailCache bool `name:"gmail-cache" help:"Cache fetched Gmail raw messages locally so interrupted full backups can resume" default:"true" negatable:""`
125+
GmailRefreshCache bool `name:"gmail-refresh-cache" help:"Refetch Gmail messages even when a local backup cache entry exists"`
120126
BestEffort bool `name:"best-effort" help:"Record optional service errors as backup rows and continue" default:"true" negatable:""`
121127
}
122128

@@ -170,6 +176,7 @@ func (c *BackupPushCmd) Run(ctx context.Context, flags *RootFlags) error {
170176
IncludeContents: c.DriveContents,
171177
IncludeBinary: c.DriveBinaryContents,
172178
MaxContentBytes: c.DriveContentMaxBytes,
179+
IncludeCollab: c.DriveCollaboration,
173180
})
174181
if err != nil {
175182
return err
@@ -181,6 +188,8 @@ func (c *BackupPushCmd) Run(ctx context.Context, flags *RootFlags) error {
181188
Max: c.Max,
182189
IncludeSpamTrash: c.IncludeSpamTrash,
183190
ShardMaxRows: c.ShardMaxRows,
191+
CacheMessages: c.GmailCache,
192+
RefreshCache: c.GmailRefreshCache,
184193
})
185194
if err != nil {
186195
return err
@@ -192,6 +201,30 @@ func (c *BackupPushCmd) Run(ctx context.Context, flags *RootFlags) error {
192201
return err
193202
}
194203
snapshots = append(snapshots, snapshot)
204+
case backupServiceGroups:
205+
snapshot, err := c.buildOptionalSnapshot(flags, backupServiceGroups, func() (backup.Snapshot, error) {
206+
return buildGroupsBackupSnapshot(ctx, flags, c.ShardMaxRows)
207+
})
208+
if err != nil {
209+
return err
210+
}
211+
snapshots = append(snapshots, snapshot)
212+
case backupServiceAdmin:
213+
snapshot, err := c.buildOptionalSnapshot(flags, backupServiceAdmin, func() (backup.Snapshot, error) {
214+
return buildAdminBackupSnapshot(ctx, flags, c.ShardMaxRows)
215+
})
216+
if err != nil {
217+
return err
218+
}
219+
snapshots = append(snapshots, snapshot)
220+
case backupServiceKeep:
221+
snapshot, err := c.buildOptionalSnapshot(flags, backupServiceKeep, func() (backup.Snapshot, error) {
222+
return buildKeepBackupSnapshot(ctx, flags, c.ShardMaxRows)
223+
})
224+
if err != nil {
225+
return err
226+
}
227+
snapshots = append(snapshots, snapshot)
195228
case backupServiceTasks:
196229
snapshot, err := buildTasksBackupSnapshot(ctx, flags, c.ShardMaxRows)
197230
if err != nil {
@@ -211,7 +244,7 @@ func (c *BackupPushCmd) Run(ctx context.Context, flags *RootFlags) error {
211244
}
212245
snapshots = append(snapshots, snapshot)
213246
default:
214-
return fmt.Errorf("unsupported backup service %q (supported: all, appscript, calendar, chat, classroom, contacts, drive, gmail, gmail-settings, tasks, workspace)", service)
247+
return fmt.Errorf("unsupported backup service %q (supported: all, admin, appscript, calendar, chat, classroom, contacts, drive, gmail, gmail-settings, groups, keep, tasks, workspace)", service)
215248
}
216249
}
217250
result, err := backup.PushSnapshot(ctx, mergeBackupSnapshots(snapshots...), c.options())
@@ -239,6 +272,8 @@ type BackupGmailPushCmd struct {
239272
Max int64 `name:"max" aliases:"limit" help:"Max Gmail messages to export; 0 means all" default:"0"`
240273
IncludeSpamTrash bool `name:"include-spam-trash" help:"Include spam and trash" default:"true"`
241274
ShardMaxRows int `name:"shard-max-rows" help:"Max messages per encrypted shard" default:"1000"`
275+
CacheMessages bool `name:"gmail-cache" help:"Cache fetched raw messages locally so interrupted full backups can resume" default:"true" negatable:""`
276+
RefreshCache bool `name:"gmail-refresh-cache" help:"Refetch messages even when a local backup cache entry exists"`
242277
}
243278

244279
func (c *BackupGmailPushCmd) Run(ctx context.Context, flags *RootFlags) error {
@@ -247,6 +282,8 @@ func (c *BackupGmailPushCmd) Run(ctx context.Context, flags *RootFlags) error {
247282
Max: c.Max,
248283
IncludeSpamTrash: c.IncludeSpamTrash,
249284
ShardMaxRows: c.ShardMaxRows,
285+
CacheMessages: c.CacheMessages,
286+
RefreshCache: c.RefreshCache,
250287
})
251288
if err != nil {
252289
return err

0 commit comments

Comments
 (0)