# 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 | | `deploy.sh` | One-command deploy | | `/home/jgitta/kiosk-home/index.html` | Senior home screen | | `~/.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 `https://gitea.jgitta.com/jgitta/senior-kiosk` — master branch ## 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 _NET_WM_STATE` to check `_NET_WM_STATE_HIDDEN`, then `xdotool windowmap ` ### 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