Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
458fba3
docs: document the 5.4 release
gantoine Oct 5, 2026
b876654
docs: humanize the 5.4 release prose
gantoine Oct 5, 2026
dbb15a2
docs: drop the password rules note from password reset
gantoine Oct 5, 2026
47e9904
docs: tighten the email page intro
gantoine Oct 5, 2026
6e9185f
docs: avoid repeating once in the email intro
gantoine Oct 5, 2026
56b31a2
docs: shorten the email channels bullet
gantoine Oct 5, 2026
cb33447
docs: cut restated context across the 5.4 pages
gantoine Oct 5, 2026
4d27230
docs: drop the plain-text note from the email page
gantoine Oct 5, 2026
cfa542e
docs: reword the ROMM_BASE_URL note on the email page
gantoine Oct 5, 2026
df61b26
docs: shorten the library conversion page description
gantoine Oct 5, 2026
980194f
docs: drop the defaults note from library conversion
gantoine Oct 5, 2026
81971f5
docs: tighten the backup warning in library conversion
gantoine Oct 5, 2026
536f1c5
docs: shorten the parental controls page description
gantoine Oct 5, 2026
fca51f3
docs: condense the age limit bullet
gantoine Oct 5, 2026
ce5efc2
docs: merge choppy sentence runs across the 5.4 pages
gantoine Oct 5, 2026
4430ec2
docs: drop filler RomM subjects and untangle clauses in the 5.4 pages
gantoine Oct 5, 2026
21a8b11
Fix review findings and fill gaps in 5.4 docs
gantoine Oct 5, 2026
0d42a5f
Merge RetroArch per-user file, device and manifest notes into one par…
gantoine Oct 5, 2026
4018934
Remove the upgrading page, its links and the 5.4 defaults warning
gantoine Oct 5, 2026
e3ad672
Drop leading article before EasyRPG RTP link
gantoine Oct 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/Navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ search:
- Administration
- [Overview](administration/index.md)
- [Users & Roles](administration/users-and-roles.md)
- [Parental Controls](administration/parental-controls.md)
- [Invitations & Registration](administration/invitations-and-registration.md)
- [Authentication](administration/authentication.md)
- [Email](administration/email.md)
- OIDC Setup
- [Overview](administration/oidc/index.md)
- [Authelia](administration/oidc/authelia.md)
Expand All @@ -42,8 +44,10 @@ search:
- [VoidAuth](administration/oidc/voidauth.md)
- [Scanning & Watcher](administration/scanning-and-watcher.md)
- [Scheduled Tasks](administration/scheduled-tasks.md)
- [Library Conversion](administration/library-conversion.md)
- [Server Stats](administration/server-stats.md)
- [Observability](administration/observability.md)
- [Audit Log](administration/audit-log.md)
- [Firmware Management](administration/firmware-management.md)
- Using RomM
- [Overview](using/index.md)
Expand All @@ -55,6 +59,7 @@ search:
- [Downloads](using/downloads.md)
- [Uploads](using/uploads.md)
- In-Browser Play
- [EasyRPG](using/in-browser-play/easyrpg.md)
- [EmulatorJS](using/in-browser-play/emulatorjs.md)
- [`js-dos`](using/in-browser-play/js-dos.md)
- [MS-DOS](using/in-browser-play/ms-dos.md)
Expand All @@ -64,11 +69,13 @@ search:
- [Migrating to webstation](using/emulator-streaming-migration.md)
- [Jukebox](using/jukebox.md)
- [Saves & States](using/saves-and-states.md)
- [Devices](using/devices.md)
- [RetroAchievements](using/retroachievements.md)
- [Walkthroughs](using/walkthroughs.md)
- [ROM Patcher](using/rom-patcher.md)
- [Netplay](using/netplay.md)
- [Account & Profile](using/account-and-profile.md)
- [Notifications](using/notifications.md)
- [Languages](using/languages.md)
- Platforms & Players
- [Overview](platforms/index.md)
Expand All @@ -78,6 +85,7 @@ search:
- [Overview](ecosystem/index.md)
- [First-Party Apps](ecosystem/first-party-apps.md)
- [Feed Clients](ecosystem/feed-clients.md)
- [RetroArch Cloud Sync](ecosystem/retroarch-cloud-sync.md)
- [Igir Collection Manager](ecosystem/igir.md)
- API & Development
- [Overview](developers/index.md)
Expand Down
79 changes: 79 additions & 0 deletions docs/administration/audit-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
title: Audit Log
description: A record of who downloaded, played, changed and signed in to what
---

# Audit Log

The audit log records who did what on the server: downloads and player launches, play sessions, uploads and edits, collection changes, scans and tasks, and security events like sign-ins, failed sign-ins and permission changes. Admins read it in the Events tab of the logs settings page, filtered by user, category and date, with a search over names and IP addresses.

Each event keeps the actor, the action, its target, when it happened, the client's IP address and, for a request made with a device-bound token, the [device](../using/devices.md) it came from. Names are copied into the event, so it still reads after the user or the game it names is deleted. Recording is best effort, so a request still succeeds when its event can't be written.

## What's recorded

Events fall into five categories:

| Category | Actions |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `consumption` | `rom.download`, `rom.bulk_download`, `rom.player_load`, `rom.play` |
| `library` | `rom.upload`, `rom.create`, `rom.edit`, `rom.match`, `rom.unmatch`, `rom.delete`, `rom.file_delete`, `platform.create`, `platform.edit`, `platform.delete`, `firmware.upload`, `firmware.delete`, `config.update` |
| `collections` | `collection.create`, `collection.edit`, `collection.delete`, `collection.add_roms`, `collection.remove_roms`, `smart_collection.create`, `smart_collection.edit`, `smart_collection.delete` |
| `operations` | `scan.start`, `scan.finish`, `scan.stop`, `task.run` |
| `security` | `auth.login`, `auth.login_failed`, `auth.password_reset_request`, `auth.password_reset`, `user.create`, `user.register`, `user.edit`, `user.delete`, `user.permissions_edit`, `permission_group.create`, `permission_group.edit`, `permission_group.delete`, `visibility.hide`, `visibility.unhide`, `client_token.create`, `client_token.regenerate`, `client_token.revoke`, `device.approve` |

Some actions and actors need more detail:

- **`rom.download` and `rom.player_load`** both come from the ROM content endpoint. When a player fetches the file to run it, the client passes `purpose=play` on `GET /api/roms/{id}/content/{file_name}` and the fetch is recorded as a player load. Any other fetch is a download (`purpose=download`, the default). A repeat of the same download by the same caller within 10 minutes counts as one event, so resumed and ranged downloads don't flood the log.
- **`rom.play`** is a [play session](../using/saves-and-states.md) reported by a player or a companion app, recorded at the time the session started.
- **Actors** are a user, an anonymous visitor (a [kiosk](authentication.md#kiosk-mode) guest, a download with endpoint auth turned off, or a failed sign-in for a username that doesn't exist), or the system for scheduled tasks and the filesystem watcher.

## Who can read it

| Caller | Sees |
| ------------------------------------ | --------------------------- |
| An admin with the `users.read` scope | Everyone's events |
| Any other signed-in user (`me.read`) | Only the events they caused |

Only admins get the Events tab in the UI, but other users can still read their own history through the API. Either way, events about a platform or game the caller [can't see](users-and-roles.md) are left out, so a hidden or age-restricted game doesn't leak through the log.

## Client IP addresses

Each event records the client address as the web server resolved it. Behind a reverse proxy, that's the address from `X-Forwarded-For`, which is only trusted when the proxy's own address is in `FORWARDED_ALLOW_IPS`. The default trusts loopback and the private ranges, so a proxy on a public address has to be added, or every event is logged as coming from the proxy (see [Reverse Proxy](../install/reverse-proxy.md)).

## Retention

Events are kept for 90 days by default, and a scheduled cleanup deletes older ones daily at `30 4 * * *`, in batches so the table stays writable while it runs.

```yaml
environment:
- AUDIT_LOG_RETENTION_DAYS=365 # 0 keeps events forever
```

With `AUDIT_LOG_RETENTION_DAYS=0` the cleanup task is turned off and the table only grows, so keep an eye on the database's size on a busy instance (see [Scheduled Tasks](scheduled-tasks.md)).

## API

`GET /api/audit-events` returns events newest first, `50` per page by default and at most `200`.

| Query param | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------- |
| `limit` | Page size, `1` to `200` |
| `offset` | Events to skip |
| `actor_id` | Only these users' events, repeatable. Ignored unless the caller sees everyone. |
| `action` | Only these actions, repeatable, such as `action=rom.download&action=rom.player_load` |
| `category` | Only these categories, repeatable: `consumption`, `library`, `collections`, `operations`, `security` |
| `target_type` | `rom`, `platform`, `firmware`, `collection`, `smart_collection`, `user`, `client_token`, `device` and so on |
| `target_id` | The target's id, used with `target_type` |
| `since` | ISO 8601 timestamp, inclusive |
| `until` | ISO 8601 timestamp, exclusive |
| `search` | Substring match on the actor's name, the target's name or the IP address |
| `max_id` | Pins later pages to the events the first page saw |

The response is a page of `items` with `total`, `limit`, `offset` and `max_id`. Pass the first page's `max_id` back on later pages, so events recorded while you page through don't shift the results.

```sh
curl -G https://romm.example.com/api/audit-events \
-H "Authorization: Bearer $ROMM_TOKEN" \
--data-urlencode "category=security" \
--data-urlencode "since=2026-10-01T00:00:00Z"
```
11 changes: 9 additions & 2 deletions docs/administration/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,16 @@ environment:
!!! warning "Keep a way in"
Before setting `DISABLE_USERPASS_LOGIN=true`, confirm that at least one Admin account can sign in via OIDC. If OIDC breaks and you've already disabled local login, your only way in is editing the container env.

### Admin-triggered password reset
### Password reset

Until email-based self-serve reset lands, admins set passwords manually for any user. The next login on that account will use the new password, but existing sessions remain valid until they expire.
A user who forgot their password can ask for a reset link from the sign-in page (`POST /api/forgot-password`), which works once within 10 minutes. Where it goes depends on the server:

- With [email](email.md) set up, `ROMM_BASE_URL` pointing at a real host, and an email address on the account, the link goes to that address, at most once a minute per user.
- Otherwise, or when the email can't be sent, the link is written to the container log for an admin to pass on.

The response is the same whether or not the username exists, so the form can't be used to find out which accounts exist. Requests for an existing account and completed resets are both recorded in the [audit log](audit-log.md).

Admins can also set any user's password directly, and the new password applies from the next login while existing sessions stay valid until they expire.

## OIDC

Expand Down
56 changes: 56 additions & 0 deletions docs/administration/email.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
title: Email
description: Send notification emails and password reset links over SMTP
---

# Email

RomM can send email through an SMTP server you provide, once both `SMTP_HOST` and `SMTP_FROM` are set. It's used for:

- Password reset links (see [Password reset](authentication.md#password-reset))
- Email [notification channels](../using/notifications.md#email)
- Confirmation codes that prove an address belongs to the user who added it as a channel

## Configuration

| Variable | Default | Description |
| --------------- | ---------- | ---------------------------------------------------------- |
| `SMTP_HOST` | | SMTP server host |
| `SMTP_PORT` | `587` | SMTP server port |
| `SMTP_USERNAME` | | Login for the SMTP server, left empty when it needs none |
| `SMTP_PASSWORD` | | Password for the SMTP server |
| `SMTP_FROM` | | Sender address, such as `romm@example.com` |
| `SMTP_SECURITY` | `starttls` | How the connection is secured: `starttls`, `tls` or `none` |

`SMTP_SECURITY` takes one of three modes:

- `starttls` connects in plain text and upgrades with STARTTLS before logging in, which is what port `587` expects.
- `tls` uses implicit TLS from the first byte, usually on port `465`.
- `none` sends everything, including the SMTP password, unencrypted, so keep it for a relay on the same host or a trusted network.

Any other value leaves email off rather than falling back to plain text. Certificates are checked against the system CAs.

```yaml
environment:
- SMTP_HOST=smtp.example.com
- SMTP_PORT=587
- SMTP_SECURITY=starttls
- SMTP_USERNAME=romm@example.com
- SMTP_PASSWORD=app-password-here
- SMTP_FROM=romm@example.com
```

For a provider that only offers implicit TLS, set `SMTP_PORT=465` and `SMTP_SECURITY=tls`. Many mail providers refuse an account's normal password over SMTP and want an app password or an SMTP-specific credential instead.

Set [`ROMM_BASE_URL`](../reference/environment-variables.md) to your instance's public URL, such as `https://romm.example.com`. Reset links are only emailed when it points at a real host, not `localhost`, a loopback address or the default `0.0.0.0`, and notification emails use it to link back into RomM.

## Testing it

`GET /api/heartbeat` reports the email status under `NOTIFICATIONS`:

- `EMAIL_ENABLED` is `true` once the `SMTP_*` settings are complete.
- `EMAILS_RESET_LINKS` is `true` when reset links will be emailed, which also needs `ROMM_BASE_URL`.

To check that mail actually goes out, add an email [notification channel](../using/notifications.md#email) for your own address. The confirmation code is the first message sent there, and if the SMTP server refuses that message, the request returns the server's error right away. A confirmed channel can send a test notification on demand too.

If a reset link can't be emailed, it's written to the container log instead, so check `docker logs romm` for `Could not email the reset link` along with the SMTP server's error.
Loading