Skip to main content

Proton Drive on Linux, Kinda

·1248 words·6 mins
Stuart Weaver
Author
Stuart Weaver
USPSA competitive shooter, bitcoin pleb, Linux and technology enthusiast.

📁 Project Overview
#

The project contains a small Podman‑based Windows environment that is periodically synchronized with several backup locations via two cron‑driven Bash scripts.

/archive/local/podman/windows

Item Purpose
docker-compose.yml Defines a single Windows container (dockurr/windows).
win-disk/ Holds the raw Windows disk image (data2.img) and a helper batch file for Windows‑side syncing.
windows/ Empty placeholder (mounted inside the container as /storage).
crontab entries Schedule the two scripts (proton‑drive‑win and proton‑drive‑win‑down).
Log files Written to /var/log/sweaver/proton-drive-win/proton-drive-win.out (rotated up to 24 versions).

1️⃣ Directory Layout
#

/archive/local/podman/windows/
├─ docker-compose.yml             ← Podman compose definition
├─ win-disk/
│  ├─ data2.img                   ← Raw Windows disk image (Btrfs subvolume)
│   └─ ProtonDrive/
│       └─ proton-drive-sync.bat  ← Windows‑side Robocopy wrapper
├─ windows                        ← Mounted inside container as /storage
└─ (other files/folders)          ← e.g. win‑disk/ProtonDrive, etc.

Log location: /var/log/sweaver/proton-drive-win/proton-drive-win.out (rotated up to 24 versions).


2️⃣ Cron Schedule
#

Minute Hour Day‑of‑Month Month Day‑of‑Week Command Description
23 * * * * /home/sweaver/bin/borg-backup > /dev/null 2>&1 Run Borg backup nightly at minute 23 of every hour.
3 0‑23/4 * * * /home/sweaver/bin/proton-drive-win > /dev/null 2>&1 Every 4 hours (00:03, 04:03, 08:03, 12:03, 16:03, 20:03).
3 1‑24/4 * * * /home/sweaver/bin/proton-drive-win-down > /dev/null 2>&1 Same interval shifted by one hour (01:03, 05:03, 09:03, 13:03, 17:03, 21:03).

Note: >/dev/null 2>&1 silences both stdout and stderr; all activity is captured in the rotating log file.


3️⃣ docker-compose.yml (Explained)
#

services:
  windows:
    image: dockurr/windows     # Windows base image
    container_name: windows
    environment:
     VERSION: "11"             # Windows 11 tag
  devices:
    - /dev/kvm                 # Enable KVM acceleration
    - /dev/net/tun             # Network tunnelling
  cap_add:
      - NET_ADMIN
      - NET_RAW
  ports:
   - 8006:8006                 # VNC / Web UI
   - 3389:3389/tcp             # RDP TCP
   - 3389:3389/udp             # RDP UDP
  volumes:
    - /archive/local/podman/windows/windows:/storage
    - /archive/local/podman/windows/win-disk:/storage2
    - /archive/local/podman/windows/win-disk/ProtonDrive:/shared
  restart: always
  stop_grace_period: 2m
  environment:
    DISK2_SIZE: "200G"
    RAM_SIZE: "4G"
    CPU_CORES: "4"

Key points

  • Device passthrough gives the container hardware‑accelerated graphics and networking.
  • Port mapping exposes RDP (3389) and a secondary service on 8006.
  • Three bind‑mounts expose the host directories inside the container, allowing the container to read/write the Windows disk image and the shared ProtonDrive folder.
  • Resource limits are expressed as environment variables; the image reads them at startup.

4️⃣ proton-drive-win – Full Sync Script
#

#!/bin/bash
ProtonDir="/var/log/sweaver/proton-drive-win"
ProtonFile="proton-drive-win.out"
MaxLogs=24

# ---------- Log rotation -------
test -d $ProtonDir || mkdir $ProtonDir
test -f $ProtonDir/$ProtonFile.$MaxLogs && rm $ProtonDir/$ProtonFile.$MaxLogs
test -f $ProtonDir/$ProtonFile && mv $ProtonDir/$ProtonFile $ProtonDir/$ProtonFile.0
MaxLogs=$((MaxLogs - 1))
while [ $MaxLogs -ge 0 ]; do
   test -f $ProtonDir/$ProtonFile.$MaxLogs && \
     mv $ProtonDir/$ProtonFile.$MaxLogs $ProtonDir/$ProtonFile.$((MaxLogs + 1))
  MaxLogs=$((MaxLogs - 1))
done

# ---------- Header --------
echo "=====" >> $ProtonDir/$ProtonFile 2>&1
echo "== $(date) ==="          >> $ProtonDir/$ProtonFile 2>&1
echo "============" >> $ProtonDir/$ProtonFile 2>&1

# ---------- Stop container -------
echo "/usr/bin/podman compose -f ... down" >> $ProtonDir/$ProtonFile 2>&1
time /usr/bin/podman compose -f /archive/local/podman/windows/docker-compose.yml down \
    >> $ProtonDir/$ProtonFile 2>&1

# ------ Btrfs snapshot handling ----
echo "sudo btrfs subvolume delete /archive/../backups" >> $ProtonDir/$ProtonFile 2>&1
time sudo btrfs subvolume delete /archive/archive/other/btrfs-snap/backups \
    >> $ProtonDir/$ProtonFile 2>&1

echo "sudo btrfs subvolume snapshot /archive/.../backups /archive/.../btrfs-snap" >> $ProtonDir/$ProtonFile 2>&1
time sudo btrfs subvolume snapshot /archive/archive/other/backups /archive/archive/other/btrfs-snap \
    >> $ProtonDir/$ProtonFile 2>&1

echo "sudo btrfs subvolume list /archive" >> $ProtonDir/$ProtonFile 2>&1
time sudo btrfs subvolume list /archive >> $ProtonDir/$ProtonFile 2>&1

# ---------- Calculate loop‑mount offset ----------
part_units=$(fdisk -l /archive/local/podman/windows/win-disk/data2.img |
           grep -E '(Units:)' | awk '{print $8}')
part_start=$(fdisk -l /archive/local/podman/windows/win-disk/data2.img |
        grep -E '(Microsoft basic data)' | awk '{print $2}')
offset=$(( part_units * part_start ))

# ---------- Mount Windows image --------
echo "sudo mount -t auto -o loop,offset=$offset ..." >> $ProtonDir/$ProtonFile 2>&1
time sudo mount -t auto -o loop,offset=$offset \
    /archive/local/podman/windows/win-disk/data2.img \
    /archive/local/podman/windows/win-disk/mnt \
    >> $ProtonDir/$ProtonFile 2>&1

# ------- Rsync data into the mounted image ----------
# Backups → BorgBackups
echo "rsync -avP /archive/../backups/ /archive/.../BorgBackups/" >> $ProtonDir/$ProtonFile 2>&1
time rsync -avP /archive/archive/other/btrfs-snap/backups/ \
    /archive/local/podman/windows/win-disk/mnt/tmp/BorgBackups/ \
    >> $ProtonDir/$ProtonFile 2>&1

# Personal files → sweaver.net
echo "rsync -avP /home/sweaver/Documents/Obsidian /home/sweaver/Documents/Syncthing ..." >> $ProtonDir/$ProtonFile 2>&1
time rsync -avP /home/sweaver/Documents/Obsidian /home/sweaver/Documents/Syncthing \
    /archive/local/podman/windows/win-disk/mnt/tmp/sweaver.net/ \
    >> $ProtonDir/$ProtonFile 2>&1

# Video backups (excluding large folders)
echo "rsync -avP --exclude USPSA --exclude DaVinciResolve ..." >> $ProtonDir/$ProtonFile 2>&1
time rsync -avP --exclude USPSA --exclude DaVinciResolve \
    amd:/home/sweaver/Videos \
    /archive/local/podman/windows/win-disk/mnt/tmp/sweaver.net/ \
    >> $ProtonDir/$ProtonFile 2>&1

# ---------- Unmount ----------
echo "sudo umount /archive/.../mnt" >> $ProtonDir/$ProtonFile 2>&1
time sudo umount /archive/local/podman/windows/win-disk/mnt \
    >> $ProtonDir/$ProtonFile 2>&1

# ---------- Restart container ----------
echo "/usr/bin/podman compose -f ... up -d" >> $ProtonDir/$ProtonFile 2>&1
time /usr/bin/podman compose -f /archive/local/podman/windows/docker-compose.yml up -d \
    >> $ProtonDir/$ProtonFile 2>&1

# ---------- Footer --------
echo "===============" >> $ProtonDir/$ProtonFile 2>&1
echo "== $(date) ==="     >> $ProtonDir/$ProtonFile 2>&1
echo "===============" >> $ProtonDir/$ProtonFile 2>&1

# ---------- Email log --------
cat $ProtonDir/$ProtonFile | mailx -s "ProtonDrive Win Sync Status" email@email.net

Why each block matters

  • Log rotation – Keeps the most recent 24 runs.
  • Btrfs snapshots – Point‑in‑time copies of the Linux backup source.
  • Loop‑mount offset – Computed from the partition table so the Windows image can be accessed without a VM.
  • Rsync – Mirrors three distinct data sets (Borg backups, personal docs, video archive) into the Windows filesystem.
  • Email – Sends a quick status report.

5️⃣ proton-drive-win-down – Quick Shut‑Down Script
#

#!/bin/bash
ProtonDir="/var/log/sweaver/proton-drive-win"
ProtonFile="proton-drive-win.out"

# Header (same as main script)
echo "=====" >> $ProtonDir/$ProtonFile 2>&1
echo "== $(date) ==="     >> $ProtonDir/$ProtonFile 2>&1
echo "=====" >> $ProtonDir/$ProtonFile 2>&1

# Stop container only
echo "/usr/bin/podman compose -f ... down" >> $ProtonDir/$ProtonFile 2>&1
time /usr/bin/podman compose -f /archive/local/podman/windows/docker-compose.yml down \
    >> $ProtonDir/$ProtonFile 2>&1

# Footer + email
echo "===" >> $ProtonDir/$ProtonFile 2>&1
echo "== $(date) ==="     >> $ProtonDir/$ProtonFile 2>&1
echo "=====" >> $ProtonDir/$ProtonFile 2>&1

cat $ProtonDir/$ProtonFile | mailx -s "ProtonDrive Win Sync Status" email@email.net

Purpose: Performs the log rotation and container shutdown, then emails the log. Useful when you only need to bring the container down without doing a full sync.


6️⃣ Windows‑Side Helper (proton-drive-sync.bat)
#

robocopy D:\tmp\ D:\ProtonDrive\ /E
start /b C:\Users\bil\AppData\Local\Programs\Proton\Drive\ProtonDrive.exe
pause
  • After the Linux side finishes copying data into D:\tmp\, the batch file copies everything into the actual Proton Drive folder (D:\ProtonDrive).
  • Then it launches the native Proton Drive client in the background (start /b).
  • pause keeps the console window open for manual inspection.

7️⃣ Putting It All Together – Execution Flow
#

  1. Cron triggers proton-drive-win (or …‑down).
  2. Script rotates logs, stops the Podman container, and creates a fresh Btrfs snapshot.
  3. Calculates the corect loop‑device offset and mounts the Windows disk image (data2.img).
  4. Runs three rsync jobs that copy:
    • Recent Borg backups → BorgBackups inside the image.
    • Personal Obsidian & Syncthing files → sweaver.net folder inside the image.
    • Video backups (excluding huge folders) → same sweaver.net folder.
  5. Unmounts the image, restarts the container, writes a footer, and emails the full log.
  6. Inside the Windows container, at boot proton-drive-sync.bat is run to push the newly‑synced data into the native Proton Drive client.

8️⃣ Quick Reference Comands
#

Action Command
View current cron jobs crontab -l
Manually run the full sync bash /home/sweaver/bin/proton-drive-win
Manually stop the container only bash /home/sweaver/bin/proton-drive-win-down
Inspect the latest log tail -n 30 /var/log/sweaver/proton-drive-win/proton-drive-win.out
Check Btrfs snapshots sudo btrfs subvolume list /archive
Mount the Windows image manually sudo mount -t auto -o loop,offset=$OFFSET data2.img /mnt (replace $OFFSET with the calculated value).
Run the Windows batch file (inside the container) cmd /c "C:\path\to\proton-drive-sync.bat"

🎯 What You Can Do Next
#

  • Adjust the cron schedule if you want a different frequency.
  • Add more rsync sources (e.g., additional project directories).
  • Enable email alerts for failures by checking the exit status of each command and adding conditional mailx calls.
  • Extend the Docker‑Compose file (add more services, GPU pas‑through, etc.) if your workflow grows.

Feel free to modify any part of the scripts or configuration to suit your exact backup and sync requirements!