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,103 @@
|
||||
# SyncGames protocol (Python ↔ Android)
|
||||
|
||||
Schema version: **1**
|
||||
|
||||
Both clients MUST implement these rules identically. Diverging behavior is a LOPE risk.
|
||||
|
||||
## Object key layout
|
||||
|
||||
Bucket: configured (default `syncgames`).
|
||||
|
||||
| Key | Purpose |
|
||||
|-----|---------|
|
||||
| `games/<game-id>/meta.json` | Lease, checksums, retention |
|
||||
| `games/<game-id>/live/<rel>` | Current SSOT save file(s) |
|
||||
| `games/<game-id>/history/<device-id>/<iso-ts>/<rel>` | Immutable snapshot |
|
||||
| `retired/<game-id>-<date>/…` | Soft-deleted games |
|
||||
|
||||
- `<game-id>`: lowercase slug, `[a-z0-9-]+`
|
||||
- `<device-id>`: stable per install (e.g. `pc-desk`, `phone-pixel`)
|
||||
- `<iso-ts>`: UTC `YYYYMMDDTHHMMSSZ`
|
||||
- `<rel>`: relative path using `/`, no `..` segments
|
||||
|
||||
## meta.json
|
||||
|
||||
```json
|
||||
{
|
||||
"schema": 1,
|
||||
"game_id": "elden-ring-seamless",
|
||||
"live_hash": "sha256:…",
|
||||
"file_checksums": {
|
||||
"ER0000.co2": "sha256:…"
|
||||
},
|
||||
"lease": {
|
||||
"holder": "pc-desk",
|
||||
"expires_at": "2026-07-14T03:00:00Z",
|
||||
"session_id": "uuid"
|
||||
},
|
||||
"versions_to_keep": 5,
|
||||
"updated_at": "2026-07-13T22:00:00Z",
|
||||
"updated_by": "pc-desk"
|
||||
}
|
||||
```
|
||||
|
||||
- `live_hash`: SHA-256 of the sorted concatenation of `path\\0hexdigest\\n` for every live object (canonical tree hash).
|
||||
- `lease` may be `null` when idle.
|
||||
- Clients MUST treat unknown JSON fields as forward-compatible (ignore).
|
||||
|
||||
## Lease rules
|
||||
|
||||
1. **Acquire** before mutating native installs from SSOT (start session).
|
||||
2. Acquire succeeds if `lease` is null/expired OR `holder` equals this device.
|
||||
3. Default TTL: **6 hours** (refreshable by same holder via start again).
|
||||
4. Another device MUST refuse start while lease is valid for a different holder.
|
||||
5. **Release** on successful end session (set `lease` to null). Crash: wait for expiry or operator clears via `doctor --break-lease` (destructive admin).
|
||||
|
||||
## start_session
|
||||
|
||||
1. Load `meta.json` (if missing, treat as empty live + null lease).
|
||||
2. Acquire lease (conditional overwrite: read-modify-write; if lost race, abort).
|
||||
3. Download all `games/<id>/live/*` objects.
|
||||
4. Install into configured native path(s) (create parents; overwrite existing).
|
||||
5. Persist local session state: `{ parent_live_hash, session_id, started_at }`.
|
||||
|
||||
## end_session
|
||||
|
||||
1. Require local session state for game.
|
||||
2. Refuse if configured process/AppID still running (Linux); Android warns if user confirms force.
|
||||
3. Compute WIP tree hash from native files.
|
||||
4. **Hash gate:** if `parent_live_hash != meta.live_hash` and not `force`, **abort** (stale WIP).
|
||||
5. Upload snapshot to `history/<device>/<ts>/…`.
|
||||
6. Upload/replace all `live/…` objects to match WIP (delete remote live keys no longer present).
|
||||
7. Update `meta.json`: new hashes, `lease=null`, `updated_by=device`.
|
||||
8. Prune: list `history/<device>/`; keep newest `versions_to_keep`; delete older prefixes.
|
||||
9. Clear local session state.
|
||||
|
||||
## restore
|
||||
|
||||
1. User selects `device/ts` history prefix explicitly.
|
||||
2. Acquire lease (or require idle + force).
|
||||
3. Copy history objects → `live/`.
|
||||
4. Update `meta.json` hashes; leave lease held by restoring device until they end or release.
|
||||
5. Never auto-restore.
|
||||
|
||||
## Checksum algorithm
|
||||
|
||||
- Per file: SHA-256 of raw bytes, encoded `sha256:<hex>`.
|
||||
- Tree hash: sort relative paths lexicographically (UTF-8), for each path append `path + "\\0" + hex_digest + "\\n"`, then SHA-256 that byte string, encoded `sha256:<hex>`.
|
||||
|
||||
## S3 addressing
|
||||
|
||||
- Path-style: `https://syncgames-s3.example.com/syncgames/games/...`
|
||||
- Region can be `us-east-1` dummy; path-style + custom endpoint required.
|
||||
- TLS required on WAN.
|
||||
|
||||
## Error codes (CLI / UI mapping)
|
||||
|
||||
| Condition | Message key |
|
||||
|-----------|-------------|
|
||||
| Lease held by other | `lease_held` |
|
||||
| Hash gate fail | `stale_wip` |
|
||||
| Network / S3 | `store_error` |
|
||||
| Game still running | `game_running` |
|
||||
| Missing config | `config_error` |
|
||||
Reference in New Issue
Block a user