Initial SyncGames tree: agent, Android, deploy, docs.
Session-gated MinIO save sync with AppImage GUI, CLI edit/session flow, and Gitea release helper. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# SyncGames planning archive
|
||||
|
||||
Frozen planning artifacts for posterity. Living operational docs live one level up in `docs/`.
|
||||
|
||||
| File | Contents |
|
||||
|------|----------|
|
||||
| [01-path-drafts.md](01-path-drafts.md) | Original three-path comparison (Syncthing / Gitea / MinIO) |
|
||||
| [02-path3-implementation.md](02-path3-implementation.md) | Canonical Path 3 implementation plan (human-readable) |
|
||||
| [syncgames_path_drafts_2b07b18f.plan.md](syncgames_path_drafts_2b07b18f.plan.md) | Cursor plan export (primary) |
|
||||
| [syncgames_path_drafts_efddc87b.plan.md](syncgames_path_drafts_efddc87b.plan.md) | Earlier Cursor plan snapshot |
|
||||
|
||||
**Chosen path:** Path 3 (MinIO + session agents). Path 1 (hardened Syncthing) is the named fallback.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Path drafts — three architectures
|
||||
|
||||
Original comparison before Path 3 was selected.
|
||||
|
||||
## Non-negotiable session model
|
||||
|
||||
Every viable design shares the same state machine. Continuous bidirectional sync of **live** save directories is the antipattern that caused prior LOPEs.
|
||||
|
||||
- Pull only at session start; push only at session end
|
||||
- One lease per game
|
||||
- Hash gate rejects stale WIP unless explicit force-restore
|
||||
- Per-device history (3–5 versions); single live SSOT slot
|
||||
- Never delete live without first moving current into history
|
||||
|
||||
### Conceptual layout
|
||||
|
||||
```
|
||||
games/<game-id>/
|
||||
live/ # SSOT current slot
|
||||
history/<device>/ # last N snapshots per device
|
||||
meta.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Path 1 — Hardened Syncthing + Session Orchestrator
|
||||
|
||||
Syncthing syncs only the **SSOT tree**, never native game save dirs. A local agent copies native ↔ WIP ↔ SSOT around sessions.
|
||||
|
||||
**Pros:** Fast LAN; mature Android Syncthing client.
|
||||
**Cons:** Discipline required; peer conflict resolution if lease fails.
|
||||
**Status:** Named **fallback** if MinIO/WAN path becomes blocking.
|
||||
|
||||
---
|
||||
|
||||
## Path 2 — Custom Agent + Gitea (Git LFS)
|
||||
|
||||
Gitea as SSOT; saves as LFS or release assets; agent pull/push around sessions.
|
||||
|
||||
**Pros:** Auditable history in web UI.
|
||||
**Cons:** Git/LFS friction for binaries.
|
||||
**Status:** Shelved.
|
||||
|
||||
---
|
||||
|
||||
## Path 3 — Custom Agent + MinIO (selected)
|
||||
|
||||
SSOT is versioned object storage on NAS MinIO. Agent implements pull-live / push-live / history / restore. Exposed via Cloudflare → NGINX → MinIO for WAN.
|
||||
|
||||
**Pros:** Cleanest binary SSOT model; no Git merge footguns; easy prune.
|
||||
**Cons:** Own the agent end-to-end; need reachable HTTPS API.
|
||||
**Status:** **Building this.**
|
||||
|
||||
### Comparison snapshot
|
||||
|
||||
| Tenet | Path 1 | Path 2 | Path 3 |
|
||||
|-------|--------|--------|--------|
|
||||
| Safety / direction | High if SSOT-only | High | Highest |
|
||||
| Automation | Excellent Linux | Good | Excellent |
|
||||
| Easy add/remove | CLI + Syncthing API | CLI + repo | CLI + key prefixes |
|
||||
| LOPE recovery | history + .stversions | git/local history | object history |
|
||||
| Android | Syncthing-Fork | Custom app | Custom Compose app |
|
||||
| Past LOPE risk | Medium if live dirs synced | Low | Low |
|
||||
@@ -0,0 +1,44 @@
|
||||
# Path 3 implementation plan (canonical copy)
|
||||
|
||||
This is the human-readable archive of the Path 3 plan. The Cursor export snapshot lives beside this file.
|
||||
|
||||
## Locked decisions
|
||||
|
||||
| Decision | Choice |
|
||||
|----------|--------|
|
||||
| Primary | Path 3 — MinIO SSOT + session agents |
|
||||
| Linux agent | Python CLI/daemon |
|
||||
| Android | Kotlin + Jetpack Compose light UI |
|
||||
| Remote access | WAN via Cloudflare (paid domain); no VPN |
|
||||
| Ingress | Cloudflare → Tunnel → NGINX → MinIO |
|
||||
| Fallback | Path 1 (Syncthing on SSOT tree only) |
|
||||
| Planning | `docs/planning/` |
|
||||
|
||||
## Session ops
|
||||
|
||||
1. `start_session`: acquire lease → download `live/` → install to native paths → record WIP parent hash
|
||||
2. `end_session`: ensure game idle → copy native → WIP → upload history → promote `live/` → update meta → release lease → prune
|
||||
3. `restore`: copy chosen history prefix → `live/` (explicit)
|
||||
4. `add` / `remove`: TOML + bucket prefixes / archive to `retired/`
|
||||
|
||||
## Object keys
|
||||
|
||||
```
|
||||
games/<id>/live/<relative-save-path>
|
||||
games/<id>/history/<device>/<iso-ts>/<relative-save-path>
|
||||
games/<id>/meta.json
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
```
|
||||
syncgames add|remove|start|end|status|history|restore|doctor
|
||||
```
|
||||
|
||||
## NGINX note
|
||||
|
||||
Treat MinIO as an S3 API vhost: preserve Host for SigV4, disable request buffering, large `client_max_body_size`, no `auth_basic` on S3 (use Cloudflare Access + MinIO keys). See `docs/nas-minio-cloudflare.md`.
|
||||
|
||||
## Out of scope (v1)
|
||||
|
||||
Path 2 Gitea, Windows-native agent, automatic binary merge, continuous sync of live game dirs.
|
||||
@@ -0,0 +1,277 @@
|
||||
---
|
||||
name: SyncGames Path Drafts
|
||||
overview: Build SyncGames on Path 3 (NAS MinIO + Python agent on Linux, light Android UI app). WAN access via Cloudflare Tunnel to paid domain. Path 1 is fallback. Planning markdown archived in-repo for posterity.
|
||||
todos:
|
||||
- id: scaffold-repo
|
||||
content: Create SyncGames/ skeleton including docs/planning/ archive of all plan markdowns
|
||||
status: in_progress
|
||||
- id: archive-plans
|
||||
content: Copy Cursor plan + path drafts into SyncGames/docs/planning/ for posterity
|
||||
status: pending
|
||||
- id: nas-minio-cloudflare
|
||||
content: Document MinIO on NAS behind existing NGINX + Cloudflare Tunnel hostname, TLS, Access/auth, S3 path-style, nginx anti-grief settings
|
||||
status: pending
|
||||
- id: agent-core
|
||||
content: Implement Python CLI/daemon - lease, pull-live, push-live, list-history, restore, add/remove game
|
||||
status: pending
|
||||
- id: safety-gates
|
||||
content: Hash gate, atomic promote, version prune (N=3-5), refuse mid-write / mid-lease operations
|
||||
status: pending
|
||||
- id: linux-automation
|
||||
content: systemd user units + process/Steam AppID watchers for auto start/end session (WAN endpoint same as laptop)
|
||||
status: pending
|
||||
- id: android-ui
|
||||
content: Light Kotlin/Compose Android app - Start/End/History/Restore/Status talking MinIO over Cloudflare HTTPS
|
||||
status: pending
|
||||
- id: protocol-spec
|
||||
content: Written protocol doc so Android and Python stay in lockstep on meta.json, leases, keys
|
||||
status: pending
|
||||
- id: path1-fallback-notes
|
||||
content: Document Path 1 fallback trigger criteria and migration steps from MinIO SSOT tree
|
||||
status: pending
|
||||
- id: first-games
|
||||
content: Seed configs for DS1/2/3, Elden Ring Seamless, Eden/Yuzu platform profiles
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# SyncGames — Path 3 Implementation Plan
|
||||
|
||||
## Locked decisions
|
||||
|
||||
| Decision | Choice |
|
||||
|----------|--------|
|
||||
| Primary architecture | **Path 3** — MinIO SSOT on NAS + session agents |
|
||||
| Linux agent | **Python** CLI/daemon |
|
||||
| Android client | **Light UI app** (Kotlin + Jetpack Compose) — not Termux as primary |
|
||||
| Remote access | **WAN via Cloudflare** (paid domain) — not LAN-only, not VPN |
|
||||
| Fallback | **Path 1** — Hardened Syncthing on SSOT tree only |
|
||||
| Planning artifacts | **Kept in-repo** under `SyncGames/docs/planning/` |
|
||||
|
||||
Path 2 (Gitea) remains shelved unless explicitly reopened.
|
||||
|
||||
---
|
||||
|
||||
## Planning docs for posterity
|
||||
|
||||
On scaffold, archive all planning markdown into the repo so it survives outside Cursor’s plan UI:
|
||||
|
||||
```
|
||||
SyncGames/docs/planning/
|
||||
00-README.md # index of planning docs
|
||||
01-path-drafts.md # original 3-path comparison
|
||||
02-path3-implementation.md # this implementation plan (canonical copy)
|
||||
syncgames_path_drafts_2b07b18f.plan.md # Cursor plan export snapshot
|
||||
```
|
||||
|
||||
Update the archive whenever the plan materializes major revisions. Operational docs (`architecture.md`, `nas-minio.md`, etc.) stay separate under `docs/` as living guides.
|
||||
|
||||
---
|
||||
|
||||
## Session model (unchanged)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Idle
|
||||
Idle --> Pulling: start_session
|
||||
Pulling --> Ready: SSOT_copied_to_WIP
|
||||
Ready --> Playing: game_running
|
||||
Playing --> Pushing: end_session
|
||||
Pushing --> Idle: SSOT_updated_and_versioned
|
||||
```
|
||||
|
||||
- **Pull** only at session start; **push** only at session end
|
||||
- One **lease** per game; second device blocked until release/expiry
|
||||
- **Hash gate**: push rejected if WIP parent ≠ current live unless force-restore
|
||||
- Per-device **history** keeps last N snapshots (default 5); live is a single slot
|
||||
- Never sync the game’s native save directory continuously
|
||||
|
||||
---
|
||||
|
||||
## Path 3 architecture (WAN)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph clients [Clients_on_WAN]
|
||||
PC[Linux_PC_Python]
|
||||
Laptop[Laptop_Python]
|
||||
Phone[Android_Compose_app]
|
||||
end
|
||||
subgraph cf [Cloudflare]
|
||||
DNS[Paid_domain_DNS]
|
||||
Tunnel[Cloudflare_Tunnel]
|
||||
Access[Optional_CF_Access]
|
||||
end
|
||||
subgraph nas [NAS]
|
||||
Nginx[NGINX_reverse_proxy]
|
||||
MinIO[MinIO_S3_API]
|
||||
end
|
||||
PC --> DNS
|
||||
Laptop --> DNS
|
||||
Phone --> DNS
|
||||
DNS --> Access
|
||||
Access --> Tunnel
|
||||
Tunnel --> Nginx
|
||||
Nginx --> MinIO
|
||||
```
|
||||
|
||||
All devices (desktop, laptop, phone) use the **same public HTTPS endpoint**, e.g. `https://syncgames-s3.example.com`. No LAN assumption, no VPN requirement.
|
||||
|
||||
**Ingress chain (matches your NAS pattern):** Cloudflare → Tunnel → **existing NGINX** → MinIO. NGINX stays the single reverse-proxy front door for all services; we do not bypass it.
|
||||
|
||||
### NGINX + MinIO — will it cause grief?
|
||||
|
||||
**Verdict:** No, if we treat MinIO as an S3 API vhost (not a generic web app) and apply the known proxy checklist. Most LOPE-adjacent failures from reverse proxies are **truncated uploads** or **broken SigV4 signatures** — both avoidable.
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| SigV4 `Host` mismatch | Public hostname must be what clients sign; NGINX must forward that Host (or MinIO must be configured for the same domain). Do not rewrite Host to `127.0.0.1`. |
|
||||
| Truncated / buffered PUTs | `proxy_request_buffering off`; `client_max_body_size` large enough (e.g. 512m+); `proxy_http_version 1.1` |
|
||||
| Expect: 100-continue quirks | `proxy_set_header Expect $http_expect;` or strip carefully per MinIO docs — test with a real multipart upload |
|
||||
| Underscore / odd S3 headers | `ignore_invalid_headers off` on the MinIO server block |
|
||||
| Chunked encoding / redirect loops | Prefer path-style addressing to one API hostname; avoid redirecting API → console |
|
||||
| Console vs API confusion | Expose **S3 API only** on `syncgames-s3.<domain>`; keep MinIO Console on a separate optional vhost or LAN-only |
|
||||
| Cloudflare body limits | Same as before — game saves are usually fine; document if huge dumps fail |
|
||||
| Double TLS / WebSockets | Console needs Upgrade headers; **API path used by SyncGames does not need WebSockets** |
|
||||
|
||||
**Recommended NGINX shape (to ship in `docs/nas-minio-cloudflare.md`):** dedicated `server_name syncgames-s3.<domain>`; `location /` → `http://127.0.0.1:9000` (MinIO API); forwarding `Authorization`, `X-Amz-*`, `Content-Type`, `Content-Length`, `X-Forwarded-Proto https`; no `proxy_buffering` on uploads; no auth_basic in front of S3 (breaks SDK) — use Cloudflare Access and/or MinIO keys instead.
|
||||
|
||||
Agents point at `https://syncgames-s3.<domain>` with path-style S3. `syncgames doctor` will include a PUT/GET round-trip probe to catch NGINX misconfig early.
|
||||
|
||||
### Cloudflare exposure (chosen approach)
|
||||
|
||||
1. Run **MinIO** on NAS private bind (e.g. localhost:9000)
|
||||
2. Add **NGINX** vhost for the SyncGames S3 hostname (same pattern as your other services)
|
||||
3. Point **cloudflared** at NGINX (or at the existing tunnel ingress that already hits NGINX), not directly at MinIO, so all services stay consistent
|
||||
4. Map `syncgames-s3.<domain>` on the paid domain
|
||||
5. Prefer **Cloudflare Access** in front (defense-in-depth); MinIO access key / secret still required
|
||||
6. Document path-style addressing for boto3 + Android AWS SDK
|
||||
7. Note CF upload size limits; fallback guidance if an emulator dump exceeds them
|
||||
|
||||
Laptop and phone never need to be on home LAN.
|
||||
|
||||
---
|
||||
|
||||
### Object key layout
|
||||
|
||||
```
|
||||
games/<game-id>/live/<relative-save-path>
|
||||
games/<game-id>/history/<device-id>/<iso-ts>/<relative-save-path>
|
||||
games/<game-id>/meta.json
|
||||
```
|
||||
|
||||
`meta.json` fields (minimum): schema version, live checksums, parent hash, lease holder device id, lease expires_at, versions_to_keep, updated_at.
|
||||
|
||||
### Ops
|
||||
|
||||
1. **start_session**: acquire lease → download `live/` → install to native save path(s) → record WIP parent hash locally
|
||||
2. **end_session**: wait until game not writing → copy native → WIP → upload history prefix → promote to `live/` → update `meta.json` → release lease → prune old history beyond N
|
||||
3. **restore**: user picks history object prefix → copies to `live/` (explicit only)
|
||||
4. **add / remove game**: declarative TOML + bucket prefix init / archive-to-`retired/`
|
||||
|
||||
### Shared protocol spec
|
||||
|
||||
`docs/protocol.md` is the source of truth for key layout, lease rules, and hash gates so **Python and Android stay compatible** without sharing a runtime.
|
||||
|
||||
### Agent CLI surface (Linux)
|
||||
|
||||
```bash
|
||||
syncgames add --name "..." --platform steam --paths "..." --versions 5
|
||||
syncgames remove <game-id>
|
||||
syncgames start <game-id>
|
||||
syncgames end <game-id>
|
||||
syncgames status [<game-id>]
|
||||
syncgames history <game-id>
|
||||
syncgames restore <game-id> --from <device>/<ts>
|
||||
syncgames doctor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repo skeleton
|
||||
|
||||
```
|
||||
SyncGames/
|
||||
README.md
|
||||
docs/
|
||||
planning/ # posterity: path drafts + plan snapshots
|
||||
architecture.md
|
||||
protocol.md
|
||||
add-game.md
|
||||
nas-minio-cloudflare.md
|
||||
fallback-path1.md
|
||||
config/
|
||||
devices.toml
|
||||
agent.toml # Cloudflare HTTPS endpoint, bucket, device-id
|
||||
games/*.toml
|
||||
templates/game.toml
|
||||
agent/ # Python package (Linux PC + laptop)
|
||||
android/ # Kotlin/Compose light UI app
|
||||
systemd/
|
||||
syncgames-agent.service
|
||||
[email protected]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Android light UI — yes, in scope for v1
|
||||
|
||||
**Answer:** Yes. Implement Android as a **light dedicated UI app**, not Termux-first.
|
||||
|
||||
**Stack:** Kotlin + Jetpack Compose + AWS S3 / MinIO-compatible SDK over HTTPS to the Cloudflare hostname.
|
||||
|
||||
**Screens (minimal):**
|
||||
|
||||
- Game list + lease/status badge
|
||||
- **Start session** (pull + lease + write into configured save dirs)
|
||||
- **End session** (push + history + release)
|
||||
- **History** + **Restore** (explicit confirm)
|
||||
- Settings: endpoint URL, keys (prefer Android Keystore), device id, game path pickers via Storage Access Framework
|
||||
|
||||
**Deployability:** sideload APK or simple GitHub/Gitea release; configure once with Cloudflare URL + credentials; play flow is button → play → button.
|
||||
|
||||
**Why not Termux as primary:** harder to hand to “just use it” and weaker UX for safeties (confirm restore, show lease holder). Termux may remain a documented escape hatch only.
|
||||
|
||||
Linux still owns `add`/`remove` game and systemd automation; Android focuses on session buttons + restore for emulator titles on phone.
|
||||
|
||||
---
|
||||
|
||||
## Linux automation
|
||||
|
||||
- systemd user service runs the Python agent
|
||||
- Per-game watcher: Steam AppID and/or process name from game TOML
|
||||
- `start` / `end` on process lifecycle
|
||||
- Endpoint in `agent.toml` is always the Cloudflare WAN URL (same as laptop/phone) for consistent behavior away from home
|
||||
|
||||
---
|
||||
|
||||
## Path 1 fallback triggers
|
||||
|
||||
- Cloudflare/MinIO WAN path too unreliable or blocked by CF limits
|
||||
- Android app file-access friction exceeds Syncthing-Fork convenience **and** you accept SSOT-dir-only Syncthing rules
|
||||
|
||||
**Migration:** export MinIO `live/` + `history/` to a folder tree; Syncthing that tree only; keep Python session agent for native ↔ SSOT copies.
|
||||
|
||||
---
|
||||
|
||||
## Seed game configs (after agents work)
|
||||
|
||||
- Dark Souls 1 / 2 / 3 — Steam / Proton + Seamless Coop paths
|
||||
- Elden Ring — Seamless Coop paths
|
||||
- Eden / Yuzu — Android + Linux emulator save profiles
|
||||
|
||||
---
|
||||
|
||||
## Out of scope for v1
|
||||
|
||||
- Path 2 Gitea
|
||||
- Windows-native agent
|
||||
- Automatic binary merge
|
||||
- Continuous sync of live game directories
|
||||
- VPN / LAN-only client modes as a requirement (WAN Cloudflare is the path)
|
||||
|
||||
---
|
||||
|
||||
## Paths 1–2 for posterity
|
||||
|
||||
Full Path 1 and Path 2 writeups are archived under `docs/planning/01-path-drafts.md` when the repo is scaffolded — not only in chat history.
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
name: SyncGames Path Drafts
|
||||
overview: Greenfield SyncGames in a new `SyncGames/` directory. Below are three deployable architectures that all enforce the same session model (pull SSOT → play WIP → push SSOT) while differing in transport/storage. Pick one path before implementation.
|
||||
todos: []
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# SyncGames — Three Architecture Paths
|
||||
|
||||
## Non-negotiable session model (all paths)
|
||||
|
||||
Every viable design shares the same state machine. Continuous bidirectional sync of **live** save directories is what caused your prior LOPE; none of these paths do that.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Idle
|
||||
Idle --> Pulling: start_session
|
||||
Pulling --> Ready: SSOT_copied_to_WIP
|
||||
Ready --> Playing: game_running
|
||||
Playing --> Pushing: end_session
|
||||
Pushing --> Idle: SSOT_updated_and_versioned
|
||||
|
||||
note right of Pulling
|
||||
Refuse start if another device
|
||||
holds lease or SSOT is dirty
|
||||
end note
|
||||
note right of Pushing
|
||||
Snapshot WIP into device history
|
||||
then promote to live SSOT slot
|
||||
end note
|
||||
```
|
||||
|
||||
**Shared layout (conceptual):**
|
||||
|
||||
```
|
||||
SyncGames/
|
||||
games/<game-id>/
|
||||
live/ # SSOT current slot (one winner)
|
||||
history/
|
||||
<device-id>/ # last N snapshots per device (default 5)
|
||||
meta.json # hash, mtime, device, session lease, schema
|
||||
config/
|
||||
games/*.toml # declarative game defs
|
||||
devices.toml
|
||||
```
|
||||
|
||||
**Shared guardrails:**
|
||||
|
||||
| Rule | Behavior |
|
||||
|------|----------|
|
||||
| Direction | Pull only at session start; push only at session end |
|
||||
| Lease | One active session per game; second device blocked until lease expires/release |
|
||||
| Hash gate | Reject push if local WIP hash != expected parent (stale WIP) unless explicit `--force-restore` |
|
||||
| Version retention | Keep N snapshots per device (configurable 3–5); prune oldest after successful new snapshot |
|
||||
| Never delete live | Promote via atomic rename/copy; old live moves into history first |
|
||||
| Game offline | Automation watches process exit / Steam AppID, never mid-write |
|
||||
|
||||
**Add-game workflow (identical UX across paths):**
|
||||
|
||||
```bash
|
||||
syncgames add \
|
||||
--name "Elden Ring (Seamless)" \
|
||||
--platform steam \
|
||||
--paths "~/.steam/.../SeamlessCoop/*.co2" \
|
||||
--versions 5
|
||||
```
|
||||
|
||||
**Remove-game:** archive `games/<id>/` to `retired/<id>-<date>/`, stop watchers, revoke folder/remote access.
|
||||
|
||||
---
|
||||
|
||||
## Path 1 — Hardened Syncthing + Session Orchestrator
|
||||
|
||||
**Idea:** Syncthing only syncs the **SSOT tree** (`live/` + `history/` + `meta`), never the game’s native save directory. A local agent copies between native save path ↔ WIP ↔ SSOT around sessions.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
NativeSave[Game_native_save_path]
|
||||
WIP[Local_WIP_staging]
|
||||
SSOT[SSOT_folder_Syncthing]
|
||||
OtherDev[Other_devices]
|
||||
|
||||
NativeSave -->|"end_session copy"| WIP
|
||||
WIP -->|"snapshot + promote"| SSOT
|
||||
SSOT <-->|"Syncthing send-receive on SSOT only"| OtherDev
|
||||
SSOT -->|"start_session copy"| WIP
|
||||
WIP -->|"start_session install"| NativeSave
|
||||
```
|
||||
|
||||
**Syncthing config hardening:**
|
||||
|
||||
- Folder type: normal send-receive **only** on `SyncGames/games/` (or per-game subfolders)
|
||||
- Enable **Simple File Versioning** (Keep Versions ≥ 5) as a second safety net behind custom history
|
||||
- `.stignore` excludes `*.tmp`, lock files, WIP staging outside the synced tree
|
||||
- Do **not** sync Proton/prefix save dirs directly
|
||||
- Optional always-on home server / NAS as Receive-favoring mirror with staggered versioning
|
||||
|
||||
**Automation:** Linux systemd user units + process watchers (Steam AppID / executable). Android: Syncthing-Fork for SSOT folder + Termux/foreground service or a thin companion app for “Start session / End session” when full auto is harder.
|
||||
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Fast LAN sync you already know | Must never point Syncthing at live saves (discipline + tooling) |
|
||||
| Mature Android client for files | Peer conflict resolution still exists if lease protocol fails |
|
||||
| Low custom infra | Orchestrator is still custom software |
|
||||
|
||||
**Best if:** You want maximum automation across Linux + Android with minimal self-hosted backend.
|
||||
|
||||
---
|
||||
|
||||
## Path 2 — Custom Agent + Gitea (Git LFS / raw releases)
|
||||
|
||||
**Idea:** Your Gitea instance is SSOT. Each game is a small repo (or one monorepo with per-game dirs). Saves live as LFS objects or release assets. Agent does pull/checkout → WIP, and commit+push / release upload on end.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Agent[syncgames_agent]
|
||||
WIP[Local_WIP]
|
||||
Gitea[Gitea_repo_LFS]
|
||||
Hist[Device_history_local]
|
||||
|
||||
Agent -->|"git pull / download live tag"| WIP
|
||||
WIP -->|"play"| Game[Game]
|
||||
Game --> WIP
|
||||
WIP -->|"snapshot"| Hist
|
||||
Agent -->|"commit live + push"| Gitea
|
||||
```
|
||||
|
||||
**Hardening vs naïve git:**
|
||||
|
||||
- Treat `live/` as a single tracked blob set; never merge binary conflicts — lease + parent-hash check abort the push
|
||||
- Store history as `history/<device>/<timestamp>/` commits **or** local-only tarballs with only `live/` pushed (cleaner; history survives offline)
|
||||
- Prefer **Git LFS** for binary saves; optional signed tags `live/<game-id>` as the authoritative pointer
|
||||
- Manual “Sync Now” buttons remain available; automation calls the same API
|
||||
|
||||
**Android:** Kotlin/Compose or Flutter thin client using Gitea HTTP API + LFS (avoid full git on phone if painful).
|
||||
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Explicit, auditable history in Gitea UI | Git/LFS friction for large/frequently-changing binaries |
|
||||
| Works over WAN without Syncthing mesh | Needs always-reachable Gitea |
|
||||
| Matches “button press = state change” mental model | Automated push still needs the same lease logic you fear forgetting |
|
||||
|
||||
**Best if:** You already run Gitea and want human-readable restore via web UI more than raw speed.
|
||||
|
||||
---
|
||||
|
||||
## Path 3 — Custom Agent + Object Store SSOT (MinIO / S3 / rclone remote)
|
||||
|
||||
**Idea:** Skip git entirely. SSOT is versioned object storage you control (self-hosted MinIO on your LAN/homelab, or any S3-compatible bucket). Agent implements `pull-live`, `push-live`, `list-history`, `restore` against object keys.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph devices [Devices]
|
||||
PC[Linux_PC_agent]
|
||||
Laptop[Laptop_agent]
|
||||
Phone[Android_agent]
|
||||
end
|
||||
subgraph ssot [SSOT_MinIO]
|
||||
LiveKey["games/er/live/*"]
|
||||
HistKeys["games/er/history/device/ts/*"]
|
||||
Meta["games/er/meta.json + lease"]
|
||||
end
|
||||
PC --> ssot
|
||||
Laptop --> ssot
|
||||
Phone --> ssot
|
||||
```
|
||||
|
||||
**Object layout:**
|
||||
|
||||
```
|
||||
games/<id>/live/<relative-save-path>
|
||||
games/<id>/history/<device>/<iso-ts>/<relative-save-path>
|
||||
games/<id>/meta.json # checksums, parent, lease holder, expires_at
|
||||
```
|
||||
|
||||
**Ops model:**
|
||||
|
||||
- Push: upload WIP → new history prefix → checksum → atomic update of `live/` + `meta.json` (lease required)
|
||||
- Pull: verify lease available → download `live/` → install into native path → mark WIP parent hash
|
||||
- Restore: copy chosen history prefix → `live/` (explicit user action)
|
||||
- Transport: MinIO SDK, or `rclone` wrapped so you can swap backends later
|
||||
|
||||
**Automation same as Path 1** (session hooks); storage is just S3 semantics instead of Syncthing/Git.
|
||||
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Cleanest model for binary SSOT + versioned keys | Needs MinIO (or cloud S3) reachable from phone |
|
||||
| No git binary-conflict footguns | You own the agent end-to-end |
|
||||
| Easy prune (delete old prefixes) | Slightly more DIY than Syncthing for LAN presence |
|
||||
|
||||
**Best if:** You want the strongest conceptual match to “one live slot + per-device history” without Syncthing’s merge semantics or Git’s binary awkwardness.
|
||||
|
||||
---
|
||||
|
||||
## Comparison against your tenets
|
||||
|
||||
| Tenet | Path 1 Syncthing+Agent | Path 2 Gitea+Agent | Path 3 MinIO+Agent |
|
||||
|------|------------------------|--------------------|--------------------|
|
||||
| Safety / controlled direction | High if SSOT-only synced | High (hash+lease) | Highest (explicit keys+lease) |
|
||||
| Automation | Excellent on Linux; good on Android | Good; WAN-friendly | Excellent; WAN-friendly |
|
||||
| Easy add/remove game | CLI generates folder + Syncthing API | CLI creates repo/paths | CLI creates key prefixes |
|
||||
| LOPE recovery | device history + `.stversions` | git history / local history | object history prefixes |
|
||||
| Android friction | Lowest (Syncthing exists) | Medium (custom app) | Medium (custom app / rclone) |
|
||||
| Your past LOPE risk | Medium — only if someone resyncs live dirs | Low | Low |
|
||||
|
||||
---
|
||||
|
||||
## Recommended default (if you want a pick)
|
||||
|
||||
**Path 3 (MinIO + session agent)** as the core SSOT design — it maps 1:1 to live slot + per-device history, avoids Syncthing merge and Git LFS pain, and still supports full automation via process hooks.
|
||||
|
||||
**Optional hybrid later:** Path 1’s Syncthing can mirror a MinIO bucket backup, or Path 3’s `history/` can be additionally restic-backed for offsite retention. That is additive hardening, not required for v1.
|
||||
|
||||
---
|
||||
|
||||
## Proposed SyncGames repo skeleton (whichever path)
|
||||
|
||||
```
|
||||
SyncGames/
|
||||
README.md
|
||||
docs/architecture.md
|
||||
docs/add-game.md
|
||||
config/games/ # declarative TOML per title
|
||||
config/devices.toml
|
||||
agent/ # Python or Go CLI + daemon
|
||||
templates/game.toml
|
||||
systemd/ # user units for watchers
|
||||
```
|
||||
|
||||
Initial titles in config templates: Dark Souls 1/2/3, Elden Ring (Seamless Coop paths), Eden/Yuzu NAND/save dirs as separate platform profiles.
|
||||
|
||||
---
|
||||
|
||||
## Decision needed before implementation
|
||||
|
||||
Reply with which path to build first (**1**, **2**, or **3**). Also confirm:
|
||||
|
||||
1. Is a always-on homelab box available for MinIO/Gitea/Syncthing hub?
|
||||
2. Primary phone OS for sync — Android only?
|
||||
3. Prefer agent language: **Python** (fast to ship) or **Go** (single static binary)?
|
||||
Reference in New Issue
Block a user