README
¶
gdrive-backup
A small Go CLI that uploads a directory or a single file to one Google Drive folder, using an rclone remote you have already configured — so there is no separate OAuth dance, client ID, or credentials file.
- A directory is compressed to
tar.zstfirst, so it lands as one object. - A single file is uploaded byte for byte, keeping its extension so Drive can
still preview it. Add
-compressto pack it into atar.zstinstead.
Every upload is size-verified against the remote afterwards, and the destination folder is checked before anything is compressed.
Install
Download a binary from Releases, or build it:
go install github.com/mgorunuch/gdrive-backup@latest
Requires Go 1.24+ to build, and rclone on PATH at runtime.
Setup
-
Configure an rclone Drive remote if you have not already:
rclone config # create a remote of type "drive", named e.g. gdrive rclone listremotes # should print gdrive: -
Point the tool at a destination folder. Open the folder in Drive and copy the last segment of its URL —
https://drive.google.com/drive/folders/<ID>:export GDRIVE_BACKUP_FOLDER_ID=<ID> # add to ~/.zshrc or ~/.bashrc-folder-id <ID>overrides it per run, and-remote <name>selects a remote other than the defaultgdrive.
Usage
gdrive-backup ~/Documents/project # -> project-20260805-221832.tar.zst
gdrive-backup ~/Documents/report.pdf # -> report-20260805-221832.pdf
For a directory the binary:
- checks the remote folder is reachable before spending time compressing,
- streams
tar→zstdinto a temp file, - uploads it with
rclone copyto, - re-stats the uploaded object and fails if the byte count does not match,
- deletes the temp file.
For a single file it skips steps 2 and 5 and uploads the file where it lies.
Naming
The default name is stamped with the run time, so repeat runs never silently overwrite an earlier backup. The stamp goes before the extension for a plain file and after the name for an archive:
| Source | Default name |
|---|---|
project/ |
project-20260805-221832.tar.zst |
report.pdf |
report-20260805-221832.pdf |
report.pdf with -compress |
report.pdf-20260805-221832.tar.zst |
logs.tar.gz |
logs-20260805-221832.tar.gz |
.bashrc |
.bashrc-20260805-221832 |
Use -no-timestamp for the bare name, or -name to set it outright. Either way
the name is reduced to a single path component, so it cannot write outside the
target folder.
Common invocations
# A single file, uploaded as-is
gdrive-backup ~/Documents/report.pdf
# A single file, compressed first (worth it for logs, dumps, text)
gdrive-backup -compress ~/dumps/db.sql
# Overwrite the same object each run instead of accumulating copies
gdrive-backup -no-timestamp ~/Documents/report.pdf
# Skip the noise
gdrive-backup -exclude node_modules -exclude '.git' -exclude '*.log' ~/code/app
# Exclusions from a file (one glob per line, # for comments)
gdrive-backup -exclude-from ~/.backup-ignore ~/code/app
# Fixed name, so each run overwrites the previous backup
gdrive-backup -name app-latest.tar.zst ~/code/app
# Squeeze harder (slower)
gdrive-backup -level best ~/code/app
# No temp file — compress straight into the upload (for low disk space)
gdrive-backup -stream /Volumes/big/dataset
# Keep a local copy too
gdrive-backup -out ~/backups/ ~/code/app
# Build the archive, report the size, skip the upload
gdrive-backup -dry-run -out /tmp/check.tar.zst ~/code/app
Flags
| Flag | Default | Meaning |
|---|---|---|
-folder-id |
$GDRIVE_BACKUP_FOLDER_ID |
Drive folder ID to upload into |
-remote |
gdrive |
rclone remote name |
-name |
see Naming | Filename on Drive |
-compress |
false |
Compress a single file too (directories always are) |
-no-timestamp |
false |
Drop the timestamp from the default name |
-level |
default |
fastest | default | better | best |
-threads |
NumCPU |
Compression threads |
-exclude |
— | Glob to exclude; repeatable |
-exclude-from |
— | File of globs, one per line |
-out |
— | Write the archive here and keep it |
-tmpdir |
system temp | Where the temporary archive lives |
-stream |
false |
Pipe straight to Drive, no temp file |
-dereference |
false |
Follow symlinks instead of storing them |
-no-root-dir |
false |
Do not nest entries under a top-level <dir>/ |
-dry-run |
false |
Build the archive, skip the upload |
-rclone |
rclone |
Path to the rclone binary |
-quiet |
false |
Only print errors |
Exclude patterns
node_modules— matches that name at any depth.*.log— glob against the basename, at any depth.build/out— anchored to the source root.build/**— thebuilddirectory and everything beneath it.
Excluding a directory skips its whole subtree.
Compression-only flags (-level, -threads, -exclude, -out, -stream) do
nothing for a plain file upload; -exclude warns and -out is an error rather
than being ignored silently.
What the archive contains
Entries are nested under a top-level directory named after the source, so
extraction never scatters files into the current directory. A single file packed
with -compress becomes a one-entry archive under its own name. Unix permissions,
symlinks (stored as links, not followed), and sub-second mtimes are preserved via
the PAX tar format. Sockets, FIFOs, and device nodes are skipped with a warning,
as are files that cannot be read — one unreadable file does not abort the backup.
Extract with:
tar --zstd -xf project-20260805-221832.tar.zst
Exit codes
| Code | Meaning |
|---|---|
0 |
Archive uploaded and size-verified |
1 |
Error (unreachable folder, upload failure, size mismatch) |
2 |
Bad usage |
130 |
Interrupted (Ctrl-C); the temp archive is removed |
Notes
- The temp archive is deleted on success, on interrupt, and on failure — except
when the upload itself fails and
-outwas given, where it is kept and its path is reported so the run can be retried without recompressing. -streamhas no local copy to fall back on, so a mid-flight failure means recompressing from scratch. Prefer the default for large or slow uploads.- The archive name is sanitized: path separators and control characters become
-, so-namecannot write outside the target folder.