mirror of
https://github.com/rommapp/docs.git
synced 2026-04-23 06:54:41 +00:00
docs: smooth staccato prose into flowing sentences
Combine clipped fragments into connected sentences (via commas, colons, parentheticals) while keeping them short. Touches ~20 files across reference, admin, using, install, getting-started, ecosystem, platforms, troubleshooting, and landing pages. https://claude.ai/code/session_0121DXEECS43gKkdvtUuuPAd
This commit is contained in:
+5
-5
@@ -7,17 +7,17 @@ description: Answers to the questions users most often ask about RomM.
|
||||
|
||||
## What is RomM?
|
||||
|
||||
A self-hosted ROM manager + player. Scan your library, get metadata, browse a clean UI, play in the browser, sync to handhelds, run it on your own hardware.
|
||||
A self-hosted ROM manager + player: scan your library, pull metadata, browse a clean UI, play in the browser, sync to handhelds, and run it all on your own hardware.
|
||||
|
||||
See [Introduction](../index.md) for the full pitch.
|
||||
|
||||
## Is it free?
|
||||
|
||||
Yes. [AGPL-3.0](license.md). Core always will be free, and other repos in the umbrella use permissive licenses. No tracking, no upsells.
|
||||
Yes, under [AGPL-3.0](license.md). The core will always be free, other repos in the umbrella use permissive licenses, and there's no tracking or upsells.
|
||||
|
||||
## How does it compare to [X other manager]?
|
||||
|
||||
Not a direct comparison page. Short version: RomM emphasises self-hosted + multi-user + in-browser-play + companion-app ecosystem. If those matter, try RomM. If you just want a local Windows app that scans a folder, tools like LaunchBox may fit better.
|
||||
Not a direct comparison page, but the short version: RomM emphasises self-hosted + multi-user + in-browser-play + companion-app ecosystem. If those matter, try RomM; if you just want a local Windows app that scans a folder, tools like LaunchBox may fit better.
|
||||
|
||||
## Do I need metadata API keys?
|
||||
|
||||
@@ -27,7 +27,7 @@ Recommended: IGDB + ScreenScraper. See [Metadata Providers](../administration/me
|
||||
|
||||
## Is RomM legal?
|
||||
|
||||
The software is legal. What you put in it depends on your jurisdiction. We don't ship ROMs or firmware, don't help you find them, and can't give legal advice. General rule: dumping games you own is usually fine, distributing copies is usually not.
|
||||
The software is legal, but what you put in it depends on your jurisdiction. We don't ship ROMs or firmware, don't help you find them, and can't give legal advice. General rule: dumping games you own is usually fine, distributing copies is usually not.
|
||||
|
||||
## Can I run RomM on [X]?
|
||||
|
||||
@@ -116,7 +116,7 @@ For public roadmap-ish information, watch the GitHub Projects board and `#announ
|
||||
|
||||
## What happened to SQLite?
|
||||
|
||||
Dropped in 3.0 for stability reasons. Use MariaDB, MySQL, or Postgres instead. See [Databases](../install/databases.md).
|
||||
Dropped in 3.0 for stability reasons: use MariaDB, MySQL, or Postgres instead. See [Databases](../install/databases.md).
|
||||
|
||||
## I found a bug.
|
||||
|
||||
|
||||
@@ -53,19 +53,19 @@ Yes. AGPL doesn't restrict private or commercial use.
|
||||
|
||||
### Can I fork RomM and relicense my fork?
|
||||
|
||||
No. AGPL is a strong copyleft, so forks remain AGPL. You can add your own changes under AGPL, but can't relicense the original code.
|
||||
No: AGPL is a strong copyleft, so forks remain AGPL, and while you can add your own changes under AGPL you can't relicense the original code.
|
||||
|
||||
### Can I charge money for RomM-as-a-service?
|
||||
|
||||
Yes, but you owe your users the source. You can't run a modified closed-source hosted RomM for paying customers.
|
||||
Yes, but you owe your users the source, so you can't run a modified closed-source hosted RomM for paying customers.
|
||||
|
||||
### Can I sell ROMs through RomM?
|
||||
|
||||
RomM doesn't care. Legality of doing so is governed by copyright law, not AGPL.
|
||||
RomM doesn't care: legality of doing so is governed by copyright law, not AGPL.
|
||||
|
||||
### Is there a commercial / dual-license option?
|
||||
|
||||
No. AGPL-only. If that's a blocker, RomM isn't the right choice.
|
||||
No, AGPL-only, and if that's a blocker then RomM isn't the right choice.
|
||||
|
||||
## Contributions
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ Authentication flows RomM supports:
|
||||
- **Username + password** (default): local account, bcrypt-hashed, stored in the DB.
|
||||
- **OIDC**: single sign-on via an external IdP. See [OIDC Setup](oidc/index.md).
|
||||
- **Client API Tokens**: long-lived per-user tokens for companion apps and scripts.
|
||||
- **Device pairing**: short codes for bootstrapping a token onto a handheld. Covered in [Client API Tokens](../ecosystem/client-api-tokens.md).
|
||||
- **Kiosk mode**: unauthenticated read-only access. Toggle for public demos / shared terminals.
|
||||
- **Device pairing**: short codes for bootstrapping a token onto a handheld, covered in [Client API Tokens](../ecosystem/client-api-tokens.md).
|
||||
- **Kiosk mode**: unauthenticated read-only access, handy for public demos and shared terminals.
|
||||
|
||||
## Session config
|
||||
|
||||
@@ -30,7 +30,7 @@ Sessions are cookie-based and stored in Redis. Relevant env vars:
|
||||
|
||||
## Local (username + password)
|
||||
|
||||
The default. Accounts are created via [invitations, registration, or the Setup Wizard](invitations-and-registration.md). Passwords are bcrypt-hashed, and RomM does not log or store plaintext passwords at any point.
|
||||
The default: accounts are created via [invitations, registration, or the Setup Wizard](invitations-and-registration.md), and passwords are bcrypt-hashed. RomM does not log or store plaintext passwords at any point.
|
||||
|
||||
**Disable local password login entirely** (force OIDC-only):
|
||||
|
||||
@@ -48,7 +48,7 @@ Until email-based self-serve reset lands, admins set passwords manually:
|
||||
|
||||
**Administration → Users → Edit → New password → Save.**
|
||||
|
||||
The next login on that account will use the new password. Existing sessions for that user remain valid until they expire. Revoke them explicitly by deleting all of the user's Client API Tokens if that's a concern.
|
||||
The next login on that account will use the new password, but existing sessions for that user remain valid until they expire. If that's a concern, revoke them explicitly by deleting all of the user's Client API Tokens.
|
||||
|
||||
### Self-serve password reset
|
||||
|
||||
@@ -81,7 +81,7 @@ Create from **Administration → Client API Tokens**. Each token:
|
||||
- Has an optional expiry (no expiry = never expires until manually revoked).
|
||||
- Can be "paired" to a device via a short code (covered in [Client API Tokens](../ecosystem/client-api-tokens.md)).
|
||||
|
||||
Each user gets up to 25 active tokens. Revoke from the same page. The API side ("how do I send this thing in a request?") lives in [API Authentication](../developers/api-authentication.md).
|
||||
Each user gets up to 25 active tokens, revokable from the same page. The API side ("how do I send this thing in a request?") lives in [API Authentication](../developers/api-authentication.md).
|
||||
|
||||
## Kiosk mode
|
||||
|
||||
@@ -119,4 +119,4 @@ To fully cut a user off:
|
||||
2. Delete all their Client API Tokens (**Administration → Client API Tokens**, filter by user).
|
||||
3. Delete the user.
|
||||
|
||||
Steps 1 + 2 together ensure any in-flight session and any token-based companion app lose access immediately. Step 3 removes the account.
|
||||
Steps 1 + 2 together ensure any in-flight session and any token-based companion app lose access immediately, and step 3 removes the account.
|
||||
|
||||
@@ -85,7 +85,7 @@ Useful when a scan is matching poorly and you want to know whether a provider is
|
||||
|
||||
## Sentry
|
||||
|
||||
Opt-in error tracking. Nothing is sent without a DSN.
|
||||
Opt-in error tracking: nothing is sent without a DSN.
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
@@ -100,7 +100,7 @@ What's sent:
|
||||
|
||||
What's not sent: ROM filenames, user credentials, metadata provider API keys. RomM filters sensitive parameters before reporting.
|
||||
|
||||
Suitable for self-hosted Sentry or [sentry.io](https://sentry.io/). Drop the DSN to stop reporting.
|
||||
Suitable for self-hosted Sentry or [sentry.io](https://sentry.io/); drop the DSN to stop reporting.
|
||||
|
||||
## OpenTelemetry
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ Every scan picks one mode. Modes differ in what they touch, so use the most-targ
|
||||
|
||||
| Mode | What it does | When to use |
|
||||
| --- | --- | --- |
|
||||
| **New Platforms** | Only scans platform folders not already in the DB. | After mounting a new ROM set. Fast. |
|
||||
| **Quick** | Skips files that already exist in the DB. No metadata refresh. | Default for scheduled runs and the watcher. |
|
||||
| **New Platforms** | Only scans platform folders not already in the DB. | After mounting a new ROM set; it's fast. |
|
||||
| **Quick** | Skips files that already exist in the DB, with no metadata refresh. | Default for scheduled runs and the watcher. |
|
||||
| **Unmatched** | Re-runs metadata matching against ROMs currently missing external IDs. | After adding a new metadata provider, or when some titles didn't match on the first scan. |
|
||||
| **Update** | Re-fetches metadata for all already-matched ROMs. | When metadata providers have meaningfully changed (e.g. IGDB restructured). Rare. |
|
||||
| **Update** | Re-fetches metadata for all already-matched ROMs. | When metadata providers have meaningfully changed (e.g. IGDB restructured); rare. |
|
||||
| **Hashes** | Recalculates CRC/MD5/SHA1 hashes. | After upgrading from a version that didn't hash (pre-4.4) or when you suspect file corruption. |
|
||||
| **Complete** | Full rescan. Recalculates hashes, re-fetches metadata for everything. | Rarely. Takes a long time. |
|
||||
| **Complete** | Full rescan, recalculating hashes and re-fetching metadata for everything. | Rarely, since it takes a long time. |
|
||||
|
||||
You can further scope a scan to specific **platforms** and specific **metadata providers**, useful when only one provider has changed (e.g. just enabled Hasheous → Unmatched scan, Hasheous selected, on all platforms).
|
||||
|
||||
@@ -47,15 +47,15 @@ Configured via env vars (full table in the [Scheduled Tasks reference](../refere
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `SCAN_INTERVAL_CRON` | `0 0 * * *` | Cron expression for the scheduled library scan. Runs a **Quick** scan by default. |
|
||||
| `SCAN_TIMEOUT_HOURS` | `1` | Hard cap. Scans that exceed this are killed and logged. |
|
||||
| `SCAN_WORKERS` | _auto_ | Concurrent worker processes for scanning. Leave as auto unless you're tuning. |
|
||||
| `SEVEN_ZIP_TIMEOUT` | _unset_ | Per-archive timeout for `.7z` extraction during scan. Raise if scanning huge compressed ROM sets. |
|
||||
| `SCAN_TIMEOUT_HOURS` | `1` | Hard cap: scans that exceed this are killed and logged. |
|
||||
| `SCAN_WORKERS` | _auto_ | Concurrent worker processes for scanning; leave as auto unless you're tuning. |
|
||||
| `SEVEN_ZIP_TIMEOUT` | _unset_ | Per-archive timeout for `.7z` extraction during scan; raise if scanning huge compressed ROM sets. |
|
||||
|
||||
To disable scheduled scans entirely, either unset the cron or set it to something unreachable (`SCAN_INTERVAL_CRON=0 0 31 2 *`).
|
||||
|
||||
## Filesystem watcher
|
||||
|
||||
The watcher tails your library folder and schedules scans in response to file events: files added, moved, deleted. It's off by default on some deployments. Enable with:
|
||||
The watcher tails your library folder and schedules scans in response to file events (files added, moved, or deleted). It's off by default on some deployments, so enable with:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
@@ -68,16 +68,16 @@ environment:
|
||||
Behaviour:
|
||||
|
||||
- Watches `/romm/library` (and everything under it) recursively.
|
||||
- Debounces bursts of events. The delay (default 10 seconds) lets a large `cp` or `rsync` settle before scanning.
|
||||
- Debounces bursts of events: the delay (default 10 seconds) lets a large `cp` or `rsync` settle before scanning.
|
||||
- Batches scans intelligently: many events → a single consolidated scan, not one scan per file.
|
||||
- Ignores content modifications and metadata-only changes. It cares about files appearing or disappearing, not about `chmod`.
|
||||
- Ignores content modifications and metadata-only changes, caring only about files appearing or disappearing (not `chmod`).
|
||||
- Skips OS noise (`.DS_Store`, `Thumbs.db`, `.tmp`, etc.).
|
||||
- If a whole new platform folder appears, switches to a **New Platforms** scan to pick it up cleanly.
|
||||
|
||||
### When **not** to enable the watcher
|
||||
|
||||
- **Slow / high-latency filesystems** (SMB mounts, rclone mounts, anything not local disk). The watcher reacts to every event, and flaky mounts generate a lot. Use scheduled scans instead.
|
||||
- **Libraries under active write load from other tools** (e.g. a ROM manager constantly tagging files). The watcher will re-scan on every change: at best noisy, at worst a scan loop.
|
||||
- **Slow / high-latency filesystems** (SMB mounts, rclone mounts, anything not local disk): the watcher reacts to every event, flaky mounts generate a lot of them, so use scheduled scans instead.
|
||||
- **Libraries under active write load from other tools** (e.g. a ROM manager constantly tagging files): the watcher will re-scan on every change, at best noisy and at worst a scan loop.
|
||||
|
||||
### Watcher vs scheduled scan
|
||||
|
||||
@@ -89,7 +89,7 @@ Behaviour:
|
||||
| Catches renames | Yes | Yes |
|
||||
| Survives a container restart | Yes, re-arms on startup | Yes |
|
||||
|
||||
Run both. The watcher handles day-to-day additions, and the scheduled scan is a safety net.
|
||||
Run both: the watcher handles day-to-day additions, and the scheduled scan is a safety net.
|
||||
|
||||
## What gets excluded
|
||||
|
||||
@@ -127,7 +127,7 @@ When a metadata provider returns multiple regional variants (Japanese cover, US
|
||||
|
||||
## Metadata source priority
|
||||
|
||||
Who wins when two providers disagree: covered in [Metadata Providers](metadata-providers.md#priority-and-conflict-resolution). Short version: `scan.priority.metadata` and `scan.priority.artwork` in `config.yml`.
|
||||
Who wins when two providers disagree is covered in [Metadata Providers](metadata-providers.md#priority-and-conflict-resolution); short version: `scan.priority.metadata` and `scan.priority.artwork` in `config.yml`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ RomM is multi-user from the start. The first user created during Setup is always
|
||||
| **Editor** | Household members who help curate the library. | Read everything, edit ROMs/platforms/collections, upload. No user management. |
|
||||
| **Viewer** | Guests, kids, anyone who should only play and track their own progress. | Read the library, manage their own saves/states/screenshots/profile. |
|
||||
|
||||
Roles are a convenience layer on top of **scopes**. See the scope matrix below for exactly what each role grants. You can't create custom roles in 5.0. If you need finer-grained access, use the role that's most restrictive and rely on [Client API Tokens](../ecosystem/client-api-tokens.md) for per-app customisation.
|
||||
Roles are a convenience layer on top of **scopes**; see the scope matrix below for exactly what each role grants. You can't create custom roles in 5.0, so if you need finer-grained access, use the most restrictive role and rely on [Client API Tokens](../ecosystem/client-api-tokens.md) for per-app customisation.
|
||||
|
||||
## Scope matrix
|
||||
|
||||
@@ -49,27 +49,27 @@ Two ways:
|
||||
|
||||
### Admin adds directly
|
||||
|
||||
**Administration → Users → Add.** Set username, email, password, and role. The account is usable immediately.
|
||||
**Administration → Users → Add.** Set username, email, password, and role, and the account is usable immediately.
|
||||
|
||||
### Invite link
|
||||
|
||||
Better when you don't want to handle someone else's password.
|
||||
|
||||
1. **Administration → Users → Invite.** Pick a role, and RomM generates a single-use invite link.
|
||||
2. Send the link. The recipient opens it, picks their own username and password, and is logged in.
|
||||
3. Invite links expire. The default is 30 days, configurable via [`INVITE_TOKEN_DAYS`](../reference/environment-variables.md).
|
||||
2. Send the link, and the recipient opens it, picks their own username and password, and is logged in.
|
||||
3. Invite links expire: the default is 30 days, configurable via [`INVITE_TOKEN_DAYS`](../reference/environment-variables.md).
|
||||
|
||||
### Public self-registration
|
||||
|
||||
Off by default. To let anyone with the URL register their own Viewer account, set `ALLOW_PUBLIC_REGISTRATION=true`. Only enable this if your instance is behind auth at the reverse-proxy layer (Authelia, etc.) or you genuinely want open registration. Once on, anyone who reaches `/register` can create an account.
|
||||
Off by default. To let anyone with the URL register their own Viewer account, set `ALLOW_PUBLIC_REGISTRATION=true`. Only enable this if your instance is behind auth at the reverse-proxy layer (Authelia, etc.) or you genuinely want open registration: once on, anyone who reaches `/register` can create an account.
|
||||
|
||||
### OIDC
|
||||
|
||||
If you've wired up OIDC, new identities can be provisioned on first login. Role mapping from OIDC claims is covered in [OIDC Setup](oidc/index.md). Look for `OIDC_CLAIM_ROLES` and the per-role env vars.
|
||||
If you've wired up OIDC, new identities can be provisioned on first login. Role mapping from OIDC claims is covered in [OIDC Setup](oidc/index.md): look for `OIDC_CLAIM_ROLES` and the per-role env vars.
|
||||
|
||||
## Editing and deleting users
|
||||
|
||||
- **Change role**: Admin → Users → Edit → Role dropdown. Takes effect on next login.
|
||||
- **Change role**: Admin → Users → Edit → Role dropdown, taking effect on next login.
|
||||
- **Reset password**: Admin → Users → Edit → New password. For self-service, the user can use the "Forgot password" flow from the login page if email is configured.
|
||||
- **Delete**: Admin → Users → red delete icon → confirm. RomM won't let you delete the last admin or delete yourself while signed in.
|
||||
|
||||
|
||||
+13
-13
@@ -5,7 +5,7 @@ description: Official Android launcher for RomM, browse and launch your library
|
||||
|
||||
# Argosy Launcher
|
||||
|
||||
**Argosy Launcher** is RomM's first-party Android app. Browse your library, download ROMs on the fly, launch into RetroArch or your emulator of choice.
|
||||
**Argosy Launcher** is RomM's first-party Android app: browse your library, download ROMs on the fly, and launch into RetroArch or your emulator of choice.
|
||||
|
||||
- **Repo:** [rommapp/argosy-launcher](https://github.com/rommapp/argosy-launcher)
|
||||
- **Language:** Kotlin
|
||||
@@ -29,16 +29,16 @@ description: Official Android launcher for RomM, browse and launch your library
|
||||
|
||||
### Via F-Droid / Play Store
|
||||
|
||||
Not currently on either. APK sideloading is the path for now.
|
||||
Not currently on either, so APK sideloading is the path for now.
|
||||
|
||||
## First-time setup
|
||||
|
||||
1. Launch Argosy.
|
||||
2. Enter your RomM URL (e.g. `https://romm.example.com`). Needs HTTPS in production.
|
||||
2. Enter your RomM URL (e.g. `https://romm.example.com`), which needs HTTPS in production.
|
||||
3. Choose **Pair Device**.
|
||||
4. Argosy shows a pairing URL or QR code: open it on a device that's already signed into RomM.
|
||||
5. On that device: RomM shows a confirmation dialog. Enter the 8-digit code Argosy displayed.
|
||||
6. Accept. Argosy receives a Client API Token bound to your RomM account.
|
||||
5. On that device, RomM shows a confirmation dialog; enter the 8-digit code Argosy displayed.
|
||||
6. Accept, and Argosy receives a Client API Token bound to your RomM account.
|
||||
|
||||
From here, Argosy lists your library. Full pairing-flow details in [Client API Tokens](client-api-tokens.md).
|
||||
|
||||
@@ -46,11 +46,11 @@ From here, Argosy lists your library. Full pairing-flow details in [Client API T
|
||||
|
||||
### Browse
|
||||
|
||||
The main view mirrors RomM's platform-and-collection layout. Tap a platform to see its games. Search works the same as the RomM web UI.
|
||||
The main view mirrors RomM's platform-and-collection layout: tap a platform to see its games, and search works the same as the RomM web UI.
|
||||
|
||||
### Download and launch
|
||||
|
||||
Tap a game → **Download**. Argosy pulls the ROM to device storage. Once downloaded, **Play** launches it with your configured emulator.
|
||||
Tap a game → **Download** and Argosy pulls the ROM to device storage. Once downloaded, **Play** launches it with your configured emulator.
|
||||
|
||||
### Emulator configuration
|
||||
|
||||
@@ -68,7 +68,7 @@ Argosy → Settings → Sync. Two modes:
|
||||
- **On session end**: uploads saves back to RomM when you exit the emulator.
|
||||
- **Manual**: you tap Upload when you want.
|
||||
|
||||
Saves show up in RomM's **Game Data** tab on the game, same as in-browser saves. See [Saves & States](../using/saves-and-states.md).
|
||||
Saves show up in RomM's **Game Data** tab on the game, same as in-browser saves: see [Saves & States](../using/saves-and-states.md).
|
||||
|
||||
## Permissions
|
||||
|
||||
@@ -78,14 +78,14 @@ Argosy needs:
|
||||
- **Network**: to talk to your RomM instance.
|
||||
- **Optional: Notifications**: download-complete and sync-complete pings.
|
||||
|
||||
No other permissions. The app doesn't request contacts, camera, location, or anything else.
|
||||
No other permissions: the app doesn't request contacts, camera, location, or anything else.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Can't connect to RomM.** Check the URL (including `https://`), and that the RomM instance is reachable from your mobile network. Cellular might be blocked, so try Wi-Fi first.
|
||||
- **Token invalid.** Pair again. Tokens can expire or be revoked on the RomM side.
|
||||
- **Emulator won't launch.** Make sure the emulator app is installed and Argosy has permission to open it. Some emulators require an intent-filter setup.
|
||||
- **Downloads fail partway.** Usually network, and Argosy resumes on retry.
|
||||
- **Can't connect to RomM**: check the URL (including `https://`) and that the RomM instance is reachable from your mobile network. Cellular might be blocked, so try Wi-Fi first.
|
||||
- **Token invalid**: pair again, since tokens can expire or be revoked on the RomM side.
|
||||
- **Emulator won't launch**: make sure the emulator app is installed and Argosy has permission to open it; some emulators require an intent-filter setup.
|
||||
- **Downloads fail partway**: usually network, and Argosy resumes on retry.
|
||||
|
||||
Full sync-specific debugging in [Device Sync Troubleshooting](../troubleshooting/sync.md).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ See the [full feeds reference](../reference/feeds.md) for URL formats, auth requ
|
||||
|
||||
## Community apps
|
||||
|
||||
Maintained by individuals in the community, not the RomM team. Support quality varies.
|
||||
Maintained by individuals in the community, not the RomM team, so support quality varies.
|
||||
|
||||
See **[Community Apps](community-apps.md)** for the full list with status flags (active / maintenance-mode / abandoned) and links.
|
||||
|
||||
@@ -62,10 +62,10 @@ For developers building something new on top of RomM:
|
||||
|
||||
Not a RomM companion, but useful alongside:
|
||||
|
||||
- **[Igir Collection Manager](igir.md)**: ROM sorting/verifying tool. Cleans up library layout before importing into RomM.
|
||||
- **[Igir Collection Manager](igir.md)**: ROM sorting/verifying tool that cleans up library layout before importing into RomM.
|
||||
|
||||
## Contributing a companion app
|
||||
|
||||
Built something RomM-adjacent? Open a PR on [rommapp/docs](https://github.com/rommapp/docs) adding it to [Community Apps](community-apps.md), or drop a link in the [Discord](https://discord.gg/romm) `#community-projects` channel.
|
||||
|
||||
We list active, maintained projects. No gate on code quality, but we do flag abandoned projects so users know what's current.
|
||||
We list active, maintained projects, with no gate on code quality, but we do flag abandoned projects so users know what's current.
|
||||
|
||||
@@ -27,7 +27,7 @@ The page switches to a live feed of the scan's progress. You can leave and come
|
||||
- Per-platform **accordion panels** show counts update live: total found, matched, unmatched.
|
||||
- You can click a **matched ROM** while the scan is still running to see what metadata RomM pulled, with no need to wait for the full run.
|
||||
|
||||
First scans on big libraries take a while. Expect ~4 seconds per ROM with a fast network to IGDB/ScreenScraper, and hashing (which runs unless you disabled it) adds IO time proportional to file size.
|
||||
First scans on big libraries take a while: expect ~4 seconds per ROM with a fast network to IGDB/ScreenScraper, and hashing (which runs unless you disabled it) adds IO time proportional to file size.
|
||||
|
||||
## What "matched" means
|
||||
|
||||
@@ -38,14 +38,14 @@ For each ROM the scanner:
|
||||
3. **Writes the DB entry** with title, cover, description, release date, and anything else the winning provider returned.
|
||||
4. **Merges overlay data** from other providers: RetroAchievements progression, HowLongToBeat completion times, SteamGridDB alternate covers if you've asked.
|
||||
|
||||
An **unmatched** ROM means no provider recognised it. Common causes:
|
||||
An **unmatched** ROM means no provider recognised it, with common causes:
|
||||
|
||||
- Filename is too generic (`game.gba`).
|
||||
- Bad rip, intro/patch applied, or a regional variant no provider has indexed.
|
||||
- Platform folder misnamed: the scanner queries providers scoped to the detected platform, so wrong platform equals no results.
|
||||
- Metadata provider credentials wrong or rate-limited: check the scan log for errors or the metadata provider status page in the admin dashboard.
|
||||
|
||||
Most of these are fixable. See [Scanning Troubleshooting](../troubleshooting/scanning.md).
|
||||
Most of these are fixable: see [Scanning Troubleshooting](../troubleshooting/scanning.md).
|
||||
|
||||
## When the scan finishes
|
||||
|
||||
@@ -66,9 +66,9 @@ From here, typical next steps:
|
||||
|
||||
If you're adding ROMs later and don't want a full rescan:
|
||||
|
||||
- **New Platforms**: only scans folders RomM hasn't seen before. Fast.
|
||||
- **Quick**: skips ROMs already catalogued. Good default for "I added a few games".
|
||||
- **Unmatched**: re-runs matching against ROMs without a provider ID. Good after adding a metadata provider.
|
||||
- **New Platforms**: only scans folders RomM hasn't seen before, and it's fast.
|
||||
- **Quick**: skips ROMs already catalogued, a good default for "I added a few games".
|
||||
- **Unmatched**: re-runs matching against ROMs without a provider ID, ideal after adding a metadata provider.
|
||||
|
||||
All six scan modes are documented in [Scanning & Watcher](../administration/scanning-and-watcher.md#scan-modes).
|
||||
|
||||
|
||||
+2
-2
@@ -75,7 +75,7 @@ RomM (ROM Manager) lets you scan, enrich, organise, and play your game collectio
|
||||
|
||||
## Philosophy
|
||||
|
||||
RomM is built for its users, not for shareholders. Self-hosted, open-source, no tracking, no upsells. The core app is licensed under [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/). Other projects in the umbrella use permissive licenses ([GPLv3](https://choosealicense.com/licenses/gpl-3.0/) for software, [CC0](https://choosealicense.com/licenses/cc0-1.0/) for documentation).
|
||||
RomM is built for its users, not for shareholders: self-hosted, open-source, no tracking, no upsells. The core app is licensed under [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/), and other projects in the umbrella use permissive licenses ([GPLv3](https://choosealicense.com/licenses/gpl-3.0/) for software, [CC0](https://choosealicense.com/licenses/cc0-1.0/) for documentation).
|
||||
|
||||
**RomM is and will always be free and open-source software.**
|
||||
|
||||
@@ -87,4 +87,4 @@ RomM is built for its users, not for shareholders. Self-hosted, open-source, no
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Join us on Discord to ask questions, share your setup, request features, or help other users. Code, issues, and releases live on [GitHub](https://github.com/rommapp/romm).
|
||||
Join us on Discord to ask questions, share your setup, request features, or help other users; code, issues, and releases live on [GitHub](https://github.com/rommapp/romm).
|
||||
|
||||
@@ -5,7 +5,7 @@ description: Canonical Docker Compose reference for a production RomM 5.0 deploy
|
||||
|
||||
# Docker Compose
|
||||
|
||||
The canonical way to run RomM is with Docker Compose. This page describes the full reference stack. The shorter happy-path walkthrough lives in [Quick Start](../getting-started/quick-start.md).
|
||||
The canonical way to run RomM is with Docker Compose, and this page describes the full reference stack. The shorter happy-path walkthrough lives in [Quick Start](../getting-started/quick-start.md).
|
||||
|
||||
The RomM stack has three parts:
|
||||
|
||||
@@ -24,8 +24,8 @@ The RomM stack has three parts:
|
||||
|
||||
| Field | Value | Notes |
|
||||
| --- | --- | --- |
|
||||
| `image` | `rommapp/romm:latest` | Or `ghcr.io/rommapp/romm:latest`. Pin to a specific tag (`5.0.0`) for production. See [Image Variants](image-variants.md) for `slim` vs `full`. |
|
||||
| `ports` | `80:8080` | Container listens on `8080`. Expose through a reverse proxy in production. See [Reverse Proxy](reverse-proxy.md). |
|
||||
| `image` | `rommapp/romm:latest` | Or `ghcr.io/rommapp/romm:latest`; pin to a specific tag (`5.0.0`) for production, and see [Image Variants](image-variants.md) for `slim` vs `full`. |
|
||||
| `ports` | `80:8080` | Container listens on `8080`; expose through a reverse proxy in production (see [Reverse Proxy](reverse-proxy.md)). |
|
||||
| `volumes` | see below | RomM writes to four distinct paths inside the container. |
|
||||
| `depends_on` | `romm-db` (healthy) | RomM exits on startup if the DB isn't reachable. |
|
||||
|
||||
@@ -33,22 +33,22 @@ The RomM stack has three parts:
|
||||
|
||||
| Path inside container | Purpose | Backup priority |
|
||||
| --- | --- | --- |
|
||||
| `/romm/library` | Your ROM files. Typically mounted **read-only**. | No. This is your source data, back it up separately. |
|
||||
| `/romm/assets` | User uploads: saves, states, uploaded screenshots. | **Critical.** Back this up with your DB. |
|
||||
| `/romm/library` | Your ROM files, typically mounted **read-only**. | No: this is your source data, back it up separately. |
|
||||
| `/romm/assets` | User uploads: saves, states, uploaded screenshots. | **Critical**, back this up with your DB. |
|
||||
| `/romm/resources` | Metadata assets fetched from IGDB, ScreenScraper, etc. | Low, and can be re-downloaded on a rescan. |
|
||||
| `/romm/config` | Holds `config.yml`. | **Critical.** Hand-tuned config, back it up. |
|
||||
| `/romm/config` | Holds `config.yml`. | **Critical**, hand-tuned config, back it up. |
|
||||
|
||||
See [Backup & Restore](backup-and-restore.md) for the full procedure.
|
||||
|
||||
#### Core environment variables
|
||||
|
||||
Minimal set. See the [Environment Variables reference](../reference/environment-variables.md) for the complete list.
|
||||
Minimal set: see the [Environment Variables reference](../reference/environment-variables.md) for the complete list.
|
||||
|
||||
| Variable | What it does |
|
||||
| --- | --- |
|
||||
| `ROMM_AUTH_SECRET_KEY` | JWT signing secret. **Required.** Generate with `openssl rand -hex 32`. |
|
||||
| `ROMM_AUTH_SECRET_KEY` | (Required) Generate the JWT signing secret with `openssl rand -hex 32`. |
|
||||
| `DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASSWD` | Database connection. |
|
||||
| `ROMM_DB_DRIVER` | `mariadb` (default), `mysql`, or `postgresql`. |
|
||||
| `ROMM_DB_DRIVER` | One of `mariadb` (default), `mysql`, or `postgresql`. |
|
||||
| `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWD` | Only set if you're using an external Redis/Valkey instance. |
|
||||
| Metadata provider creds | See [Metadata Providers](../administration/metadata-providers.md). |
|
||||
|
||||
@@ -57,21 +57,21 @@ Minimal set. See the [Environment Variables reference](../reference/environment-
|
||||
| Field | Value | Notes |
|
||||
| --- | --- | --- |
|
||||
| `image` | `mariadb:latest` | Pin to a major version (`mariadb:11`) for production. |
|
||||
| `volumes` | `mysql_data:/var/lib/mysql` | Back this up. It's your entire catalogue. |
|
||||
| `volumes` | `mysql_data:/var/lib/mysql` | Back this up, since it's your entire catalogue. |
|
||||
| `healthcheck` | `healthcheck.sh --connect --innodb_initialized` | RomM waits for this. |
|
||||
|
||||
The default compose file uses MariaDB because it requires no extra driver configuration. To use PostgreSQL, swap the image for `postgres:16`, set `ROMM_DB_DRIVER=postgresql`, and point `DB_PORT` at `5432`. See [Databases](databases.md) for the full swap.
|
||||
The default compose file uses MariaDB because it requires no extra driver configuration. To use PostgreSQL, swap the image for `postgres:16`, set `ROMM_DB_DRIVER=postgresql`, and point `DB_PORT` at `5432`: see [Databases](databases.md) for the full swap.
|
||||
|
||||
## Production hardening
|
||||
|
||||
The quick-start compose file is functional but not production-ready. Before exposing RomM to the internet:
|
||||
|
||||
- **Pin image tags.** `rommapp/romm:latest` moves, so use `rommapp/romm:5.0.0` (or whatever release you're on).
|
||||
- **Use a reverse proxy with HTTPS.** The built-in nginx listens on `8080` and terminates plain HTTP. Put Traefik, Caddy, or nginx in front with TLS. See [Reverse Proxy](reverse-proxy.md).
|
||||
- **Set a non-default `MARIADB_ROOT_PASSWORD`.** And don't reuse it for `MARIADB_PASSWORD`.
|
||||
- **Pin image tags**: `rommapp/romm:latest` moves, so use `rommapp/romm:5.0.0` (or whatever release you're on).
|
||||
- **Use a reverse proxy with HTTPS**: the built-in nginx listens on `8080` and terminates plain HTTP, so put Traefik, Caddy, or nginx in front with TLS (see [Reverse Proxy](reverse-proxy.md)).
|
||||
- **Set a non-default `MARIADB_ROOT_PASSWORD`**, and don't reuse it for `MARIADB_PASSWORD`.
|
||||
- **Mount the library read-only** unless you need RomM to write into it: `- /path/to/library:/romm/library:ro`.
|
||||
- **Use Docker secrets for credentials** if your orchestrator supports them. RomM recognises `ROMM_AUTH_SECRET_KEY_FILE` for loading the secret from a file mount.
|
||||
- **Enable backups.** At minimum: daily `mysqldump` + rsync of `/romm/assets` and `/romm/config`. See [Backup & Restore](backup-and-restore.md).
|
||||
- **Use Docker secrets for credentials** if your orchestrator supports them: RomM recognises `ROMM_AUTH_SECRET_KEY_FILE` for loading the secret from a file mount.
|
||||
- **Enable backups**: at minimum, daily `mysqldump` + rsync of `/romm/assets` and `/romm/config` (see [Backup & Restore](backup-and-restore.md)).
|
||||
|
||||
## Updating
|
||||
|
||||
@@ -80,4 +80,4 @@ docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Alembic migrations run automatically on startup. Read the [release notes](../releases/index.md) before every major-version bump. The 4.x → 5.0 jump in particular has breaking changes, covered in [Upgrading to 5.0](../releases/upgrading-to-5.0.md).
|
||||
Alembic migrations run automatically on startup, but read the [release notes](../releases/index.md) before every major-version bump. The 4.x → 5.0 jump in particular has breaking changes, covered in [Upgrading to 5.0](../releases/upgrading-to-5.0.md).
|
||||
|
||||
+21
-21
@@ -7,21 +7,21 @@ description: How to run DOS games in RomM via dosbox-pure, homebrew, shareware d
|
||||
|
||||
# MS-DOS
|
||||
|
||||
DOS games run in RomM via [`dosbox-pure`](https://github.com/schellingb/dosbox-pure), the EmulatorJS integration. Create a DOS platform (folder named `dos`) and drop your games in.
|
||||
DOS games run in RomM via [`dosbox-pure`](https://github.com/schellingb/dosbox-pure), the EmulatorJS integration: create a DOS platform (folder named `dos`) and drop your games in.
|
||||
|
||||
!!! tip "Upload games as `.zip`"
|
||||
`dosbox-pure` knows how to unzip and auto-mount zipped DOS games. Much easier than packaging a raw folder.
|
||||
`dosbox-pure` knows how to unzip and auto-mount zipped DOS games, which is much easier than packaging a raw folder.
|
||||
|
||||
!!! info "Save states work"
|
||||
Once you've got a game running, save state and resume from there. You only need to do the boot dance once per game.
|
||||
Once you've got a game running, save state and resume from there, so you only need to do the boot dance once per game.
|
||||
|
||||
## Game categories
|
||||
|
||||
DOS games come in three flavours, each needs a different approach:
|
||||
DOS games come in three flavours, each needing a different approach:
|
||||
|
||||
- **Homebrew:** indie / modern DOS games. Just an `.exe`, and usually Just Works once mounted.
|
||||
- **Shareware demos:** what most "free DOS games" sites distribute. Same as homebrew: all files in one folder.
|
||||
- **Retail:** need the original CD mounted alongside the installed game files. More work, and every game is different.
|
||||
- **Homebrew:** indie / modern DOS games, just an `.exe` that usually Just Works once mounted.
|
||||
- **Shareware demos:** what most "free DOS games" sites distribute, same as homebrew with all files in one folder.
|
||||
- **Retail:** need the original CD mounted alongside the installed game files, which means more work and every game is different.
|
||||
|
||||
## The manual way (commandline)
|
||||
|
||||
@@ -34,13 +34,13 @@ dir # find the .EXE
|
||||
filename.exe # launch
|
||||
```
|
||||
|
||||
Works for homebrew and shareware. Retail games usually fail here because they want a CD mounted somewhere.
|
||||
Works for homebrew and shareware, but retail games usually fail here because they want a CD mounted somewhere.
|
||||
|
||||
## The automatic way (`.conf` autoload)
|
||||
|
||||
`dosbox-pure` looks for a `.conf` file matching the `.exe` name before booting. If found, it reads the config, mounts whatever's specified, and runs the `[autoexec]` block.
|
||||
|
||||
Once you have a working `.conf`, click Play → game boots straight into DOS → `[autoexec]` runs → you're in the game. No typing.
|
||||
Once you have a working `.conf`, click Play → game boots straight into DOS → `[autoexec]` runs → you're in the game, with no typing.
|
||||
|
||||
### Homebrew / demo example: DOOM shareware
|
||||
|
||||
@@ -118,7 +118,7 @@ Walkthrough of `[autoexec]`:
|
||||
|
||||
3. Zip up the folder (including `DOOM.conf`).
|
||||
4. Upload to RomM under the `dos` platform.
|
||||
5. Click Play. Game boots.
|
||||
5. Click Play, and the game boots.
|
||||
|
||||
Blank screen after boot? Something's off in `[autoexec]`, usually a path or mount issue.
|
||||
|
||||
@@ -164,33 +164,33 @@ Retail games need the game CD image mounted alongside the install directory.
|
||||
4. Zip, upload, play.
|
||||
|
||||
!!! info "GOG DOS re-releases don't work"
|
||||
GOG's DOS games ship with custom wrappers (DOSBox Staging, ScummVM scripts, etc.) that don't translate to `dosbox-pure`. 100% failure rate observed. For GOG titles, extract the underlying game files and build a `.conf` yourself.
|
||||
GOG's DOS games ship with custom wrappers (DOSBox Staging, ScummVM scripts, etc.) that don't translate to `dosbox-pure`: 100% failure rate observed. For GOG titles, extract the underlying game files and build a `.conf` yourself.
|
||||
|
||||
### Retail troubleshooting
|
||||
|
||||
If the game lands on a blank DOS prompt:
|
||||
|
||||
1. **Remove the final `.exe` line from `[autoexec]`.** Drops you at a prompt after mounting. Check manually:
|
||||
1. **Remove the final `.exe` line from `[autoexec]`**, which drops you at a prompt after mounting, then check manually:
|
||||
- Is the game directory mounted correctly?
|
||||
- `type DEFAULT.cfg` (or whatever the game's config is named): the install path should line up with what you mounted.
|
||||
2. **Check drive paths.** Many retail DOS games hard-code `D:\` as the CD. If you mount the CD as `E:`, the game won't find it.
|
||||
3. **Debug with native `dosbox-pure`.** Run the same zip in [RetroArch](https://retroarch.com/) with the `dosbox-pure` core. If it works there, it should work in RomM. If it doesn't, the `.conf` is the problem.
|
||||
2. **Check drive paths**: many retail DOS games hard-code `D:\` as the CD, so if you mount the CD as `E:` the game won't find it.
|
||||
3. **Debug with native `dosbox-pure`**: run the same zip in [RetroArch](https://retroarch.com/) with the `dosbox-pure` core. If it works there it should work in RomM, and if it doesn't the `.conf` is the problem.
|
||||
|
||||
## Config file deep dive
|
||||
|
||||
The DOOM example above is minimal. The full config can include dozens more options (sound card tweaks, CPU cycles, joysticks, IPX networking). The `dosbox-pure` wiki is the authoritative reference for every option.
|
||||
The DOOM example above is minimal: the full config can include dozens more options (sound card tweaks, CPU cycles, joysticks, IPX networking), and the `dosbox-pure` wiki is the authoritative reference for every option.
|
||||
|
||||
## Netplay on DOS
|
||||
|
||||
Not supported: `dosbox-pure` works with EmulatorJS Netplay intermittently, not reliably. Stick to single-player for DOS.
|
||||
Not supported: `dosbox-pure` works with EmulatorJS Netplay intermittently rather than reliably, so stick to single-player for DOS.
|
||||
|
||||
## Known issues
|
||||
|
||||
- **Mouse lag.** Enable `autolock=true` in `[sdl]`.
|
||||
- **Wrong resolution / aspect.** Tweak `[render] aspect=` and `windowresolution=`.
|
||||
- **Audio crackle.** Lower `rate=22050` to `rate=11025` or raise the mixer blocksize.
|
||||
- **Game runs too fast.** Most auto-cycle issues. Set `cycles=fixed 4000` (or another number) explicitly.
|
||||
- **Keyboard doesn't work for certain keys.** `usescancodes=false` sometimes fixes it.
|
||||
- **Mouse lag**: enable `autolock=true` in `[sdl]`.
|
||||
- **Wrong resolution / aspect**: tweak `[render] aspect=` and `windowresolution=`.
|
||||
- **Audio crackle**: lower `rate=22050` to `rate=11025` or raise the mixer blocksize.
|
||||
- **Game runs too fast**: most auto-cycle issues, so set `cycles=fixed 4000` (or another number) explicitly.
|
||||
- **Keyboard doesn't work for certain keys**: `usescancodes=false` sometimes fixes it.
|
||||
|
||||
## Dosemu / Exodos alternatives
|
||||
|
||||
|
||||
@@ -47,17 +47,17 @@ You'll always set these:
|
||||
|
||||
| Variable | Purpose |
|
||||
| --- | --- |
|
||||
| `ROMM_AUTH_SECRET_KEY` | JWT signing key. Generate with `openssl rand -hex 32`. **Never rotate lightly**: breaks all sessions. |
|
||||
| `ROMM_AUTH_SECRET_KEY` | JWT signing key generated with `openssl rand -hex 32`. **Never rotate lightly**, as it breaks all sessions. |
|
||||
| `DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASSWD` | Database connection. |
|
||||
| `ROMM_DB_DRIVER` | `mariadb` (default), `mysql`, `postgresql`, or `sqlite`. See [Databases](../install/databases.md). |
|
||||
| `ROMM_DB_DRIVER` | One of `mariadb` (default), `mysql`, `postgresql`, or `sqlite`: see [Databases](../install/databases.md). |
|
||||
|
||||
For metadata providers (IGDB, ScreenScraper, etc.) see [Metadata Providers](../administration/metadata-providers.md). For OIDC, see [OIDC Setup](../administration/oidc/index.md).
|
||||
|
||||
## When env vars are read
|
||||
|
||||
- **Startup.** Most vars are consumed once at container start. Change requires `docker compose up -d` to apply.
|
||||
- **Per-request.** A handful of feature toggles (`KIOSK_MODE`, `DISABLE_USERPASS_LOGIN`) are re-checked per request. Still, restart is safest to avoid partial-state caching.
|
||||
- **Never at runtime.** There's no reload-config endpoint.
|
||||
- **Startup:** most vars are consumed once at container start, so changes need a `docker compose up -d` to apply.
|
||||
- **Per-request:** a handful of feature toggles (`KIOSK_MODE`, `DISABLE_USERPASS_LOGIN`) are re-checked per request, but a restart is safest to avoid partial-state caching.
|
||||
- **Never at runtime:** there's no reload-config endpoint.
|
||||
|
||||
## Full reference
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ rsync -a /path/to/assets/ /backup/romm/assets-pre-5.0/
|
||||
rsync -a /path/to/config/ /backup/romm/config-pre-5.0/
|
||||
```
|
||||
|
||||
Don't skip this. "It's a minor upgrade" isn't a thing for a major version.
|
||||
Don't skip this: "it's a minor upgrade" isn't a thing for a major version.
|
||||
|
||||
### 4. Pin the current version
|
||||
|
||||
@@ -130,10 +130,10 @@ docker compose logs -f romm
|
||||
|
||||
What to watch for:
|
||||
|
||||
- **Alembic migrations running:** lines prefixed `INFO [alembic.runtime.migration]`. Takes anywhere from seconds (small libraries) to a couple minutes (very large ones).
|
||||
- **`Application startup complete.`** RomM is healthy.
|
||||
- **`Watcher started.`** Filesystem watcher up (if enabled).
|
||||
- **ERROR lines:** bad. Go to [Rollback](#rollback).
|
||||
- **Alembic migrations running:** lines prefixed `INFO [alembic.runtime.migration]`, taking anywhere from seconds (small libraries) to a couple minutes (very large ones).
|
||||
- **`Application startup complete.`**: RomM is healthy.
|
||||
- **`Watcher started.`**: filesystem watcher is up (if enabled).
|
||||
- **ERROR lines:** bad news, go to [Rollback](#rollback).
|
||||
|
||||
### 4. Smoke-test
|
||||
|
||||
@@ -246,7 +246,7 @@ Open a bug report on [GitHub](https://github.com/rommapp/romm/issues) with:
|
||||
|
||||
### Migrations hang
|
||||
|
||||
Watch `docker logs -f romm`. If Alembic is still running, it's still running. Large DBs take a while. If it's been more than 10 minutes with no progress, something's wrong. Check for:
|
||||
Watch `docker logs -f romm`: if Alembic is still running, it's still running, and large DBs take a while. If it's been more than 10 minutes with no progress, something's wrong. Check for:
|
||||
|
||||
- DB connection issues (`docker logs romm-db`).
|
||||
- Out-of-memory kills (`dmesg | grep -i oom`).
|
||||
@@ -266,7 +266,7 @@ See [OIDC Setup → Role mapping](../administration/oidc/index.md#role-mapping-5
|
||||
|
||||
### Scheduled tasks stop firing
|
||||
|
||||
5.0 renamed several cron env vars. Check [Environment Variables](../reference/environment-variables.md) and update your env to the new names, otherwise the old cron expressions are ignored and tasks run on defaults (or not at all, if you previously disabled them).
|
||||
5.0 renamed several cron env vars, so check [Environment Variables](../reference/environment-variables.md) and update your env to the new names: otherwise the old cron expressions are ignored and tasks run on defaults (or not at all, if you previously disabled them).
|
||||
|
||||
## After the upgrade
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ Fix: [clear cookies](https://support.google.com/accounts/answer/32050) for the R
|
||||
|
||||
## `Forbidden (403) CSRF verification failed`
|
||||
|
||||
CSRF protection is on by default. A mismatched or missing `csrftoken` cookie causes this.
|
||||
CSRF protection is on by default, so a mismatched or missing `csrftoken` cookie causes this.
|
||||
|
||||
1. Reload the page. RomM sets a fresh CSRF cookie on GET requests, which should fix it on the next POST.
|
||||
1. Reload the page: RomM sets a fresh CSRF cookie on GET requests, which should fix it on the next POST.
|
||||
2. Still broken? Clear cookies for the RomM host and hard-reload (`CMD+SHIFT+R` / `CTRL+F5`).
|
||||
3. Known to happen on Chrome specifically, and rare on Firefox/Safari.
|
||||
|
||||
@@ -26,11 +26,11 @@ If you're behind a reverse proxy and CSRF keeps failing, the proxy is probably s
|
||||
|
||||
## `400 Bad Request` on the WebSocket endpoint
|
||||
|
||||
Your reverse proxy is stripping the WebSocket upgrade. RomM uses Socket.IO for live updates (scan progress, Netplay).
|
||||
Your reverse proxy is stripping the WebSocket upgrade, and RomM uses Socket.IO for live updates (scan progress, Netplay).
|
||||
|
||||
Fixes per proxy:
|
||||
|
||||
- **Nginx / NPM**: enable WebSockets Support. The [Reverse Proxy](../install/reverse-proxy.md) snippets already do this.
|
||||
- **Nginx / NPM**: enable WebSockets Support (the [Reverse Proxy](../install/reverse-proxy.md) snippets already do this).
|
||||
- **Traefik**: add `proxy_set_header Upgrade $http_upgrade` (or use the Traefik middleware equivalent).
|
||||
- **Caddy**: WebSockets work out of the box with `reverse_proxy`.
|
||||
- **Cloudflare**: enable **WebSockets** under Network settings.
|
||||
@@ -65,10 +65,10 @@ The `OIDC_REDIRECT_URI` in RomM's env doesn't **exactly** match what's registere
|
||||
|
||||
Check for:
|
||||
|
||||
- **Trailing slashes.** `/api/oauth/openid` vs `/api/oauth/openid/` are different to the IdP.
|
||||
- **Scheme.** `http://` vs `https://`.
|
||||
- **Host.** `romm.example.com` vs `www.romm.example.com` vs the bare IP.
|
||||
- **Port.** Implied `80` / `443` on HTTPS vs an explicit port.
|
||||
- **Trailing slashes**: `/api/oauth/openid` vs `/api/oauth/openid/` are different to the IdP.
|
||||
- **Scheme**: `http://` vs `https://`.
|
||||
- **Host**: `romm.example.com` vs `www.romm.example.com` vs the bare IP.
|
||||
- **Port**: implied `80` / `443` on HTTPS vs an explicit port.
|
||||
|
||||
Fix: make them identical on both sides.
|
||||
|
||||
@@ -77,10 +77,10 @@ Fix: make them identical on both sides.
|
||||
You configured `OIDC_CLAIM_ROLES` but RomM isn't honouring it.
|
||||
|
||||
1. **Is the claim actually in the token?** Decode your IdP's ID token at [jwt.io](https://jwt.io) and verify the claim name (e.g. `groups`, `realm_access.roles`) is present and non-empty.
|
||||
2. **Does the value match?** `OIDC_ROLE_ADMIN=romm-admin` will only match if the claim contains exactly the string `romm-admin`. Case-sensitive.
|
||||
2. **Does the value match?** `OIDC_ROLE_ADMIN=romm-admin` will only match if the claim contains exactly the string `romm-admin`, and it's case-sensitive.
|
||||
3. **Is the claim mapper on the IdP side configured to include the claim?** On Keycloak, for example, you need a Client Scope with a Group Membership mapper added to the client.
|
||||
|
||||
Roles are re-evaluated on every login, and there's no cache to bust. Log out and back in after fixing.
|
||||
Roles are re-evaluated on every login, with no cache to bust, so log out and back in after fixing.
|
||||
|
||||
### "Email is missing from token" (Zitadel-specific)
|
||||
|
||||
@@ -90,7 +90,7 @@ See [OIDC with Zitadel → Enable claims](../administration/oidc/zitadel.md) for
|
||||
|
||||
### Authentik 2025.10: login succeeds but RomM rejects the user
|
||||
|
||||
Authentik 2025.10 changed the default `email_verified` claim from `true` to `false`. RomM requires a verified email, so the claim must arrive as `true`.
|
||||
Authentik 2025.10 changed the default `email_verified` claim from `true` to `false`, but RomM requires a verified email so the claim must arrive as `true`.
|
||||
|
||||
Fix: add the property mapping documented in [OIDC with Authentik → Create a property mapping](../administration/oidc/authentik.md#2-create-a-property-mapping-authentik-202510).
|
||||
|
||||
@@ -98,12 +98,12 @@ Fix: add the property mapping documented in [OIDC with Authentik → Create a pr
|
||||
|
||||
Two possibilities:
|
||||
|
||||
1. **Email not verified in Keycloak.** Admin Console → Users → open the user → **Email Verified**: on. RomM rejects unverified emails.
|
||||
2. **Email mismatch between Keycloak and a pre-existing RomM user.** If RomM already has a local account `alice@example.com`, the first OIDC login for `alice@example.com` signs into that account. If the emails don't match exactly, RomM creates a *second* account. Fix: edit the user in RomM to set the correct email, then log in via OIDC.
|
||||
1. **Email not verified in Keycloak**: Admin Console → Users → open the user → **Email Verified**: on. RomM rejects unverified emails.
|
||||
2. **Email mismatch between Keycloak and a pre-existing RomM user**: if RomM already has a local account `alice@example.com`, the first OIDC login for `alice@example.com` signs into that account. If the emails don't match exactly, RomM creates a *second* account. Fix: edit the user in RomM to set the correct email, then log in via OIDC.
|
||||
|
||||
### `OAuthException: expired token` on callback
|
||||
|
||||
Your RomM host and the IdP have significant clock drift. Run NTP on both.
|
||||
Your RomM host and the IdP have significant clock drift, so run NTP on both.
|
||||
|
||||
### Autologin loops forever
|
||||
|
||||
@@ -120,5 +120,5 @@ If `bypass_autologin` doesn't work in your version, shell into the container and
|
||||
## Still stuck
|
||||
|
||||
- Check the container logs: `docker logs romm 2>&1 | grep -iE 'auth|oidc|oauth'`.
|
||||
- Cross-reference your IdP's audit logs. They often show exactly why a login was rejected on their side.
|
||||
- Cross-reference your IdP's audit logs, which often show exactly why a login was rejected on their side.
|
||||
- Ask on [Discord](https://discord.gg/romm) `#help` with the IdP name and the exact error text.
|
||||
|
||||
@@ -11,13 +11,13 @@ Most scan problems boil down to: library not mounted where RomM expects, folder
|
||||
|
||||
Three common causes:
|
||||
|
||||
1. **Library mounted in the wrong place.** RomM expects your library at `/romm/library` inside the container. Verify with `docker exec romm ls /romm/library`. You should see your `roms/` directory (Structure A) or platform folders (Structure B).
|
||||
2. **Permissions.** The RomM container has to read the files. Check from the host: `ls -lh /path/to/library`. If the RomM process can't see them, nothing scans.
|
||||
3. **Invalid folder structure.** Review [Folder Structure](../getting-started/folder-structure.md). RomM tries Structure A first, so if it finds neither layout, the scan completes in seconds with nothing found.
|
||||
1. **Library mounted in the wrong place**: RomM expects your library at `/romm/library` inside the container. Verify with `docker exec romm ls /romm/library`: you should see your `roms/` directory (Structure A) or platform folders (Structure B).
|
||||
2. **Permissions**: the RomM container has to read the files, so check from the host with `ls -lh /path/to/library`. If the RomM process can't see them, nothing scans.
|
||||
3. **Invalid folder structure**: review [Folder Structure](../getting-started/folder-structure.md). RomM tries Structure A first, so if it finds neither layout the scan completes in seconds with nothing found.
|
||||
|
||||
## "ROMs not found for platform X, check romm folder structure"
|
||||
|
||||
Same root cause as the previous one. RomM needs a `roms/` directory somewhere: either `/romm/library/roms/{platform}/` (Structure A) or `/romm/library/{platform}/roms/` (Structure B).
|
||||
Same root cause as the previous one: RomM needs a `roms/` directory somewhere, either `/romm/library/roms/{platform}/` (Structure A) or `/romm/library/{platform}/roms/` (Structure B).
|
||||
|
||||
Common mount-path mistakes:
|
||||
|
||||
@@ -35,7 +35,7 @@ Common mount-path mistakes:
|
||||
|
||||
## Platform folder isn't detected
|
||||
|
||||
RomM matches your platform folder names against IGDB platform slugs. If your folder is named differently, it won't match.
|
||||
RomM matches your platform folder names against IGDB platform slugs, so if your folder is named differently, it won't match.
|
||||
|
||||
Example: Nintendo 64DD's IGDB slug is `64dd` ([igdb.com/platforms/64dd](https://www.igdb.com/platforms/64dd)), so the folder should be named `64dd/`. If your folder is `n64dd/` or `nintendo-64dd/`, it won't detect.
|
||||
|
||||
@@ -54,10 +54,10 @@ Full list of supported slugs: [Supported Platforms](../platforms/supported-platf
|
||||
|
||||
## Scan times out at ~4 hours (or whatever `SCAN_TIMEOUT` says)
|
||||
|
||||
Scans are capped. If yours hits the cap:
|
||||
Scans are capped, so if yours hits the cap:
|
||||
|
||||
1. **Use `Quick` mode** from the Scan page: skips already-catalogued files. Most repeated scans complete in minutes.
|
||||
2. **Raise the cap** if you need a full rescan: `SCAN_TIMEOUT_HOURS=8` (or whatever). See [Environment Variables](../reference/environment-variables.md).
|
||||
1. **Use `Quick` mode** from the Scan page, which skips already-catalogued files so most repeated scans complete in minutes.
|
||||
2. **Raise the cap** if you need a full rescan: `SCAN_TIMEOUT_HOURS=8` (or whatever), per [Environment Variables](../reference/environment-variables.md).
|
||||
3. **Run scans per-platform** instead of everything at once, to checkpoint progress.
|
||||
|
||||
## Scan stops mid-platform
|
||||
@@ -71,8 +71,8 @@ docker logs romm 2>&1 | grep -E 'ERROR.*scan_handler'
|
||||
Common culprits:
|
||||
|
||||
- **Corrupted file**: unzip it, re-zip, try again.
|
||||
- **Old DOS zip with backslash paths**: Python's `zipfile` chokes on these. Re-create the archive with forward slashes.
|
||||
- **Read errors on the mount**: usually SMB/NFS flakiness. Watch `dmesg` for mount drops.
|
||||
- **Old DOS zip with backslash paths**: Python's `zipfile` chokes on these, so re-create the archive with forward slashes.
|
||||
- **Read errors on the mount**: usually SMB/NFS flakiness, so watch `dmesg` for mount drops.
|
||||
|
||||
Log lines look like:
|
||||
|
||||
@@ -82,7 +82,7 @@ ERROR: [RomM][scan_handler][2025-04-12 11:48:55] Failed to process /romm/li
|
||||
|
||||
## Tracking progress on a big library
|
||||
|
||||
When scans run for hours across many platforms, the live UI log is hard to follow. Tail the container logs instead:
|
||||
When scans run for hours across many platforms, the live UI log is hard to follow, so tail the container logs instead:
|
||||
|
||||
```sh
|
||||
docker logs romm 2>/dev/null | grep -E 'scan_handler.*Identified as'
|
||||
@@ -101,9 +101,9 @@ INFO: [RomM][scan_handler][2025-04-13 12:50:42] Identified as WonderSwan
|
||||
Check the scan log for per-provider errors:
|
||||
|
||||
- **IGDB: "Could not get twitch auth token"** → `IGDB_CLIENT_ID` or `IGDB_CLIENT_SECRET` is wrong, or revoked on the Twitch side.
|
||||
- **ScreenScraper: "403 / DAILY_LIMIT_REACHED"** → you've hit the free-tier quota. Wait, or upgrade your ScreenScraper membership.
|
||||
- **ScreenScraper: "403 / DAILY_LIMIT_REACHED"** → you've hit the free-tier quota, so wait or upgrade your ScreenScraper membership.
|
||||
- **RetroAchievements: "Invalid API key"** → regenerate at [retroachievements.org/settings](https://retroachievements.org/settings).
|
||||
- **Any provider: "Request timed out"** → transient. Re-run an **Unmatched** scan to retry only the failures.
|
||||
- **Any provider: "Request timed out"** → transient, so re-run an **Unmatched** scan to retry only the failures.
|
||||
|
||||
Full provider reference: [Metadata Providers](../administration/metadata-providers.md).
|
||||
|
||||
@@ -111,18 +111,18 @@ Full provider reference: [Metadata Providers](../administration/metadata-provide
|
||||
|
||||
Options, in order of effort:
|
||||
|
||||
1. **Add more providers.** A ROM IGDB doesn't know about might be in ScreenScraper, Hasheous, or LaunchBox. Enable one or more, then run an **Unmatched** scan.
|
||||
2. **Use filename tags.** If you already know the provider ID, rename the file to include `(igdb-1234)` or similar. See [Metadata Providers → Filename tags](../administration/metadata-providers.md#metadata-tags-in-filenames).
|
||||
3. **Manually match.** Open the ROM detail page, click the **Match** button, and search for the right title.
|
||||
1. **Add more providers**: a ROM IGDB doesn't know about might be in ScreenScraper, Hasheous, or LaunchBox, so enable one or more, then run an **Unmatched** scan.
|
||||
2. **Use filename tags**: if you already know the provider ID, rename the file to include `(igdb-1234)` or similar (see [Metadata Providers → Filename tags](../administration/metadata-providers.md#metadata-tags-in-filenames)).
|
||||
3. **Manually match**: open the ROM detail page, click the **Match** button, and search for the right title.
|
||||
|
||||
## Hash calculations are slow
|
||||
|
||||
Hashing large ROMs (PS1, Saturn, DC images) is IO-bound. Options:
|
||||
Hashing large ROMs (PS1, Saturn, DC images) is IO-bound, with a few options:
|
||||
|
||||
- **Skip hashing on small hosts**: check **Skip hash calculation** on the Scan page, or set `filesystem.skip_hash_calculation: true` in `config.yml`. You lose RetroAchievements and Hasheous matching (both depend on hashes).
|
||||
- **Skip hashing on small hosts**: check **Skip hash calculation** on the Scan page, or set `filesystem.skip_hash_calculation: true` in `config.yml`. You lose RetroAchievements and Hasheous matching, since both depend on hashes.
|
||||
- **Use SSD/NVMe for the library** if you care about hash performance.
|
||||
- **Raise `SEVEN_ZIP_TIMEOUT`** if you're scanning many large `.7z` archives.
|
||||
|
||||
## Scan seems to work but nothing shows up
|
||||
|
||||
Refresh the page. The ribbons on the home dashboard cache aggressively during a scan. Still blank? Check **Administration → Server Stats**. If counts are zero, the scan didn't actually persist anything. Usually a DB permission issue, so check `docker logs romm 2>&1 | grep -i 'database'`.
|
||||
Refresh the page, since the ribbons on the home dashboard cache aggressively during a scan. Still blank? Check **Administration → Server Stats**: if counts are zero, the scan didn't actually persist anything, usually a DB permission issue, so check `docker logs romm 2>&1 | grep -i 'database'`.
|
||||
|
||||
@@ -26,7 +26,7 @@ OIDC users don't have local passwords, because authentication is via the IdP. Pa
|
||||
|
||||
### Avatar
|
||||
|
||||
Upload a PNG / JPEG / WebP. Displayed next to your name across the UI.
|
||||
Upload a PNG / JPEG / WebP, and it shows next to your name across the UI.
|
||||
|
||||
### Preferred username in OIDC
|
||||
|
||||
@@ -36,19 +36,19 @@ If you're an OIDC user and want RomM to show your `preferred_username` from the
|
||||
|
||||
Long-lived API tokens for companion apps, scripts, and integrations. Each token is scoped to a subset of your user's scopes, and optionally expires.
|
||||
|
||||
See the full spec in [Client API Tokens](../ecosystem/client-api-tokens.md). This section is the "how I create one from the UI" version.
|
||||
See the full spec in [Client API Tokens](../ecosystem/client-api-tokens.md); this section is just the "how I create one from the UI" version.
|
||||
|
||||
### Creating a token
|
||||
|
||||
1. **Profile → Client API Tokens → + New Token**.
|
||||
2. Pick:
|
||||
- **Name**: descriptive (e.g. "Grout on my RG35XX").
|
||||
- **Scopes**: which permissions to include. Default is read-only. Tick write scopes deliberately.
|
||||
- **Expiry**: optional. Blank = never expires.
|
||||
- **Scopes**: which permissions to include. Default is read-only, so tick write scopes deliberately.
|
||||
- **Expiry**: optional, blank = never expires.
|
||||
3. **Create**.
|
||||
4. The token appears **once**. Copy it immediately, because you can't retrieve it later.
|
||||
4. The token appears **once**, so copy it immediately because you can't retrieve it later.
|
||||
|
||||
Token format: `rmm_` + 40 hex chars. Treat it like a password.
|
||||
Token format: `rmm_` + 40 hex chars, and you should treat it like a password.
|
||||
|
||||
### Pairing to a device
|
||||
|
||||
@@ -65,12 +65,12 @@ Full flow in [Client API Tokens](../ecosystem/client-api-tokens.md).
|
||||
|
||||
### Limits
|
||||
|
||||
- **25 active tokens per user.** Delete old ones to free slots.
|
||||
- **Tokens carry a subset of your scopes.** If an admin demotes you to Viewer, any token you hold with admin scopes stops working. Tokens don't escalate privileges beyond the owning user's current role.
|
||||
- **25 active tokens per user**, so delete old ones to free slots.
|
||||
- **Tokens carry a subset of your scopes**, so if an admin demotes you to Viewer, any token you hold with admin scopes stops working. Tokens don't escalate privileges beyond the owning user's current role.
|
||||
|
||||
### Revoking
|
||||
|
||||
**Profile → Client API Tokens → [token] → Revoke.** Takes effect immediately. The companion app loses access on its next API call.
|
||||
**Profile → Client API Tokens → [token] → Revoke** takes effect immediately, and the companion app loses access on its next API call.
|
||||
|
||||
## Keyboard shortcuts
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ description: Download ROMs from RomM: single, bulk, QR codes, copy-link, and str
|
||||
|
||||
The quick path: hover a game card → click **Download**. Or from the game detail page → **Download** button.
|
||||
|
||||
RomM streams the file directly: no temp file on disk, no copy, no waiting for packaging. Large ROMs and multi-disc sets download just as quickly as small ones.
|
||||
RomM streams the file directly, with no temp file on disk, no copy, and no waiting for packaging, so large ROMs and multi-disc sets download just as quickly as small ones.
|
||||
|
||||
For multi-file games (folder-based), RomM streams a zip on the fly. See [Multi-file downloads](#multi-file-and-bulk-downloads-nginx-mod_zip) below.
|
||||
|
||||
@@ -19,7 +19,7 @@ For cases where you want the URL, not the file right now: sending it to another
|
||||
|
||||
Context menu (…) on a game card → **Copy download link**, and the URL is on your clipboard.
|
||||
|
||||
Anyone with access to the link and the server can download. By default the link requires your session cookie or a bearer token. See [Third-party download auth](#third-party-download-auth) for the exception.
|
||||
Anyone with access to the link and the server can download, though by default the link requires your session cookie or a bearer token. See [Third-party download auth](#third-party-download-auth) for the exception.
|
||||
|
||||
## QR code
|
||||
|
||||
@@ -27,7 +27,7 @@ For handheld-to-desktop or desktop-to-phone transfers without typing a URL.
|
||||
|
||||
Context menu (…) → **Show QR Code**, then point the other device's camera at the screen.
|
||||
|
||||
The QR decodes to the same URL as Copy Link. Same auth rules apply.
|
||||
The QR decodes to the same URL as Copy Link, and the same auth rules apply.
|
||||
|
||||
### Nintendo 3DS direct install
|
||||
|
||||
@@ -45,13 +45,13 @@ RomM's nginx is built with `mod_zip`, which streams a zip archive over HTTP with
|
||||
|
||||
### Multi-disc / multi-file games
|
||||
|
||||
When a game is stored as a folder (multi-disc, game + DLC, game + patch, etc.), clicking **Download** builds a zip on the fly containing the whole folder. The browser sees a zip download start immediately, no "packaging…" delay.
|
||||
When a game is stored as a folder (multi-disc, game + DLC, game + patch, etc.), clicking **Download** builds a zip on the fly containing the whole folder, and the browser sees a zip download start immediately with no "packaging…" delay.
|
||||
|
||||
### Bulk download from a gallery
|
||||
|
||||
Multi-select ROMs in any gallery (platform view, collection, search results) → toolbar → **Download selected**, producing a single zip with every selected ROM preserved in its platform folder structure.
|
||||
|
||||
No practical limit: the zip is streamed, so memory doesn't grow with selection size. Disk I/O and network bandwidth are the actual limits.
|
||||
No practical limit: the zip is streamed so memory doesn't grow with selection size, and disk I/O and network bandwidth are the actual limits.
|
||||
|
||||
## Third-party download auth
|
||||
|
||||
@@ -81,7 +81,7 @@ Some emulators can take an HTTP URL directly. Point them at the same URL the Cop
|
||||
|
||||
## Download history
|
||||
|
||||
Not tracked in 5.0. RomM doesn't log downloads for privacy reasons. Use your reverse proxy's access log if you need to audit.
|
||||
Not tracked in 5.0: RomM doesn't log downloads for privacy reasons, so use your reverse proxy's access log if you need to audit.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ The library is the heart of RomM. This page covers the day-to-day UI: the dashbo
|
||||
|
||||
## Dashboard
|
||||
|
||||
Home screen. Several **ribbons** of content:
|
||||
Home screen, with several **ribbons** of content:
|
||||
|
||||
- **Recently Added**: a carousel of the latest ROMs RomM has indexed.
|
||||
- **Continue Playing**: games with an active play session. See [Play Sessions](../getting-started/concepts.md#play-session).
|
||||
@@ -22,7 +22,7 @@ Each ribbon can be hidden or shown from **Profile → User Interface**, handy if
|
||||
|
||||
## Menu bar
|
||||
|
||||
Visible everywhere. Shortcuts to:
|
||||
Visible everywhere, with shortcuts to:
|
||||
|
||||
- **Search**: global search across every ROM, metadata field, and tag.
|
||||
- **Platforms**: drawer listing every platform as a link.
|
||||
@@ -34,7 +34,7 @@ Visible everywhere. Shortcuts to:
|
||||
|
||||
## Grid/list toggle
|
||||
|
||||
Every gallery (platform view, collection view, search results) has a grid-vs-list toggle in the top right. Grid is the default: card thumbnails at scale. List is denser: one row per ROM, sortable columns for title, release date, rating, playtime, region.
|
||||
Every gallery (platform view, collection view, search results) has a grid-vs-list toggle in the top right. Grid is the default (card thumbnails at scale), while list is denser: one row per ROM with sortable columns for title, release date, rating, playtime, and region.
|
||||
|
||||
## Game cards
|
||||
|
||||
|
||||
Reference in New Issue
Block a user