8553705e47
Close the hamburger panel on outside click/focus via X11 (xinput/xdotool) without a fullscreen overlay; point home Email to Roundcube; sync home-screen-index.html in deploy/install; remove credentials.md from git and document private LAN-only Gitea usage. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
6.7 KiB
Markdown
120 lines
6.7 KiB
Markdown
# SeniorNet Kiosk — Project Context for AI Assistants
|
||
|
||
## What this project is
|
||
|
||
A locked-down, zero-maintenance browser workstation for elderly users. The senior never sees a desktop — only a full-screen Chromium window with a slim toolbar on top. No configuration, no maintenance required from the user.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ PyQt5 toolbar (72px, always-on-top, FramelessHint) │
|
||
│ [SeniorNet] [◀] [▶] [⌂] [☰ bookmarks] │
|
||
├─────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ Chromium --app mode │
|
||
│ (CDP port 9222 for navigation) │
|
||
│ │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- `browser.py` — main app. Launches both the PyQt5 toolbar and Chromium as a subprocess.
|
||
- Toolbar talks to Chromium via Chrome DevTools Protocol (CDP) over WebSocket on port 9222.
|
||
- Bookmarks stored in `bookmarks.json` (nested JSON, supports folders).
|
||
- Home screen: `/home/jgitta/kiosk-home/index.html`
|
||
|
||
## Machines
|
||
|
||
### kiosk-dev — 192.168.88.48 (SSH alias: `kiosk-dev`) ← ACTIVE DEV MACHINE
|
||
- Debian 13 Trixie, Proxmox VM 130 on jg-hud
|
||
- System Chromium: `/usr/lib/chromium/chromium` — NOT snap
|
||
- Repo at: `/home/jgitta/kiosk-browser/`
|
||
- This is the machine being actively developed and tested on
|
||
|
||
### Production kiosk — 192.168.88.44 (SSH alias: `kiosk`)
|
||
- Ubuntu 24.04 + snap Chromium — needs to be migrated to Debian to match kiosk-dev
|
||
- Currently offline / pending migration
|
||
|
||
## Deploy workflow
|
||
|
||
```bash
|
||
bash /home/jgitta/kiosk-browser/deploy.sh "describe your change"
|
||
```
|
||
|
||
This: commits + pushes to Gitea, restarts kiosk-dev immediately, and deploys to production if reachable.
|
||
|
||
**CRITICAL**: `kiosk-restart` must kill Chromium (`pkill -f "remote-debugging-port=9222"`) BEFORE killing browser.py. If only Python is killed, Chromium holds the SingletonLock and the new instance silently exits — deploys appear to work but nothing changes on screen.
|
||
|
||
`/usr/local/bin/kiosk-restart` on kiosk-dev:
|
||
```bash
|
||
pkill -f "remote-debugging-port=9222" || true
|
||
pkill -f "python3.*browser" || true
|
||
sleep 1
|
||
```
|
||
|
||
## Key files
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `browser.py` | Main application — toolbar + CDP + Chromium launcher |
|
||
| `bookmarks.json` | Nested bookmark structure |
|
||
| `home-screen-index.html` | Home screen source (deploy copies to `/home/jgitta/kiosk-home/index.html`) |
|
||
| `deploy.sh` | One-command deploy |
|
||
| `/home/jgitta/kiosk-home/index.html` | Senior home screen (live copy, outside repo) |
|
||
| `~/.config/openbox/autostart` | Auto-restart loop on crash |
|
||
| `/usr/local/bin/kiosk-restart` | Safe restart (kills Chromium + Python) |
|
||
| `/etc/X11/xorg.conf.d/50-kiosk-hardening.conf` | Blocks TTY switching |
|
||
|
||
## Gitea repo (private LAN only)
|
||
|
||
- **URL**: `git@gitea.jgitta.com:jgitta/senior-kiosk.git` (SSH) or `https://gitea.jgitta.com/jgitta/senior-kiosk.git` (HTTPS)
|
||
- **Branch**: `master`
|
||
- **Exposure**: Internal only — `gitea.jgitta.com` resolves on LAN/VPN (Caddy at 192.168.88.110). Not public internet.
|
||
- **Auth**: SSH key preferred (`git@gitea.jgitta.com:jgitta/senior-kiosk.git`) once the machine’s public key is in Gitea → Settings → SSH Keys. Until then use HTTPS without a token in the URL and `git config credential.helper store`. Rotate any token that was ever in a remote URL.
|
||
- **Deploy**: `bash deploy.sh "message"` — push to Gitea, sync home screen, `kiosk-restart`; production via SSH on 192.168.88.x / Tailscale.
|
||
- **Secrets**: `credentials.md` is gitignored; Roundcube passwords live on the mail server, not in this repo.
|
||
|
||
## Roundcube webmail
|
||
|
||
- URL: https://mail.jgitta.com/roundcube/ (proxied via Caddy at 192.168.88.110)
|
||
- LXC container 203 on jg-hud, IP: 192.168.88.45
|
||
- Ubuntu 24.04, Apache2, PHP, MariaDB, Roundcube 1.6.6
|
||
- Gmail IMAP: `ssl://imap.gmail.com:993`
|
||
- Autologin plugin: `/var/lib/roundcube/plugins/autologin/autologin.php`
|
||
- Logs: `/var/lib/roundcube/logs/autologin.log` and `errors.log`
|
||
- Run commands on container: `ssh jg-hud "pct exec 203 -- bash -c '...'"` (jg-hud is the Proxmox host)
|
||
- browser.py clears the Roundcube session cookie on every startup (forces fresh autologin)
|
||
|
||
## Known hard-won lessons
|
||
|
||
### Chromium
|
||
- `--no-sandbox` causes "unsupported flag" infobar — never use it on Debian system Chromium
|
||
- `--test-type` triggers Google CAPTCHA bot detection — never use it
|
||
- `--disable-blink-features=AutomationControlled` causes "unsupported flag" infobar — removed
|
||
- `--disable-infobars`, `--disable-session-crashed-bubble`, `--disable-save-password-bubble`, `--suppress-message-center-popups`, `--disable-translate` are all deprecated/removed in current Chromium — do not use
|
||
- Profile dir: `/home/jgitta/chromium-kiosk` (not hidden — snap couldn't write hidden dirs; kept this convention)
|
||
- Chromium adds its own flags via the Debian wrapper (`--show-component-extension-options` etc.) — ignore these, they're harmless
|
||
|
||
### PyQt5 / Openbox
|
||
- `QMenu` does NOT work under Openbox/X11 with `Qt.Tool | WindowStaysOnTopHint` parent — use a custom `QWidget` with the same flags instead (BookmarkPanel)
|
||
- Qt event filters only see clicks on Qt widgets — clicks that land on Chromium (separate X11 process) are invisible. Use a `QTimer` polling `QApplication.mouseButtons()` + `QCursor.pos()` to detect outside clicks.
|
||
- To prevent toolbar minimize: override `changeEvent`, detect `Qt.WindowMinimized`, call `QTimer.singleShot(50, self._self_restore)`
|
||
- To restore minimized Chromium: periodic `QTimer` + `xprop -id <wid> _NET_WM_STATE` to check `_NET_WM_STATE_HIDDEN`, then `xdotool windowmap <wid>`
|
||
|
||
### Roundcube autologin
|
||
- Plugin MUST call both `session->regenerate_id(false)` AND `session->set_auth_cookie()` after login
|
||
- Logs are in `/var/lib/roundcube/logs/` NOT `/var/log/roundcube/`
|
||
|
||
### Deployment
|
||
- `pkill -f browser.py` in SSH kills its own session (shell cmdline contains "browser.py") — use `kiosk-restart`
|
||
- Chromium must be killed before Python or SingletonLock prevents new instance from starting
|
||
|
||
## Infrastructure overview
|
||
|
||
- **Proxmox host**: jg-hud (main), siklos (192.168.88.27)
|
||
- **TrueNAS**: 192.168.88.24
|
||
- **Thinkstation** (dev desktop): 192.168.88.41
|
||
- **Caddy reverse proxy**: 192.168.88.110 — handles *.jgitta.com
|
||
- **Gitea**: gitea.jgitta.com (port 3002 internal)
|
||
- **Headscale**: self-hosted Tailscale on siklos
|