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,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.
|
||||
Reference in New Issue
Block a user