c8534c967c
Co-authored-by: Cursor <cursoragent@cursor.com>
7.2 KiB
7.2 KiB
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 /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:
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 (see also jgitta/homelab-configs)
Source of truth for infra: https://gitea.jgitta.com/jgitta/homelab-configs
| Piece | Detail |
|---|---|
| Gitea app | Docker on siklos 192.168.88.27 — HTTP 3002, SSH 2222 (not 22) |
| Public URL | gitea.jgitta.com → Caddy 192.168.88.110 → proxies to Gitea :3002 |
| DNS | MikroTik 192.168.88.1 / siklos; public DNS may resolve to WAN IP (still reaches Caddy :443) |
This repo (senior-kiosk):
- HTTPS (usual — what worked before):
https://gitea.jgitta.com/jgitta/senior-kiosk.git— through Caddy. Auth via Gitea username + access token (git config credential.helper store), or token in URL (legacy on kiosk-dev). - SSH: Gitea SSH is on port 2222, not 22. Plain
git@gitea.jgitta.com:...hits port 22 on the WAN and will not accept your Gitea password. Use either:ssh://git@192.168.88.27:2222/jgitta/senior-kiosk.git(LAN), or~/.ssh/config:Host gitea.jgitta.com→Port 2222(requires WAN/LAN port-forward 2222→siklos:2222 if using the hostname)
- Branch:
master - Deploy:
bash deploy.sh "message"— push, sync home screen,kiosk-restart - Secrets:
credentials.mdgitignored
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.loganderrors.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-sandboxcauses "unsupported flag" infobar — never use it on Debian system Chromium--test-typetriggers Google CAPTCHA bot detection — never use it--disable-blink-features=AutomationControlledcauses "unsupported flag" infobar — removed--disable-infobars,--disable-session-crashed-bubble,--disable-save-password-bubble,--suppress-message-center-popups,--disable-translateare 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-optionsetc.) — ignore these, they're harmless
PyQt5 / Openbox
QMenudoes NOT work under Openbox/X11 withQt.Tool | WindowStaysOnTopHintparent — use a customQWidgetwith 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
QTimerpollingQApplication.mouseButtons()+QCursor.pos()to detect outside clicks. - To prevent toolbar minimize: override
changeEvent, detectQt.WindowMinimized, callQTimer.singleShot(50, self._self_restore) - To restore minimized Chromium: periodic
QTimer+xprop -id <wid> _NET_WM_STATEto check_NET_WM_STATE_HIDDEN, thenxdotool windowmap <wid>
Roundcube autologin
- Plugin MUST call both
session->regenerate_id(false)ANDsession->set_auth_cookie()after login - Logs are in
/var/lib/roundcube/logs/NOT/var/log/roundcube/
Deployment
pkill -f browser.pyin SSH kills its own session (shell cmdline contains "browser.py") — usekiosk-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 — terminates HTTPS for
*.jgitta.com(includinggitea.jgitta.com→ Gitea :3002) - Gitea: behind Caddy at
gitea.jgitta.com; app listens on port 3002 on the Gitea host - Headscale: self-hosted Tailscale on siklos