Files
SyncGames/docs/planning/syncgames_path_drafts_efddc87b.plan.md
T
DawnsorrowandCursor 0d6b0b2f80 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]>
2026-07-14 22:06:36 -05:00

9.3 KiB
Raw Blame History

name, overview, todos, isProject
name overview todos isProject
SyncGames Path Drafts 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.
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.

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 35); 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):

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 games native save directory. A local agent copies between native save path ↔ WIP ↔ SSOT around sessions.

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.

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.

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 Syncthings merge semantics or Gits 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

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 1s Syncthing can mirror a MinIO bucket backup, or Path 3s 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)?