mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-11-03 09:12:19 +01:00
The Interface tab carried data-demo-lock as a whole, which greyed out the sidebar layout breakpoint along with the toast settings. The breakpoint is localStorage — per browser, invisible to every other visitor — so it belongs with theme, notification permission and the quick-access bar, all of which were already allowed. The toast settings are server-side and shared, so the lock moves onto the uiSettingsForm that owns them. Audited every other locked region against the localStorage key list: this was the only per-browser control caught by a lock. Notifications, theme, FAB placement/size/position and the sidebar breakpoint are now the complete set of preferences a demo visitor may change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
7.8 KiB
Markdown
177 lines
7.8 KiB
Markdown
# Demo Mode
|
|
|
|
mc-webui has no login. On a home network that is the right trade: everyone who can reach
|
|
the port is someone you already trust. It stops being the right trade the moment you hand
|
|
the address to people you have never met — to show the project off, to collect feedback,
|
|
or because Google Play wants twelve testers who use the app for a fortnight.
|
|
|
|
Demo mode is for exactly that instance. Set one variable and everything that reconfigures
|
|
the radio, deletes data or reaches this server is refused, while reading and chatting keep
|
|
working. Visitors see the whole interface, greyed out where it would bite.
|
|
|
|
Everything here is opt-in. Skip this document and nothing about your installation changes.
|
|
|
|
---
|
|
|
|
## What it stops
|
|
|
|
The guard is **default-deny**: every write is refused unless it is on a short list of
|
|
things a visitor may still do. That is the important property — an endpoint added to
|
|
mc-webui next month is locked the day it is written, not the day someone remembers to
|
|
lock it.
|
|
|
|
**Refused while locked:**
|
|
|
|
| Area | Examples |
|
|
|---|---|
|
|
| Device configuration | Name, coordinates, advert location policy, path hash mode |
|
|
| Radio | Frequency, bandwidth, spreading factor, coding rate, TX power |
|
|
| Device actions | Reboot, advert, flood advert |
|
|
| The console | Every command that changes anything — `set`, `reboot`, `remove_channel`, `msg`, `login`, … |
|
|
| Channels | Create, join, delete, change region scope |
|
|
| Contacts | Delete, bulk cleanup, push to device, block, ignore, protect, path edits |
|
|
| Repeaters | Login, settings, CLI, reboot, **power off**, stored passwords |
|
|
| This server | Update trigger (rebuilds the containers), backups, database vacuum, retention settings |
|
|
| External publishing | Observer/MQTT settings and brokers, diagnostics capture and upload |
|
|
| Shared preferences | UI language, chat settings, toast notification settings, DM retry policy, analyzer and region registries |
|
|
| Secrets | Repeater passwords, backup downloads, diagnostic captures, the server log |
|
|
|
|
**Still works:**
|
|
|
|
- Reading everything — messages, channels, contacts, the map, the path analyzer
|
|
- **Sending messages**, in channels and as DMs, and resending
|
|
- Marking messages read
|
|
- Every preference that lives in the visitor's own browser, because changing it affects
|
|
nobody else: theme, desktop notification permission, the sidebar layout breakpoint,
|
|
whether the quick-access bar is shown, where each item sits (bar or menu), and the
|
|
bar's size, spacing and position
|
|
- The console, for read-only commands: `infos`, `status`, `stats`, `bat`, `ver`, `help`,
|
|
`contacts`, `contacts_all`, `contact_info`, `pending_contacts`, `channels`, `path`, `get`
|
|
|
|
Two of those deserve a note. **Sending is allowed on purpose** — a demo nobody can type
|
|
into is not much of a demo — but it does mean visitors transmit from your node, under
|
|
your node's name. If that is not acceptable, say so and it can be locked too. And
|
|
`get_channel` is deliberately *not* in the read-only console list, because it prints a
|
|
channel's shared key.
|
|
|
|
Language is locked because mc-webui stores it server-side: one visitor switching to Polish
|
|
switches it for everyone. Theme is not, because that one is per-browser.
|
|
|
|
---
|
|
|
|
## Turning it on
|
|
|
|
Edit `.env` in your mc-webui directory:
|
|
|
|
```bash
|
|
MC_DEMO=true
|
|
```
|
|
|
|
Then rebuild as usual:
|
|
|
|
```bash
|
|
docker compose up -d --build
|
|
```
|
|
|
|
The log says what it decided, once, at startup:
|
|
|
|
```
|
|
Demo mode ON — writes are refused unless the caller is unlocked
|
|
Demo mode: no trusted networks configured
|
|
Demo mode: unlock code is set
|
|
```
|
|
|
|
Read those three lines after every deploy. A demo instance that quietly failed to lock
|
|
looks exactly like one that locked correctly — until a tester finds the difference.
|
|
|
|
---
|
|
|
|
## Keeping full access for yourself
|
|
|
|
With only `MC_DEMO=true`, the instance is locked for everybody, including you. The startup
|
|
log warns about it. There are two ways back in, and they work independently — set either,
|
|
or both.
|
|
|
|
### By unlock code — works from anywhere
|
|
|
|
```bash
|
|
MC_DEMO_UNLOCK_CODE=choose-something-long-and-unguessable
|
|
```
|
|
|
|
Open **Settings**, and at the bottom of the panel there is an *Operator access* box. Type
|
|
the code once and the browser is remembered for 30 days, on any network. There is a *Lock
|
|
again* button in the same place when you want to see the instance as a visitor does.
|
|
|
|
The cookie stores an HMAC of the code, never the code itself, and it is `HttpOnly`. A
|
|
wrong guess costs a one-second delay and is logged with the caller's address. Make the
|
|
code long anyway: it is the only thing between a stranger and your radio, and there is no
|
|
lockout beyond that delay.
|
|
|
|
This is the option to use when the instance is published through a tunnel or a proxy —
|
|
which is most of the time.
|
|
|
|
### By network — works with no typing
|
|
|
|
```bash
|
|
MC_DEMO_TRUSTED_NETS=192.168.0.0/16,10.0.0.0/8
|
|
```
|
|
|
|
Comma-separated CIDRs. Anyone whose address falls inside one of them is never restricted,
|
|
so from your own LAN the app simply behaves as it always has. IPv4 and IPv6 both work, and
|
|
an unparseable entry is skipped with a warning rather than throwing the rest away.
|
|
|
|
> ### ⚠️ The trap worth reading twice
|
|
>
|
|
> **If mc-webui is published through a tunnel (cloudflared, ngrok, a VPN box) or a reverse
|
|
> proxy, every visitor arrives from that one address.** If the tunnel runs on your LAN —
|
|
> and it usually does — that address sits inside the very network you just listed as
|
|
> trusted. Taken at face value, a trusted-network list would unlock the instance for the
|
|
> entire internet at once.
|
|
>
|
|
> mc-webui refuses to fall into this. When a request carries a proxy header
|
|
> (`X-Forwarded-For`, `X-Real-IP`, `Forwarded`, `CF-Connecting-IP`) and `MC_TRUST_PROXY`
|
|
> is off, the address is treated as the proxy's rather than the visitor's, and is **not**
|
|
> matched against the trusted list. The startup log warns when your configuration is in
|
|
> this shape.
|
|
>
|
|
> So: use `MC_DEMO_TRUSTED_NETS` when the port is reached **directly** from your LAN. When
|
|
> the app is behind a tunnel or a proxy, either set `MC_TRUST_PROXY=true` — only correct
|
|
> if the app is reached *exclusively* through that proxy, see
|
|
> [HTTPS Setup](https-setup.md) — or skip trusted networks entirely and use the unlock
|
|
> code, which does not care how the request arrived.
|
|
|
|
---
|
|
|
|
## What a visitor sees
|
|
|
|
Locked controls are **greyed out, not hidden**. The instance is shared to show mc-webui
|
|
off, and a feature nobody can see is a feature nobody is impressed by — so the Settings
|
|
panel still lists every tab, the radio fields still show the real values, and the console
|
|
still works for the commands that only read.
|
|
|
|
- A yellow **DEMO** badge sits next to the version in the menu, so it is obvious at a
|
|
glance which instance a screenshot or a bug report came from
|
|
- A short notice at the top of Settings, and another above the console input, explains
|
|
why things are grey
|
|
- Clicking a locked control produces a toast rather than a silent no-op
|
|
- Advert, flood advert and the system log are removed from the menu and the quick-access
|
|
buttons entirely — a greyed-out floating button reads as broken rather than deliberate
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
**The frontend is not the guard.** Greying out is a courtesy; the refusal happens on the
|
|
server, in front of every `/api` route, on the console socket and on the log socket. A
|
|
stale page, a crafted request or a direct `curl` all get the same 403.
|
|
|
|
**Demo mode is not authentication.** It stops a visitor from changing things. It does not
|
|
stop them from reading your messages and contacts, because that is the point of the demo.
|
|
If you need the instance genuinely private, put a login in front of it — NPM's *Access
|
|
Lists*, described in [HTTPS Setup](https-setup.md), do that in a few clicks, and the two
|
|
compose perfectly well.
|
|
|
|
**Changing the code invalidates old cookies.** The cookie is derived from
|
|
`MC_DEMO_UNLOCK_CODE`, so editing it in `.env` and rebuilding logs every unlocked browser
|
|
back out — which is also how you revoke access you regret.
|