Releases: The-HBA/Retrosync
Retrosync v0.5.0
EmuDeck is now a first-class frontend, and the recommended setup for cross-platform sync.
Unlike RetroBat↔RetroDECK (different projects, different layouts), EmuDeck uses the identical Emulation/ layout on Windows and Linux. That means an EmuDeck Windows PC and an EmuDeck Steam Deck can sync their saves straight across — the cross-OS sync that wasn't possible before.
Frontend menus now lead with EmuDeck
Linux (retrosync-setup.sh): [1] EmuDeck [2] RetroDECK [3] Custom
Windows (retrosync-setup.ps1): [1] EmuDeck [2] RetroBat [3] Custom
Auto-detection
Parses the EmuDeck settings file for emulationPath:
- Linux:
~/.config/EmuDeck/backend/settings.sh(emulationPath=~/Emulation) - Windows:
%APPDATA%\EmuDeck\settings.ps1($emulationPath="D:\Emulation")
The stored value can be stale (e.g. it says ~/Emulation while your data is actually on an SD card), so RetroSync uses it as a starting guess, checks whether it exists on disk, and always lets you confirm or correct it at the prompt.
What syncs
| Scope | Folder | Notes |
|---|---|---|
roms |
Emulation/roms/ |
bare/shared; per-game selective picker still works |
bios |
Emulation/bios/ |
bare/shared |
ed-saves |
Emulation/saves/ (whole tree) |
the cross-OS win — one folder, shared between EmuDeck-Win ↔ EmuDeck-Linux; includes saves and states; versioned (5 kept) |
Because the saves/ tree has the same per-emulator layout on both OSes, RetroSync syncs it as a single folder — no per-emulator path mapping needed. The per-emulator saves picker is now shown only for RetroDECK/RetroBat.
Deferred to a follow-up
EmuDeck's storage/ directory is intentionally left out for now — it mixes huge device-specific data (installed games under ryujinx/games, shadps4/games, rpcs3/dev_hdd0) with scraped media, and the downloaded_media location differs between installs. Use custom mode to sync specific parts meanwhile.
Versions
retrosync-setup.sh/.ps1→0.5.0
Retrosync v0.4.2
Two hardening additions while we stabilise the 0.4 feature set.
🛑 Case-collision detector (cross-OS deal-breaker)
Before applying, the script now scans every local folder it's about to sync for sibling entries that differ only in case — e.g. databases/ + Databases/, or Bully (USA).iso + bully (usa).iso.
Why it matters: a case-insensitive peer (Windows NTFS, or a ZFS dataset created with casesensitivity != sensitive) can't hold both names, so Syncthing gets permanently stuck "out of sync" on everything beneath the collision. This is exactly what broke the Bazzite BIOS databases/Databases sync. The check now lists the exact colliding paths and lets you fix them before they cause 600+ failed items.
- Linux (
find_case_collisions) is where it matters — ext4/btrfs can actually hold both names. - Windows (
Find-CaseCollisions) usually finds nothing (NTFS prevents the pairs), but the check is symmetric and cheap. - Informational + a continue prompt; it doesn't block.
Reset / cleanup tool (new: retrosync-reset.sh / retrosync-reset.ps1)
A standalone maintenance utility that talks to the NAS Syncthing over REST:
- Show all RetroSync folders on the NAS
- Remove a user (all their folders at once)
- Remove specific folders
- Wipe every RetroSync folder for a fresh start
- Prune device pairings no longer used by any folder
It only ever removes Syncthing config entries whose folder id starts with retrosync- — it never deletes data files. Reads the NAS URL/key from an existing profile.json (plaintext key only) or prompts; supports --dry-run / -DryRun.
This fixes the "ghost user" problem: if you delete a user's directory on the NAS by hand, the Syncthing folder config stays behind and the user keeps appearing in the multi-user picker. Run the reset tool → Remove a user to clear the orphaned config.
# Linux
curl -LO https://raw.githubusercontent.com/The-HBA/Retrosync/main/retrosync-reset.sh
bash retrosync-reset.sh --dry-run # preview first# Windows
Invoke-WebRequest https://raw.githubusercontent.com/The-HBA/Retrosync/main/retrosync-reset.ps1 -OutFile retrosync-reset.ps1
.\retrosync-reset.ps1 -DryRunVersions
retrosync-setup.sh/.ps1→0.4.2- new:
retrosync-reset.sh/.ps1
Retrosync v0.4.1
Bugfix release. Three crash-class bugs in the v0.4.0 per-game picker, found during a pre-test code review before the picker had been run interactively. If you're on v0.4.0, upgrade before testing selective sync — it doesn't work on v0.4.0.
1. Bash picker crashed under set -e
The selected-count tally and the down-arrow handler used [[ cond ]] && (( x++ )). ((x++)) returns exit status 1 when x was 0, and as the final command in an && list that trips set -euo pipefail and aborts the script. The count crashed on the first render; the down-arrow crashed on the first press. Rewritten as explicit if + arithmetic assignment.
2. PowerShell selective sync was a no-op
Show-MultiSelect returned ,$result. Because every caller wraps the call in @(), the comma-wrapped array was seen as a single element (Count always 1) — so the whole game list was treated as one "game." Changed to a plain return $result.
3. Bash end-of-run summary crashed
print_summary used ${#ROMS_GAME_SELECTED[@]:-0}, an invalid "bad substitution" that aborted the summary on every run. Fixed.
No feature or prompt changes.
Versions
retrosync-setup.sh→0.4.1retrosync-setup.ps1→0.4.1
Retrosync v0.4.0
This release bundles the previously-unreleased v0.3.3 work with the new v0.4.0 selective-sync feature.
Selective per-game sync (new in v0.4.0)
You no longer have to mirror an entire console folder to every device. For large consoles (PS3, PS2, Switch, Wii, Wii U, Xbox, Xbox 360), the script now asks whether to sync all games or pick specific ones — handy when your PS3 folder is 80 GB but you only want a game or two on the Steam Deck.
Picking opens a terminal checkbox:
Select ps3 games to sync (the rest stay on the NAS only):
↑/↓ move · SPACE toggle · a=all · n=none · ENTER confirm
> [x] Minecraft
[ ] Toy Story 3
[x] Gran Turismo 6
2 of 47 selected
- No dependencies — pure terminal on Linux (works in Steam Deck Game Mode and over SSH), native console on Windows PowerShell 5.1. No
dialog/whiptail/fzfneeded. - Unchecked games stay on the NAS; they just don't download to this device. Implemented with standard Syncthing
.stignoreinclude rules — nothing custom on the protocol side. - Small consoles are never prompted. Non-interactive/piped runs select everything and never hang.
.stignorepatterns moved to a raw-lines channel, so ROM names with commas (e.g. "Zelda, The") no longer corrupt the ignore list.
Existing-user picker for multi-user setups
When you choose multi-user, the script reads the NAS Syncthing config and lists the users it already knows about:
Found 2 existing user(s) on the NAS:
[1] Windows (8 folder(s))
[2] linux (5 folder(s))
[3] Create new user
Choice [1]:
Pick a number to join an existing user — no retyping or miscapitalising the name. Reserved scope names (roms, bios, saves, …) are filtered from the list and rejected as usernames. A warning explains mixed single-user + multi-user layouts.
Setup-role moved earlier + better logic
The "first device vs adding to existing setup" question now runs right after the layout choice, and is inferred when possible:
- Joining an existing user → receive-only (pull NAS data down first)
- Creating a new user → two-way
- Single-user against a NAS that already has data → adding
You can always override.
Show current configuration
The re-run menu gained a read-only [1] Show current configuration option (frontend, NAS settings, API-key storage, sync ports, and every configured folder). The menu now defaults to [2] Update and clarifies the difference between [3] Start fresh (deletes the local profile only) and [4] Remove (cleans up Syncthing on both sides).
Versions
retrosync-setup.sh→0.4.0retrosync-setup.ps1→0.4.0
Retrosync v0.3.2
Fix SteamOS crash (missing hostname binary)
Fixes crash on Steam Deck (SteamOS Game Mode) and any other minimal Linux setup that doesn't ship the hostname binary.
Symptom
The script crashed mid-setup with:
retrosync-setup.sh: line 1424: hostname: command not found
...after the user had already entered the NAS address, API key, and API-key storage choice. The crash came from pair_devices(), which needed the device's hostname to register itself with the NAS Syncthing.
Cause
Stock SteamOS doesn't include the hostname package. The previous fallback (hostname -s 2>/dev/null || hostname) silenced stderr but still hit set -e because both branches returned exit 127 (command not found).
Fix
New get_hostname helper cascades through several mechanisms:
$HOSTNAME(set automatically by bash — covers Steam Deck out of the box)/etc/hostname(readable on most distros, even minimal ones)hostnamectl --static(systemd systems)hostname -s/hostname(the original assumption, still tried if available)- Literal
"device"(last resort — never crashes)
All three previous call sites now use get_hostname instead of calling hostname directly.
Versions
retrosync-setup.sh→0.3.2retrosync-setup.ps1→0.3.2(lockstep version bump only; PS uses[System.Environment]::MachineNamewhich is always available on Windows)
No changes to functionality, prompts, or profile schema.
Retrosync v0.3.1
What's new
New: [1] Show current configuration option
When you re-run the script and a profile already exists, the menu now starts with a read-only "show me what's set up" view. Lets you peek at your last config before deciding whether to update, start fresh, or remove the device.
Shows:
- Frontend (RetroBat / RetroDECK / custom locations) and base path
- Single-user / multi-user mode (and username if multi-user)
- NAS address, NAS base directory, API-key storage mode, sync ports
- Ignore-perms toggle, created/updated timestamps, profile file path
- Every configured Syncthing folder: name, direction (↔ / → / ←), versioning, local path, NAS path, and per-device ignore patterns
After viewing, returns to the menu instead of exiting.
Menu shifted by one, default is now [2] Update
[1] Show current configuration (new, read-only)
[2] Update (was [1])
[3] Start fresh (was [2])
[4] Remove this device from sync (was [3])
[5] Exit (was [4])
Clarified [3] Start fresh vs [4] Remove
The menu now explains the difference inline:
[3] Start fresh— deletes only the localprofile.json. Does NOT touch Syncthing on either this device or the NAS. The Syncthing folders and device pairings stay configured; the script will PATCH them on the next reconfigure.[4] Remove this device from sync— actively unshares from the NAS (or fully deletes folders if this is the last peer), removes the local Syncthing folder configs, and optionally deletes data on this device.
For a true clean slate: pick [4] Remove first, then re-run and reconfigure.
Versions
retrosync-setup.sh→0.3.1retrosync-setup.ps1→0.3.1
No README changes. No behavior changes outside the re-run menu.
Retrosync v0.3.0
Custom locations mode
The big one: a new frontend option [2] Custom locations that works with any frontend, any layout, or none at all.
Instead of auto-detecting RetroBat / RetroDECK paths, you enter the absolute path for each thing you want to sync:
- ROMs folder
- BIOS folder
- Save states folder
- ES-DE gamelists folder
- ES-DE downloaded media folder
- Per-emulator save folders (PCSX2, DuckStation, RPCS3, Dolphin GC/Wii, Cemu, Ryujinx/RyuBing, Azahar, Vita3K, melonDS, Flycast, xemu, MAME, Ruffle, PrimeHack, PPSSPP, RetroArch, …)
Leave any prompt blank to skip that entry.
What this unlocks
- EmuDeck on Linux or Windows: works today via custom mode (first-class auto-detection still planned).
- Standalone ES-DE: works today.
- Batocera, Lakka, RecalBox, plain RetroArch: work today.
- Non-standard installs: saves on a different drive, BIOS on a network share, ROMs on a NAS mount point custom mode at whatever paths you actually use.
How it shares with auto-detected modes
- ROMs and BIOS reuse the same Syncthing folder IDs as RetroBat/RetroDECK, so a custom-mode device can share those folders with a RetroBat or RetroDECK peer on the same NAS.
- States, gamelists, media use
custom-*folder IDs (different fromrb-*/rd-*) their on-disk layout is unknown, so the script doesn't try to cross-sync them with RetroBat/RetroDECK's incompatible structures. - Per-emulator saves reuse the same
saves/<console_id>NAS path convention, so format-compatible saves (PCSX2 memcards, DuckStation memcards, etc.) can still cross-sync between a custom device and an auto-detected one.
Other changes
- Updated README with the v0.3.0 frontend table, custom-mode flow notes, and the known re-run limitation (custom-mode re-runs re-apply saved paths rather than re-prompting; to add a new path, edit
profile.jsonor use[3] Removeand re-setup). - Both scripts bumped v0.3.0.
- Changed the project's license from MIT to GPL 3.0
Limitations
- Re-running the script in custom mode does not let you add a new emulator's save folder interactively. It reconstructs state from
profile.json. To add a path after first-run: edit profile.json directly, or use[3] Remove this device from syncand re-setup. - Custom mode auto-includes every scope you typed a path for no follow-up "Sync ROMs? y/n" prompt, since the act of typing the path already selected it.
Upgrade notes
Existing v0.2.x profiles continue to work unchanged. New custom-mode profiles save "frontend": "custom" with an empty frontend_base_path; folder entries hold absolute local paths.
Retrosync v0.2.1
Encryption on both OSes
Windows already had DPAPI as a built-in encryption option. Linux had libsecret, which only worked if secret-tool was installed and a keyring agent was running — meaning headless servers and Steam Deck Game Mode silently fell through to plaintext.
New Linux option: openssl-machine. AES-256-CBC encryption with a key derived from /etc/machine-id + your UID. Provides the same DPAPI-style guarantee ("decryptable only by this user on this machine") without needing a desktop session or any extra packages beyond openssl (which is essentially universal). The storage-mode menu now dynamically lists what's available and defaults to the strongest option:
How should the NAS Syncthing API key be stored?
[1] System keyring (libsecret) (recommended)
Encrypted by your desktop's keyring (GNOME Keyring,
KWallet, etc.). profile.json holds no key material.
Strongest option, but needs a desktop session.
[2] Encrypted with machine-bound key (openssl)
AES-256 encrypted using a key derived from your
machine-id and user UID. Decryptable only by you on
this machine. No desktop session needed; works on
headless boxes and Steam Deck Game Mode.
[3] Plaintext in profile.json
...
[4] Don't store - prompt on every run
...
If secret-tool isn't available, option [1] becomes openssl-machine and the libsecret entry is hidden. Either way you always get an encryption option as the default.
Retrosync v0.2.0
Let the Linux testing Begin!
The Retrosync project started at version 0.1.0 and kept testing the powershell script on two windows machines running two independent Retrobat instances, kept updating and fixing up until v0.1.9, confirmed that RetroBat <-> RetroBat syncing works, now in v0.2.0 up untill v0.2.9 will be about testing RetroDECK <-> RetroDECK syncing.
If all goes well v0.3.0 will be focus on RetroDECK <-> RetroBat syncing.