# MPS — Martinhal Presentation System A self-hosted app that turns any presentation into a link and a QR code. Upload a **video, PowerPoint, Word doc, Excel sheet, PDF, or image**; the app converts it to a browser-friendly format and generates a QR code. Anyone who scans it watches along on their own phone — **live and in sync with the presenter**, or browsing at their own pace. Everything runs in a single Docker container. No cloud, no third-party services except the mail account you choose to connect for email sharing. **Version 1.7** · © 2026 Martinhal IT — Joao Vaz --- ## What's new in 1.7 - **Present Local Only** opens **two browser tabs**: the presenter console (to drive the talk, exactly like **Present**) and a second, chrome-free tab showing the presentation only — no header text, no control buttons. The second tab mirrors the console live, so it's ideal for a projector or second screen. Both open as normal tabs (not pop-up windows). #### Since 1.4 - **Simpler login screen** — just the credentials, nothing else. - **End session** button in the presenter bar (left of Exit): it **invalidates the QR code** and disconnects everyone watching, so the link can't be reused afterwards. - **Refresh regenerates codes** — pressing **Refresh** on the dashboard issues a fresh QR code for every presentation, so links from a finished talk stop working. - The presenter bar now shows a single **QR Code** button (opens the large, scannable page); the old "Share / QR" pop-over was removed. #### Since 1.3 - **First-run setup.** The very first time you open the app it asks you to **create the administrator account** — no default password to remember or change. - **Rebranded** as MPS — Martinhal Presentation System, with the MPS logo across the app. - **QR Code button** on every ready presentation opens the code on its own page in **large format** — ideal for putting on a projector. It has copy-link, fullscreen and print actions. - **Bigger presentation cards** on the host dashboard (twice as wide, 50% taller) so the QR preview and controls are easier to use. ### From 1.2 Login page and user accounts, admin-only user management, mail settings and activity logs, email sharing, PurelyMail SMTP+IMAP with a copy saved to **Sent**, and First/Last slide controls. --- ## Quick start (Docker Compose) ```bash # Point QR codes at an address phones can actually reach (LAN IP or public URL). export PUBLIC_BASE_URL=http://192.168.1.50:3000 docker compose up --build -d ``` Open `http://:3000`. On first launch you'll be asked to **create the admin account**. After that, sign in, upload a file, and hit **Present**. Show the QR code (**Share / QR**, or the **QR Code** button for a full-screen page) and the room scans to join. > **Unattended installs:** if you'd rather not use the setup screen, set `ADMIN_USERNAME` > and `ADMIN_PASSWORD` before the first start and an admin will be pre-created from those > values instead. They're ignored once an account exists. ### Plain Docker ```bash docker build -t mps . docker run -d --name mps \ -p 3000:3000 \ -e PUBLIC_BASE_URL=http://192.168.1.50:3000 \ -v qr_data:/data \ mps ``` ### Without Docker (Node 18+) Requires `libreoffice`, `ffmpeg`, and fonts on the host. ```bash npm install npm run fetch-vendor # vendors PDF.js for offline use npm start ``` --- ## Updating (no data loss) All state — user accounts, mail settings, logs, uploaded decks — lives under the `/data` volume, never inside the app code. To update: 1. Unzip the new version over your existing project folder (code files are replaced; your `data/` and the Docker `qr_data` volume are left untouched). 2. Rebuild and restart: ```bash docker compose up --build -d ``` Your admin account, users, presentations, and mail configuration carry straight over, and the first-run setup screen does **not** reappear. --- ## Users & roles | Role | Can do | |---|---| | **admin** | Everything: manage users, configure mail, view logs, see and manage **all** presentations. | | **user** | Sign in, upload and present **their own** decks, share them (link, QR, email). | The first admin is created during setup. Manage further accounts at **/users** (admins only). The app prevents removing or demoting the last remaining admin, and you can't delete your own account. --- ## Sharing a presentation Each ready presentation offers several ways to get people in: - **QR Code** — the presenter bar opens a dedicated full-screen page with a large, scannable code (plus copy-link, fullscreen, and print). Great for a projector. - **End session** — invalidates the QR code and disconnects viewers when you're done. - **Copy link** — the plain audience URL. - **Email** — sends the link and QR straight to recipients (requires mail set up). --- ## Email sharing (PurelyMail) 1. Sign in as an admin → **Manage users → Mail**. 2. Tick **Enable email sending**, enter the PurelyMail **account email** and **password**. Servers are prefilled and match PurelyMail's settings: | | Host | Port | Security | |---|---|---|---| | SMTP (send) | `smtp.purelymail.com` | `465` | SSL/TLS | | IMAP (Sent copy) | `imap.purelymail.com` | `993` | SSL/TLS | 3. Save, then **Send test** to confirm it works. Every message is delivered over SMTP and a copy is appended to the **Sent** folder over IMAP, so it appears in your normal mail client. The stored mail password lives only in `/data/config.json` on your server and is never sent back to the browser. --- ## Presenting - **Slides:** `« First` · `‹ Prev` · `Next ›` · `Last »`, or the keys `←` `→` `Home` `End`. - **Present Local Only:** opens the presenter console plus a separate controls-free tab that mirrors it for a projector/second screen. If the second tab doesn't open, allow pop-ups for the site. - **Synced ↔ Free browse:** in Synced mode the audience follows you; in Free browse they move on their own. Audience members who wander off get a one-tap "Rejoin presenter". - **Video:** play / pause / seek are broadcast to everyone. Viewers tap once to allow sound (browsers block autoplay audio until then). --- ## Supported formats | Input | Handled as | Tool | |---|---|---| | `.pdf` | paged deck | served directly | | `.pptx .ppt .odp .pps .ppsx` | paged deck | LibreOffice → PDF | | `.docx .doc .odt .rtf .txt .md` | paged deck | LibreOffice → PDF | | `.xlsx .xls .ods .csv .tsv` | paged deck | LibreOffice → PDF | | `.mp4 .webm .ogg` | synced video | served directly | | `.mov .avi .mkv .wmv .flv .3gp .mpeg .ts` | synced video | ffmpeg → MP4 | | `.png .jpg .jpeg .gif .webp .svg .bmp .avif` | image | served directly | --- ## Configuration | Variable | Default | Purpose | |---|---|---| | `PUBLIC_BASE_URL` | derived from request | Absolute URL encoded in QR codes / links. **Set this.** | | `ADMIN_USERNAME` | `admin` | Only for unattended pre-seed (see Quick start). | | `ADMIN_PASSWORD` | *(unset)* | If set on first run, pre-creates an admin instead of showing setup. | | `ADMIN_EMAIL` | *(empty)* | Email for a pre-seeded admin. | | `MAX_UPLOAD_MB` | `500` | Maximum upload size. | | `PORT` | `3000` | Listen port. | | `DATA_DIR` | `/data` (Docker) | Where all state is stored. Mount as a volume. | | `CONVERT_TIMEOUT_MS` | `240000` | Per-file conversion timeout. | --- ## Security notes - Put a reverse proxy with **HTTPS** in front for anything beyond a trusted LAN, and set `PUBLIC_BASE_URL` to the https address. - Passwords are stored as salted scrypt hashes. Login sessions are HttpOnly cookies. - The container runs as a non-root user; all state stays on the mounted volume. ## License MIT.