|
1 | 1 | # RetroSaveManager |
2 | 2 |
|
3 | | -RetroSaveManager is a self-hosted, LAN-first save synchronization service with compatibility-focused API behavior for existing helper clients. |
| 3 | +RetroSaveManager is a self-hosted save sync manager for retro gaming setups. |
| 4 | + |
| 5 | +It is built for homelab users who want to run one Docker container, keep all save data on their own storage, and use compatible helper apps with MiSTer, RetroArch, and other supported systems. |
| 6 | + |
| 7 | +## What You Get |
| 8 | + |
| 9 | +- One runtime container |
| 10 | +- One web UI |
| 11 | +- One API for helper apps |
| 12 | +- One save root you can back up |
| 13 | +- Docker-first self-hosting |
| 14 | + |
| 15 | +## Quick Start |
| 16 | + |
| 17 | +This is the smallest useful Docker Compose setup. |
| 18 | + |
| 19 | +```yaml |
| 20 | +services: |
| 21 | + retrosavemanager: |
| 22 | + image: ghcr.io/joeblack2k/retrosavemanager:latest |
| 23 | + container_name: retrosavemanager |
| 24 | + restart: unless-stopped |
| 25 | + ports: |
| 26 | + - "80:80" |
| 27 | + environment: |
| 28 | + AUTH_MODE: disabled |
| 29 | + SAVE_ROOT: /saves |
| 30 | + STATE_ROOT: /config |
| 31 | + PORT: 80 |
| 32 | + volumes: |
| 33 | + - /srv/retrosavemanager/config:/config |
| 34 | + - /srv/retrosavemanager/saves:/saves |
| 35 | +``` |
4 | 36 |
|
5 | | -## Scope |
| 37 | +Start it: |
6 | 38 |
|
7 | | -- In scope: user web flows + helper API compatibility |
8 | | -- Out of scope: admin, billing, manager, forge parity |
9 | | -- Auth mode default: `AUTH_MODE=disabled` (trusted internal environment) |
| 39 | +```bash |
| 40 | +docker compose up -d |
| 41 | +``` |
10 | 42 |
|
11 | | -## Repository Structure |
| 43 | +Open it: |
12 | 44 |
|
13 | | -- `backend/` Go API (compat routes, save store, contracts, tests) |
14 | | -- `frontend/` Modular React + TypeScript web app |
15 | | -- `deploy/` Single-container Docker Compose + deployment scripts |
16 | | -- `contracts/` Route matrix and compatibility contract snapshots |
17 | | -- `scripts/` Contract verification, backup/restore wrappers, security gate |
18 | | -- `tests/` Evidence output folder for local verification runs |
| 45 | +```text |
| 46 | +http://YOUR-DOCKER-HOST-IP/ |
| 47 | +``` |
19 | 48 |
|
20 | | -## Release Notes |
| 49 | +## What Each Option Does |
21 | 50 |
|
22 | | -See [docs/RELEASE_NOTES.md](docs/RELEASE_NOTES.md). |
| 51 | +`image: ghcr.io/joeblack2k/retrosavemanager:latest` |
23 | 52 |
|
24 | | -## Save Data Layout |
| 53 | +Uses the published container image from GitHub Container Registry. |
25 | 54 |
|
26 | | -All save files live below a single backup-friendly root: |
| 55 | +`container_name: retrosavemanager` |
27 | 56 |
|
28 | | -- `SAVE_ROOT/<System Display>/<Game Title or Memory Card>/<save-id>/payload.*` |
29 | | -- `SAVE_ROOT/<System Display>/<Game Title or Memory Card>/<save-id>/metadata.json` |
| 57 | +Gives the container a fixed and easy-to-recognize name. |
30 | 58 |
|
31 | | -Back up one root folder to capture all saves. |
| 59 | +`restart: unless-stopped` |
32 | 60 |
|
33 | | -### Layout Migration |
| 61 | +Starts the container again after a reboot or Docker restart. |
34 | 62 |
|
35 | | -Migrate existing slug-based paths to the display layout: |
| 63 | +`ports:` |
36 | 64 |
|
37 | | -```bash |
38 | | -./scripts/migrate-save-layout.sh --dry-run |
39 | | -./scripts/migrate-save-layout.sh --manifest ./deploy/data/config/save-layout-manifest.json |
40 | | -``` |
| 65 | +Publishes the web UI and API to your Docker host. |
41 | 66 |
|
42 | | -Rollback with the same manifest: |
| 67 | +`- "80:80"` |
43 | 68 |
|
44 | | -```bash |
45 | | -./scripts/migrate-save-layout.sh --rollback --manifest ./deploy/data/config/save-layout-manifest.json |
46 | | -``` |
| 69 | +Maps host port `80` to container port `80`. |
47 | 70 |
|
48 | | -### Save Rescan And Noise Prune |
| 71 | +`AUTH_MODE: disabled` |
49 | 72 |
|
50 | | -Run a deep rescan to normalize console detection, clean title noise, and optionally remove unsupported/unknown save entries: |
| 73 | +Disables login requirements. This is the default and is intended for trusted internal networks. |
51 | 74 |
|
52 | | -```bash |
53 | | -./scripts/rescan-saves.sh --dry-run |
54 | | -./scripts/rescan-saves.sh --prune-unsupported=true |
55 | | -``` |
| 75 | +`SAVE_ROOT: /saves` |
56 | 76 |
|
57 | | -Docker-only deploys can run the built-in command directly: |
| 77 | +The container stores all managed save data under `/saves`. |
58 | 78 |
|
59 | | -```bash |
60 | | -docker compose exec app /usr/local/bin/retrosave-api rescan-saves --prune-unsupported=true |
61 | | -``` |
| 79 | +`STATE_ROOT: /config` |
62 | 80 |
|
63 | | -## Quick Start (Docker) |
| 81 | +The container stores app state, indexes, and internal metadata under `/config`. |
64 | 82 |
|
65 | | -1. Copy environment template: |
| 83 | +`PORT: 80` |
66 | 84 |
|
67 | | -```bash |
68 | | -cp deploy/.env.example deploy/.env |
| 85 | +Tells the app to listen on port `80` inside the container. |
| 86 | + |
| 87 | +`/srv/retrosavemanager/config:/config` |
| 88 | + |
| 89 | +Persistent config and app-state storage on your Docker host. |
| 90 | + |
| 91 | +`/srv/retrosavemanager/saves:/saves` |
| 92 | + |
| 93 | +Persistent save storage on your Docker host. This is the most important volume for backups. |
| 94 | + |
| 95 | +## Volume Mappings |
| 96 | + |
| 97 | +These are the two important host folders: |
| 98 | + |
| 99 | +- `/srv/retrosavemanager/config` |
| 100 | +- `/srv/retrosavemanager/saves` |
| 101 | + |
| 102 | +What they contain: |
| 103 | + |
| 104 | +- `config` |
| 105 | + - internal app state |
| 106 | + - save index metadata |
| 107 | + - helper-related state |
| 108 | + - settings used by the service |
| 109 | +- `saves` |
| 110 | + - the actual save files |
| 111 | + - per-system folders |
| 112 | + - per-game folders |
| 113 | + - metadata for each stored save version |
| 114 | + |
| 115 | +If you only care about protecting save history, back up both folders. |
| 116 | + |
| 117 | +If you want the shortest answer to “where are my saves?”, it is this: |
| 118 | + |
| 119 | +```text |
| 120 | +/srv/retrosavemanager/saves |
69 | 121 | ``` |
70 | 122 |
|
71 | | -2. Start single-container direct HTTP (`:80`): |
| 123 | +## Save Storage Layout |
72 | 124 |
|
73 | | -```bash |
74 | | -cd deploy |
75 | | -./scripts/pull-up.sh direct |
| 125 | +RetroSaveManager keeps save data under one backup-friendly root: |
| 126 | + |
| 127 | +```text |
| 128 | +SAVE_ROOT/<System>/<Game>/<save-id>/ |
76 | 129 | ``` |
77 | 130 |
|
78 | | -3. Optional macvlan mode (single container with own LAN IP): |
| 131 | +Example: |
79 | 132 |
|
80 | | -```bash |
81 | | -cd deploy |
82 | | -./scripts/pull-up.sh macvlan |
| 133 | +```text |
| 134 | +/srv/retrosavemanager/saves/Nintendo Super Nintendo Entertainment System/Super Mario World/save-123456/ |
83 | 135 | ``` |
84 | 136 |
|
85 | | -4. Optional backup retention cleanup: |
| 137 | +Inside each save folder you will typically see: |
86 | 138 |
|
87 | | -```bash |
88 | | -cd deploy |
89 | | -./scripts/prune-backups.sh --root /srv/retrosavemanager/backups --keep-recent 4 --keep-days 7 --dry-run |
90 | | -./scripts/prune-backups.sh --root /srv/retrosavemanager/backups --keep-recent 4 --keep-days 7 |
| 139 | +- `payload.*` |
| 140 | +- `metadata.json` |
| 141 | + |
| 142 | +## Ports Used |
| 143 | + |
| 144 | +By default, RetroSaveManager uses: |
| 145 | + |
| 146 | +- Container port `80` |
| 147 | +- Host port `80` in the example above |
| 148 | + |
| 149 | +That means: |
| 150 | + |
| 151 | +- Web UI: `http://YOUR-HOST-IP/` |
| 152 | +- API: `http://YOUR-HOST-IP/` |
| 153 | + |
| 154 | +There is no required separate frontend port. |
| 155 | + |
| 156 | +There is no required separate API port. |
| 157 | + |
| 158 | +There is no built-in HTTPS requirement inside the container. |
| 159 | + |
| 160 | +If you want to use a different host port, change only the left side: |
| 161 | + |
| 162 | +```yaml |
| 163 | +ports: |
| 164 | + - "8080:80" |
91 | 165 | ``` |
92 | 166 |
|
93 | | -Development-only local image build: |
| 167 | +Then the service will be available at: |
94 | 168 |
|
95 | | -```bash |
96 | | -cd deploy |
97 | | -./scripts/build-local.sh direct |
| 169 | +```text |
| 170 | +http://YOUR-HOST-IP:8080/ |
98 | 171 | ``` |
99 | 172 |
|
100 | | -## Helper Compatibility |
| 173 | +## Optional Environment Variables |
101 | 174 |
|
102 | | -Use your helper client with: |
| 175 | +You only need the minimal compose above to get started. |
103 | 176 |
|
104 | | -- `RSM_API_URL=http://<internal-hostname-or-ip>` |
105 | | -- `RSM_APP_PASSWORD=<optional-app-password>` |
| 177 | +These extra variables are optional: |
106 | 178 |
|
107 | | -The service exposes compatibility routes at both root and `/v1` aliases. |
| 179 | +`BASE_URL` |
108 | 180 |
|
109 | | -## Development |
| 181 | +Set this if you want a fixed public or internal base URL. |
110 | 182 |
|
111 | | -Backend: |
| 183 | +`PUBLIC_HOST` |
112 | 184 |
|
113 | | -```bash |
114 | | -cd backend |
115 | | -go test ./... |
| 185 | +Useful if you use an internal DNS name such as `retrosavemanager.lan`. |
| 186 | + |
| 187 | +`IGDB_CLIENT_ID` |
| 188 | + |
| 189 | +Optional metadata and cover-art enrichment. |
| 190 | + |
| 191 | +`IGDB_CLIENT_SECRET` |
| 192 | + |
| 193 | +Optional metadata and cover-art enrichment. |
| 194 | + |
| 195 | +`RAWG_API_KEY` |
| 196 | + |
| 197 | +Optional metadata and cover-art fallback enrichment. |
| 198 | + |
| 199 | +`BOOTSTRAP_DEMO_DATA` |
| 200 | + |
| 201 | +Set to `true` only if you want demo data in a fresh environment. |
| 202 | + |
| 203 | +## Recommended Example With Named Host Paths |
| 204 | + |
| 205 | +```yaml |
| 206 | +services: |
| 207 | + retrosavemanager: |
| 208 | + image: ghcr.io/joeblack2k/retrosavemanager:latest |
| 209 | + container_name: retrosavemanager |
| 210 | + restart: unless-stopped |
| 211 | + ports: |
| 212 | + - "80:80" |
| 213 | + environment: |
| 214 | + AUTH_MODE: disabled |
| 215 | + PORT: 80 |
| 216 | + SAVE_ROOT: /saves |
| 217 | + STATE_ROOT: /config |
| 218 | + PUBLIC_HOST: retrosavemanager.lan |
| 219 | + volumes: |
| 220 | + - /srv/retrosavemanager/config:/config |
| 221 | + - /srv/retrosavemanager/saves:/saves |
116 | 222 | ``` |
117 | 223 |
|
118 | | -Frontend: |
| 224 | +## Updating |
| 225 | +
|
| 226 | +Pull the latest image and recreate the container: |
119 | 227 |
|
120 | 228 | ```bash |
121 | | -cd frontend |
122 | | -npm ci |
123 | | -npm run test |
124 | | -npm run build |
| 229 | +docker compose pull |
| 230 | +docker compose up -d |
125 | 231 | ``` |
126 | 232 |
|
127 | | -Contract + security checks: |
| 233 | +## Helper App Configuration |
128 | 234 |
|
129 | | -```bash |
130 | | -./scripts/verify-contract.sh |
131 | | -./scripts/security-gate.sh |
| 235 | +Point helper apps to your RetroSaveManager host: |
| 236 | + |
| 237 | +```text |
| 238 | +http://YOUR-HOST-IP/ |
132 | 239 | ``` |
133 | 240 |
|
134 | | -Optional live enrichment (cover art + metadata fallback chain): |
| 241 | +If your helper supports an API URL variable, use: |
135 | 242 |
|
136 | | -- `IGDB_CLIENT_ID` |
137 | | -- `IGDB_CLIENT_SECRET` |
138 | | -- `RAWG_API_KEY` |
| 243 | +```text |
| 244 | +RSM_API_URL=http://YOUR-HOST-IP/ |
| 245 | +``` |
| 246 | + |
| 247 | +If you use an internal DNS name: |
| 248 | + |
| 249 | +```text |
| 250 | +RSM_API_URL=http://retrosavemanager.lan/ |
| 251 | +``` |
139 | 252 |
|
140 | | -## Image Publishing |
| 253 | +## Notes |
141 | 254 |
|
142 | | -GitHub Actions publishes: |
| 255 | +- This project is designed for internal and self-hosted use. |
| 256 | +- Default auth mode is disabled for trusted LAN setups. |
| 257 | +- The standard deployment is exactly one container. |
| 258 | +- The web UI and helper API are served by the same container. |
143 | 259 |
|
144 | | -- `ghcr.io/<owner>/retrosavemanager` |
| 260 | +## Releases |
145 | 261 |
|
146 | | -on pushes to `main` and version tags. |
| 262 | +Container images are published here: |
147 | 263 |
|
148 | | -See also [DEPLOY.md](DEPLOY.md). |
| 264 | +- [ghcr.io/joeblack2k/retrosavemanager](https://ghcr.io/joeblack2k/retrosavemanager) |
0 commit comments