Automated Off-Site Backups for NixOS Using the Google Drive API

A fun project for Junior Engineer/College Students

Automated Off-Site Backups for NixOS Using the Google Drive API

//I don't use this in production this is just a fun thing I discovered you can do which is absolutely iron clad intended as a reliable solution for students in college and maybe junior developers. I personally use Proton Drive & Gitlab for back ups.

Working in Tech, I’ve seen more backup strategies than I can count—cron-driven rsync jobs, USB-stick shuffle, half-baked Git repos… and every one eventually broke when you needed it most. But there’s a deceptively simple pattern that leverages tools you already know: use Google Drive as your off-site backup target, scripted through its REST API, and wire it declaratively into NixOS. The result? A reproducible, automated, nearly maintenance-free backup pipeline that runs on every boot and weekly thereafter.


Why Google Drive?

Universality
Everyone has a Google account. No new buckets, no extra contracts—Drive is ready.

Free Tier & Affordability
15 GB free, then pay-as-you-grow. Perfect for personal or small-team archives.

Manual Restore Fallback
Lost your machine? You can still log into drive.google.com, grab your configs, and be back online in minutes.


How the Drive API Works

At the heart of this approach is the Drive API v3’s files.create endpoint:

Raw uploads:

POST https://www.googleapis.com/drive/v3/files?uploadType=media
Authorization: Bearer <ACCESS_TOKEN>
Content-Type: application/octet-stream

<file-bytes>

Metadata + content (multipart):
Allows you to set the file name, parent folder IDs, and other properties in-line.

Authentication can be:

OAuth 2.0 (user-driven): Ideal for manual restores or desktop scripts.

Service Account (headless): Perfect for servers or automated agents—grant only the drive.file scope so your backup script can only touch its own files.


Declarative Packaging on NixOS

NixOS excels at reproducible environments. In your /etc/nixos/configuration.nix, include:

environment.systemPackages = with pkgs; [
  python39
  python39Packages.google-api-python-client
  jq
];

Running nixos-rebuild switch pulls in the official Google client and its dependencies—no manual pip installs.


The Backup Script

Below is a Python example (/etc/backup/backup_drive.py) that:

Mirrors your $HOME hierarchy in Drive.

Uploads each file under its corresponding folder.

Uses resumable uploads for reliability.

#!/usr/bin/env python3
import os
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload

# 1. Load credentials
creds = service_account.Credentials.from_service_account_file(
    '/etc/backup/drive-sa.json',
    scopes=['https://www.googleapis.com/auth/drive.file']
)
drive = build('drive', 'v3', credentials=creds)

# 2. Ensure a folder path exists and return its Drive ID
def ensure_folder(path_parts, parent_id='root'):
    for part in path_parts:
        query = (
            f"name='{part}' and "
            f"mimeType='application/vnd.google-apps.folder' and "
            f"'{parent_id}' in parents"
        )
        res = drive.files().list(q=query, fields='files(id)').execute()
        if res['files']:
            parent_id = res['files'][0]['id']
        else:
            folder = drive.files().create(body={
                'name': part,
                'mimeType': 'application/vnd.google-apps.folder',
                'parents': [parent_id]
            }).execute()
            parent_id = folder['id']
    return parent_id

# 3. Upload a file under the given parent ID
def upload_file(local_path, parent_id):
    media = MediaFileUpload(local_path, resumable=True)
    metadata = {
        'name': os.path.basename(local_path),
        'parents': [parent_id]
    }
    drive.files().create(body=metadata, media_body=media).execute()

# 4. Traverse $HOME and push everything
HOME = os.environ['HOME']
for root, dirs, files in os.walk(HOME):
    rel_parts = os.path.relpath(root, HOME).split(os.sep)
    parent = ensure_folder(rel_parts if rel_parts != ['.'] else [])
    for fname in files:
        upload_file(os.path.join(root, fname), parent)

Place your Service Account JSON at /etc/backup/drive-sa.json (managed securely via NixOS secrets).


Declarative Scheduling & Activation

Add the following to your Nix configuration to:

Run on boot (1 minute after networking is up)

Run weekly with catch-up if the machine was powered off

{ config, pkgs, ... }:

let
  backupScript = pkgs.writeScriptBin "backup-drive" ''
    #!/usr/bin/env bash
    exec ${pkgs.python39}/bin/python3 /etc/backup/backup_drive.py
  '';
in {
  # Deploy script & credentials
  environment.etc."backup/backup_drive.py".text = builtins.readFile ./backup_drive.py;
  environment.etc."backup/drive-sa.json".text     = builtins.readFile ./drive-sa.json;
  environment.systemPackages = [ backupScript ];

  # Service: Upload $HOME
  services.systemd.services.backupHome = {
    description = "Upload $HOME to Google Drive";
    after       = [ "network-online.target" ];
    wants       = [ "network-online.target" ];
    serviceConfig = {
      Type          = "oneshot";
      ExecStart     = "${backupScript}";
      WorkingDirectory = "/home/youruser";
      User          = "youruser";
      Group         = "youruser";
      Environment   = "HOME=/home/youruser";
    };
  };

  # Timer: Boot + Weekly
  services.systemd.timers.backupHomeTimer = {
    description   = "Run backup-drive on boot and weekly";
    wants         = [ "backupHome.service" ];
    timerConfig = {
      OnBootSec    = "1min";
      OnCalendar   = "weekly";
      Persistent   = true;
    };
    unit = "backupHome.service";
  };
}

After nixos-rebuild switch, you’re set. On each boot (1 min in) and once per week, your entire home directory mirrors to Google Drive.


Security & Reliability Considerations

Least Privilege: Grant only drive.file scope. Keep your SA key out of version control and in an encrypted NixOS secret.

API Quotas: Drive limits ~10 requests/sec. For large file sets, batch or throttle using simple sleep() calls or built-in retry logic.

Large Files: For files >100 MB, use resumable uploads to avoid timeouts.

Filesystem Metadata: Drive won’t preserve Unix permissions or symlinks. Tar or encrypt archives first if you need to restore exact file modes.


Benefits

Reproducible: All scripts, packages, schedules defined in one Nix file. Roll back any change instantly.

Automated: No more manual USB or cron hacks—activation scripts and timers handle everything.

Portable: Lost your machine? New laptop shows up, point it at your Drive directory, and run a few API calls to restore dotfiles, configs, even VM images.

Vendor Agile: Swap Drive for another HTTP-compatible target with minimal script tweaks.


Conclusion

Stop cobbling together half-baked backup jobs. Embrace the simplicity of “Drive + NixOS” and get:

Zero-touch off-site backups on every boot + weekly.

Declarative control over scripts, schedules, and dependencies.

Peace of mind knowing your home directory is safely stored off-site.

Drop your favorite backup hacks or horror stories below—let’s keep each other’s automation game sharp!