đ 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>&1silences 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.netWhy 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.netPurpose: 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). pausekeeps the console window open for manual inspection.
7ď¸âŁ Putting It All Together â Execution Flow #
- Cron triggers
proton-drive-win(orâŚâdown). - Script rotates logs, stops the Podman container, and creates a fresh Btrfs snapshot.
- Calculates the corect loopâdevice offset and mounts the Windows disk image (
data2.img). - Runs three
rsyncjobs that copy:- Recent Borg backups â
BorgBackupsinside the image. - Personal Obsidian & Syncthing files â
sweaver.netfolder inside the image. - Video backups (excluding huge folders) â same
sweaver.netfolder.
- Recent Borg backups â
- Unmounts the image, restarts the container, writes a footer, and emails the full log.
- Inside the Windows container, at boot
proton-drive-sync.batis 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
mailxcalls. - 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!