Files
SyncGames/docs/planning/syncgames_path_drafts_2b07b18f.plan.md
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

11 KiB
Raw Permalink Blame History

name, overview, todos, isProject
name overview todos isProject
SyncGames Path Drafts 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.
id content status
scaffold-repo Create SyncGames/ skeleton including docs/planning/ archive of all plan markdowns in_progress
id content status
archive-plans Copy Cursor plan + path drafts into SyncGames/docs/planning/ for posterity pending
id content status
nas-minio-cloudflare Document MinIO on NAS behind existing NGINX + Cloudflare Tunnel hostname, TLS, Access/auth, S3 path-style, nginx anti-grief settings pending
id content status
agent-core Implement Python CLI/daemon - lease, pull-live, push-live, list-history, restore, add/remove game pending
id content status
safety-gates Hash gate, atomic promote, version prune (N=3-5), refuse mid-write / mid-lease operations pending
id content status
linux-automation systemd user units + process/Steam AppID watchers for auto start/end session (WAN endpoint same as laptop) pending
id content status
android-ui Light Kotlin/Compose Android app - Start/End/History/Restore/Status talking MinIO over Cloudflare HTTPS pending
id content status
protocol-spec Written protocol doc so Android and Python stay in lockstep on meta.json, leases, keys pending
id content status
path1-fallback-notes Document Path 1 fallback trigger criteria and migration steps from MinIO SSOT tree pending
id content status
first-games Seed configs for DS1/2/3, Elden Ring Seamless, Eden/Yuzu platform profiles pending
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 Cursors 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)

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 games native save directory continuously

Path 3 architecture (WAN)

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)

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 12 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.