proton-home-sync 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proton_home_sync-0.1.1/LICENSE +21 -0
- proton_home_sync-0.1.1/PKG-INFO +248 -0
- proton_home_sync-0.1.1/README.md +216 -0
- proton_home_sync-0.1.1/proton_home_sync/__init__.py +8 -0
- proton_home_sync-0.1.1/proton_home_sync/__main__.py +10 -0
- proton_home_sync-0.1.1/proton_home_sync/cli.py +415 -0
- proton_home_sync-0.1.1/proton_home_sync/config.py +37 -0
- proton_home_sync-0.1.1/proton_home_sync/daemon.py +185 -0
- proton_home_sync-0.1.1/proton_home_sync/linker.py +27 -0
- proton_home_sync-0.1.1/proton_home_sync/merge.py +524 -0
- proton_home_sync-0.1.1/proton_home_sync/models.py +114 -0
- proton_home_sync-0.1.1/proton_home_sync/notifier.py +165 -0
- proton_home_sync-0.1.1/proton_home_sync/prefix.py +103 -0
- proton_home_sync-0.1.1/proton_home_sync/py.typed +0 -0
- proton_home_sync-0.1.1/proton_home_sync/steam.py +105 -0
- proton_home_sync-0.1.1/proton_home_sync/utils.py +16 -0
- proton_home_sync-0.1.1/proton_home_sync.egg-info/PKG-INFO +248 -0
- proton_home_sync-0.1.1/proton_home_sync.egg-info/SOURCES.txt +31 -0
- proton_home_sync-0.1.1/proton_home_sync.egg-info/dependency_links.txt +1 -0
- proton_home_sync-0.1.1/proton_home_sync.egg-info/entry_points.txt +2 -0
- proton_home_sync-0.1.1/proton_home_sync.egg-info/requires.txt +13 -0
- proton_home_sync-0.1.1/proton_home_sync.egg-info/top_level.txt +1 -0
- proton_home_sync-0.1.1/pyproject.toml +52 -0
- proton_home_sync-0.1.1/setup.cfg +4 -0
- proton_home_sync-0.1.1/tests/test_cli.py +397 -0
- proton_home_sync-0.1.1/tests/test_daemon.py +348 -0
- proton_home_sync-0.1.1/tests/test_linker.py +14 -0
- proton_home_sync-0.1.1/tests/test_merge.py +690 -0
- proton_home_sync-0.1.1/tests/test_models.py +31 -0
- proton_home_sync-0.1.1/tests/test_notifier.py +196 -0
- proton_home_sync-0.1.1/tests/test_prefix.py +149 -0
- proton_home_sync-0.1.1/tests/test_steam.py +125 -0
- proton_home_sync-0.1.1/tests/test_utils.py +29 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vality
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proton-home-sync
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Merge every Steam Proton prefix's home directory into one shared home and replace each prefix with a symlink, so backup tools see a single real directory of game saves
|
|
5
|
+
Author: vality
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/vality/proton-home-sync
|
|
8
|
+
Project-URL: Issues, https://github.com/vality/proton-home-sync/issues
|
|
9
|
+
Keywords: steam,proton,wine,saves,backup,symlink
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: System :: Filesystems
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: vdf>=3.4
|
|
22
|
+
Provides-Extra: daemon
|
|
23
|
+
Requires-Dist: watchfiles>=0.21; extra == "daemon"
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
26
|
+
Requires-Dist: ruff>=0.4; extra == "test"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
30
|
+
Requires-Dist: watchfiles>=0.21; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# proton-home-sync
|
|
34
|
+
|
|
35
|
+
Merge every Steam Proton prefix's home directory into a single shared
|
|
36
|
+
home, then replace each prefix's home with a symlink to it. Backup tools
|
|
37
|
+
that don't cooperate well with symlinks (most of them) can target one
|
|
38
|
+
real directory and get every game's saves.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
~/.steam/steam/steamapps/compatdata/
|
|
42
|
+
├── 11111/pfx/drive_c/users/steamuser ──► ~/proton-shared-home/steamuser/
|
|
43
|
+
├── 22222/pfx/drive_c/users/steamuser ──► ~/proton-shared-home/steamuser/
|
|
44
|
+
└── 44444/pfx/drive_c/users/steamuser ──► ~/proton-shared-home/steamuser/
|
|
45
|
+
└── (real directory — the only backup target)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The arrows go **prefix → shared home**. The shared home is one real
|
|
49
|
+
directory on disk. Once a prefix is linked, it stays linked — there is
|
|
50
|
+
no deletion tracking to worry about. When Steam creates a brand-new
|
|
51
|
+
prefix, the daemon picks it up, merges its initial Wine state into the
|
|
52
|
+
shared home, and turns the prefix's home into a symlink.
|
|
53
|
+
|
|
54
|
+
## Why this shape
|
|
55
|
+
|
|
56
|
+
* Most backup tools either de-duplicate symlinks to nothing or chase
|
|
57
|
+
pointers back into your Steam library. With a real shared home, the
|
|
58
|
+
tool walks one tree and sees real files.
|
|
59
|
+
* No deletion tracking. New prefixes just *add* their initial files to
|
|
60
|
+
the shared home; after the symlink flip they write straight into it.
|
|
61
|
+
* One backup target: `~/proton-shared-home/steamuser/` (or wherever you
|
|
62
|
+
point `--central-home`).
|
|
63
|
+
|
|
64
|
+
The cost is that all games now share one Windows user namespace, so two
|
|
65
|
+
games that use the same path under `%USERPROFILE%` would collide. In
|
|
66
|
+
practice most games namespace themselves via `%APPDATA%\<Studio>\<Game>\`,
|
|
67
|
+
`Documents/My Games/<Game>/`, or `Saved Games/<Game>/` and don't trip
|
|
68
|
+
on each other.
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pipx install . # from a cloned checkout
|
|
74
|
+
# or directly from GitHub:
|
|
75
|
+
pipx install git+https://github.com/vality/proton-home-sync
|
|
76
|
+
# or
|
|
77
|
+
pip install --user .
|
|
78
|
+
# plus, for daemon mode:
|
|
79
|
+
pip install 'proton-home-sync[daemon]'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Requires Python ≥ 3.9 and the `vdf` package (pulled in automatically).
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# First run — merges every existing prefix into the shared home and links them.
|
|
88
|
+
proton-home-sync sync
|
|
89
|
+
|
|
90
|
+
# Use a custom shared home location.
|
|
91
|
+
proton-home-sync sync --central-home ~/backups/proton-shared-home
|
|
92
|
+
|
|
93
|
+
# See what would happen.
|
|
94
|
+
proton-home-sync sync --dry-run
|
|
95
|
+
|
|
96
|
+
# Show every prefix and its link state (✓ linked, · real dir, ↗ manual symlink).
|
|
97
|
+
proton-home-sync list
|
|
98
|
+
|
|
99
|
+
# Health summary — central home size + per-prefix link state.
|
|
100
|
+
proton-home-sync status
|
|
101
|
+
|
|
102
|
+
# Tear down the shared home (does NOT unlink the prefixes).
|
|
103
|
+
proton-home-sync clean
|
|
104
|
+
|
|
105
|
+
# Long-running watcher — auto-migrates new prefixes as games install.
|
|
106
|
+
proton-home-sync daemon
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Conflict policy
|
|
110
|
+
|
|
111
|
+
When a prefix's home contains a file that's already in the shared home,
|
|
112
|
+
the default is `--on-conflict skip` — the shared copy wins, the
|
|
113
|
+
prefix's copy is left behind (and effectively discarded because the
|
|
114
|
+
prefix's home is then replaced with a symlink). Pass
|
|
115
|
+
`--on-conflict overwrite` to make the prefix's copy win instead; this
|
|
116
|
+
is useful when re-migrating after a manual rollback.
|
|
117
|
+
|
|
118
|
+
## Hooking up a backup tool
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# restic — encrypted, incremental
|
|
122
|
+
restic backup ~/proton-shared-home/steamuser
|
|
123
|
+
|
|
124
|
+
# borg — encrypted, deduplicated
|
|
125
|
+
borg create ~/backup::saves-$(date +%F) ~/proton-shared-home/steamuser
|
|
126
|
+
|
|
127
|
+
# rclone — to a cloud target
|
|
128
|
+
rclone sync ~/proton-shared-home/steamuser remote:proton-saves
|
|
129
|
+
|
|
130
|
+
# rsync — to a USB drive
|
|
131
|
+
rsync -a --delete ~/proton-shared-home/steamuser/ /mnt/usb/saves/
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
That's the entire backup story — one real directory, one command.
|
|
135
|
+
|
|
136
|
+
## Daemon mode
|
|
137
|
+
|
|
138
|
+
`proton-home-sync daemon` runs forever, watching each Steam library's
|
|
139
|
+
`compatdata/` for new prefixes. When one shows up (or Steam re-creates
|
|
140
|
+
an existing one), the daemon migrates it.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install 'proton-home-sync[daemon]'
|
|
144
|
+
proton-home-sync daemon
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Useful flags:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# React faster to filesystem events.
|
|
151
|
+
proton-home-sync daemon --debounce-ms 500
|
|
152
|
+
|
|
153
|
+
# Less frequent full rescans (default every 5 minutes).
|
|
154
|
+
proton-home-sync daemon --rescan-seconds 900
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The daemon logs to stderr. Run it under your service supervisor of
|
|
158
|
+
choice.
|
|
159
|
+
|
|
160
|
+
### systemd --user unit
|
|
161
|
+
|
|
162
|
+
```ini
|
|
163
|
+
# ~/.config/systemd/user/proton-home-sync.service
|
|
164
|
+
[Unit]
|
|
165
|
+
Description=Merge new Proton prefix home directories into the shared home
|
|
166
|
+
After=network-online.target
|
|
167
|
+
|
|
168
|
+
[Service]
|
|
169
|
+
Type=simple
|
|
170
|
+
ExecStart=/usr/bin/proton-home-sync daemon
|
|
171
|
+
Restart=on-failure
|
|
172
|
+
RestartSec=5
|
|
173
|
+
|
|
174
|
+
[Install]
|
|
175
|
+
WantedBy=default.target
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
systemctl --user enable --now proton-home-sync.service
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Configuration
|
|
183
|
+
|
|
184
|
+
Defaults live in `proton_home_sync/config.py`:
|
|
185
|
+
|
|
186
|
+
| Setting | Default | Override |
|
|
187
|
+
|-------------------|-----------------------------------------------|-----------------------------------|
|
|
188
|
+
| Steam roots | `~/.steam/steam`, Flatpak variants | `--steam-root` |
|
|
189
|
+
| Shared home | `~/proton-shared-home/steamuser` | `--central-home` |
|
|
190
|
+
| Wine user | `steamuser` | `--username` |
|
|
191
|
+
| Conflict policy | `skip` (shared home wins) | `--on-conflict overwrite` |
|
|
192
|
+
|
|
193
|
+
## Safety properties
|
|
194
|
+
|
|
195
|
+
* **Idempotent.** Re-running `sync` is a no-op once everything is linked.
|
|
196
|
+
* **Atomic flip.** When migrating, the prefix's real directory is
|
|
197
|
+
renamed aside first, the symlink is created, and only then is the
|
|
198
|
+
backup deleted. If anything fails midway, the prefix is restored.
|
|
199
|
+
* **Manual symlinks are respected.** A prefix whose home already
|
|
200
|
+
symlinks somewhere else is left alone with a warning.
|
|
201
|
+
* **`clean` is paranoid.** Refuses `~`, `/`, symlinks, or non-directories.
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
[](https://github.com/vality/proton-home-sync/actions/workflows/test.yml)
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
python -m venv .venv
|
|
209
|
+
source .venv/bin/activate
|
|
210
|
+
pip install -e '.[daemon,test]'
|
|
211
|
+
pytest
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The test suite builds a fake Steam install on disk (see
|
|
215
|
+
`tests/conftest.py`) — two libraries, three valid prefixes plus one
|
|
216
|
+
broken one — and exercises every code path.
|
|
217
|
+
|
|
218
|
+
## Project layout
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
proton-home-sync/
|
|
222
|
+
├── pyproject.toml
|
|
223
|
+
├── README.md
|
|
224
|
+
├── proton_home_sync/
|
|
225
|
+
│ ├── __init__.py
|
|
226
|
+
│ ├── __main__.py
|
|
227
|
+
│ ├── cli.py # argparse + subcommand dispatch
|
|
228
|
+
│ ├── config.py # default paths and tunables
|
|
229
|
+
│ ├── daemon.py # long-running watcher (uses watchfiles)
|
|
230
|
+
│ ├── merge.py # merge-then-symlink migration logic
|
|
231
|
+
│ ├── models.py # ProtonPrefix, MigrationResult, MigrationReport
|
|
232
|
+
│ ├── prefix.py # scan libraries for prefixes
|
|
233
|
+
│ ├── steam.py # locate Steam root + libraryfolders.vdf parser
|
|
234
|
+
│ ├── utils.py # human_path, helpers
|
|
235
|
+
│ └── linker.py # deprecation shim → re-exports merge.py
|
|
236
|
+
└── tests/
|
|
237
|
+
├── conftest.py # fake_steam fixture (two libraries, 3 valid prefixes)
|
|
238
|
+
├── test_steam.py
|
|
239
|
+
├── test_prefix.py
|
|
240
|
+
├── test_merge.py
|
|
241
|
+
├── test_linker.py # deprecation marker
|
|
242
|
+
├── test_cli.py
|
|
243
|
+
└── test_daemon.py
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT.
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# proton-home-sync
|
|
2
|
+
|
|
3
|
+
Merge every Steam Proton prefix's home directory into a single shared
|
|
4
|
+
home, then replace each prefix's home with a symlink to it. Backup tools
|
|
5
|
+
that don't cooperate well with symlinks (most of them) can target one
|
|
6
|
+
real directory and get every game's saves.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
~/.steam/steam/steamapps/compatdata/
|
|
10
|
+
├── 11111/pfx/drive_c/users/steamuser ──► ~/proton-shared-home/steamuser/
|
|
11
|
+
├── 22222/pfx/drive_c/users/steamuser ──► ~/proton-shared-home/steamuser/
|
|
12
|
+
└── 44444/pfx/drive_c/users/steamuser ──► ~/proton-shared-home/steamuser/
|
|
13
|
+
└── (real directory — the only backup target)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The arrows go **prefix → shared home**. The shared home is one real
|
|
17
|
+
directory on disk. Once a prefix is linked, it stays linked — there is
|
|
18
|
+
no deletion tracking to worry about. When Steam creates a brand-new
|
|
19
|
+
prefix, the daemon picks it up, merges its initial Wine state into the
|
|
20
|
+
shared home, and turns the prefix's home into a symlink.
|
|
21
|
+
|
|
22
|
+
## Why this shape
|
|
23
|
+
|
|
24
|
+
* Most backup tools either de-duplicate symlinks to nothing or chase
|
|
25
|
+
pointers back into your Steam library. With a real shared home, the
|
|
26
|
+
tool walks one tree and sees real files.
|
|
27
|
+
* No deletion tracking. New prefixes just *add* their initial files to
|
|
28
|
+
the shared home; after the symlink flip they write straight into it.
|
|
29
|
+
* One backup target: `~/proton-shared-home/steamuser/` (or wherever you
|
|
30
|
+
point `--central-home`).
|
|
31
|
+
|
|
32
|
+
The cost is that all games now share one Windows user namespace, so two
|
|
33
|
+
games that use the same path under `%USERPROFILE%` would collide. In
|
|
34
|
+
practice most games namespace themselves via `%APPDATA%\<Studio>\<Game>\`,
|
|
35
|
+
`Documents/My Games/<Game>/`, or `Saved Games/<Game>/` and don't trip
|
|
36
|
+
on each other.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pipx install . # from a cloned checkout
|
|
42
|
+
# or directly from GitHub:
|
|
43
|
+
pipx install git+https://github.com/vality/proton-home-sync
|
|
44
|
+
# or
|
|
45
|
+
pip install --user .
|
|
46
|
+
# plus, for daemon mode:
|
|
47
|
+
pip install 'proton-home-sync[daemon]'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python ≥ 3.9 and the `vdf` package (pulled in automatically).
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# First run — merges every existing prefix into the shared home and links them.
|
|
56
|
+
proton-home-sync sync
|
|
57
|
+
|
|
58
|
+
# Use a custom shared home location.
|
|
59
|
+
proton-home-sync sync --central-home ~/backups/proton-shared-home
|
|
60
|
+
|
|
61
|
+
# See what would happen.
|
|
62
|
+
proton-home-sync sync --dry-run
|
|
63
|
+
|
|
64
|
+
# Show every prefix and its link state (✓ linked, · real dir, ↗ manual symlink).
|
|
65
|
+
proton-home-sync list
|
|
66
|
+
|
|
67
|
+
# Health summary — central home size + per-prefix link state.
|
|
68
|
+
proton-home-sync status
|
|
69
|
+
|
|
70
|
+
# Tear down the shared home (does NOT unlink the prefixes).
|
|
71
|
+
proton-home-sync clean
|
|
72
|
+
|
|
73
|
+
# Long-running watcher — auto-migrates new prefixes as games install.
|
|
74
|
+
proton-home-sync daemon
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Conflict policy
|
|
78
|
+
|
|
79
|
+
When a prefix's home contains a file that's already in the shared home,
|
|
80
|
+
the default is `--on-conflict skip` — the shared copy wins, the
|
|
81
|
+
prefix's copy is left behind (and effectively discarded because the
|
|
82
|
+
prefix's home is then replaced with a symlink). Pass
|
|
83
|
+
`--on-conflict overwrite` to make the prefix's copy win instead; this
|
|
84
|
+
is useful when re-migrating after a manual rollback.
|
|
85
|
+
|
|
86
|
+
## Hooking up a backup tool
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# restic — encrypted, incremental
|
|
90
|
+
restic backup ~/proton-shared-home/steamuser
|
|
91
|
+
|
|
92
|
+
# borg — encrypted, deduplicated
|
|
93
|
+
borg create ~/backup::saves-$(date +%F) ~/proton-shared-home/steamuser
|
|
94
|
+
|
|
95
|
+
# rclone — to a cloud target
|
|
96
|
+
rclone sync ~/proton-shared-home/steamuser remote:proton-saves
|
|
97
|
+
|
|
98
|
+
# rsync — to a USB drive
|
|
99
|
+
rsync -a --delete ~/proton-shared-home/steamuser/ /mnt/usb/saves/
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
That's the entire backup story — one real directory, one command.
|
|
103
|
+
|
|
104
|
+
## Daemon mode
|
|
105
|
+
|
|
106
|
+
`proton-home-sync daemon` runs forever, watching each Steam library's
|
|
107
|
+
`compatdata/` for new prefixes. When one shows up (or Steam re-creates
|
|
108
|
+
an existing one), the daemon migrates it.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pip install 'proton-home-sync[daemon]'
|
|
112
|
+
proton-home-sync daemon
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Useful flags:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# React faster to filesystem events.
|
|
119
|
+
proton-home-sync daemon --debounce-ms 500
|
|
120
|
+
|
|
121
|
+
# Less frequent full rescans (default every 5 minutes).
|
|
122
|
+
proton-home-sync daemon --rescan-seconds 900
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The daemon logs to stderr. Run it under your service supervisor of
|
|
126
|
+
choice.
|
|
127
|
+
|
|
128
|
+
### systemd --user unit
|
|
129
|
+
|
|
130
|
+
```ini
|
|
131
|
+
# ~/.config/systemd/user/proton-home-sync.service
|
|
132
|
+
[Unit]
|
|
133
|
+
Description=Merge new Proton prefix home directories into the shared home
|
|
134
|
+
After=network-online.target
|
|
135
|
+
|
|
136
|
+
[Service]
|
|
137
|
+
Type=simple
|
|
138
|
+
ExecStart=/usr/bin/proton-home-sync daemon
|
|
139
|
+
Restart=on-failure
|
|
140
|
+
RestartSec=5
|
|
141
|
+
|
|
142
|
+
[Install]
|
|
143
|
+
WantedBy=default.target
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
systemctl --user enable --now proton-home-sync.service
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
Defaults live in `proton_home_sync/config.py`:
|
|
153
|
+
|
|
154
|
+
| Setting | Default | Override |
|
|
155
|
+
|-------------------|-----------------------------------------------|-----------------------------------|
|
|
156
|
+
| Steam roots | `~/.steam/steam`, Flatpak variants | `--steam-root` |
|
|
157
|
+
| Shared home | `~/proton-shared-home/steamuser` | `--central-home` |
|
|
158
|
+
| Wine user | `steamuser` | `--username` |
|
|
159
|
+
| Conflict policy | `skip` (shared home wins) | `--on-conflict overwrite` |
|
|
160
|
+
|
|
161
|
+
## Safety properties
|
|
162
|
+
|
|
163
|
+
* **Idempotent.** Re-running `sync` is a no-op once everything is linked.
|
|
164
|
+
* **Atomic flip.** When migrating, the prefix's real directory is
|
|
165
|
+
renamed aside first, the symlink is created, and only then is the
|
|
166
|
+
backup deleted. If anything fails midway, the prefix is restored.
|
|
167
|
+
* **Manual symlinks are respected.** A prefix whose home already
|
|
168
|
+
symlinks somewhere else is left alone with a warning.
|
|
169
|
+
* **`clean` is paranoid.** Refuses `~`, `/`, symlinks, or non-directories.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
[](https://github.com/vality/proton-home-sync/actions/workflows/test.yml)
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
python -m venv .venv
|
|
177
|
+
source .venv/bin/activate
|
|
178
|
+
pip install -e '.[daemon,test]'
|
|
179
|
+
pytest
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The test suite builds a fake Steam install on disk (see
|
|
183
|
+
`tests/conftest.py`) — two libraries, three valid prefixes plus one
|
|
184
|
+
broken one — and exercises every code path.
|
|
185
|
+
|
|
186
|
+
## Project layout
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
proton-home-sync/
|
|
190
|
+
├── pyproject.toml
|
|
191
|
+
├── README.md
|
|
192
|
+
├── proton_home_sync/
|
|
193
|
+
│ ├── __init__.py
|
|
194
|
+
│ ├── __main__.py
|
|
195
|
+
│ ├── cli.py # argparse + subcommand dispatch
|
|
196
|
+
│ ├── config.py # default paths and tunables
|
|
197
|
+
│ ├── daemon.py # long-running watcher (uses watchfiles)
|
|
198
|
+
│ ├── merge.py # merge-then-symlink migration logic
|
|
199
|
+
│ ├── models.py # ProtonPrefix, MigrationResult, MigrationReport
|
|
200
|
+
│ ├── prefix.py # scan libraries for prefixes
|
|
201
|
+
│ ├── steam.py # locate Steam root + libraryfolders.vdf parser
|
|
202
|
+
│ ├── utils.py # human_path, helpers
|
|
203
|
+
│ └── linker.py # deprecation shim → re-exports merge.py
|
|
204
|
+
└── tests/
|
|
205
|
+
├── conftest.py # fake_steam fixture (two libraries, 3 valid prefixes)
|
|
206
|
+
├── test_steam.py
|
|
207
|
+
├── test_prefix.py
|
|
208
|
+
├── test_merge.py
|
|
209
|
+
├── test_linker.py # deprecation marker
|
|
210
|
+
├── test_cli.py
|
|
211
|
+
└── test_daemon.py
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT.
|