Claude c1370edc3e Migrate Display & Interaction menu; drop version number from installer filename; bump to v2.0.0
- menus/display.sh: second menu migrated onto lib/menu.sh + lib/config.sh,
  covering touch gesture mode, link navigation security, and the
  pause/keyboard/navigation button toggles (previously three separate
  Core Settings entries). Deliberately a different shape from Sites
  (toggle list vs. list CRUD) to exercise the framework more broadly.
  Wired into install.sh's top-level menu alongside Sites.

- Renamed ubuntu-based-kiosk-v1.0.3.sh -> ubuntu-based-kiosk.sh so the
  installer can be updated in place instead of growing a new
  version-numbered filename every release; released versions are now
  tracked via git history and the in-script changelog. Updated all
  Readme download/re-run commands accordingly. Older versioned files
  (ubuntu-based-kiosk-v*.sh, install_kiosk_*.sh) are left in place as
  archived releases.

- Bumped SCRIPT_VERSION to 2.0.0 (new script-level changelog entry) and
  the Readme version/changelog to match, given the new modular
  management path, the rename, and the two real bugs fixed along the
  way (settings clobbered on save, off-by-one in reorder).

Verified before moving on to the web admin work:
- Regression: re-ran the full Sites scratch-config test suite (add,
  edit, delete, reorder, home) - still clean, no invalid-input paths hit.
- New: scratch-config test for every display.sh action (touch mode,
  navigation security, all three toggles), confirming values persist
  through save/reload and that a previously-added site survives
  untouched across Display-menu saves.
- End-to-end: ran the real install.sh (not just sourced functions) as a
  genuine non-root, non-"kiosk" user with real sudo, driving actual menu
  input through Sites -> add a page -> Display -> toggle a setting ->
  exit. Confirmed final config.json on disk matches every action taken,
  and both guard clauses (run as root; no installed kiosk found) fire
  correctly.
2026-08-18 15:48:21 +00:00
2025-12-09 11:28:31 -05:00

Ubuntu Based Kiosk

Current Version: 2.0.0 (check script header for latest version) Built with Claude Sonnet 4.6 AI assistance License: GPL v3 - Keep derivatives open source Repository: https://github.com/outis1one/ubuntu-based-kiosk/


Target Systems

  • Ubuntu 24.04+ Server (minimal install recommended)
  • Raspberry Pi 4+ (with or without touchscreen) - untested
  • Laptops, desktops, all-in-ones, 2-in-1s
  • Touch support optional (works with keyboard/mouse)

⚠️ Security Notice

This is NOT suitable for secure locations or public kiosks.

  • Do NOT use as a replacement for hardened kiosk solutions
  • Designed for home/office/trusted environments only
  • Use entirely at your own risk
  • No warranty or security guarantees provided

Purpose

Home/office kiosk for reusing old hardware, displaying:

  • Self-hosted services (Immich, MagicMirror2, Home Assistant, Plex, Jellyfin, Emby)
  • Web dashboards and digital signage
  • Photo slideshows and family calendars
  • Video conferencing (Jitsi, Zoom, Google Meet)
  • Any web-based content

Quick Install

# Install Ubuntu 24.04 Server
# ***Do not use "kiosk" as a user name when installing, the script creates a restricted user named kiosk and the script will not install if the sudo user/user name when setting up the system is "kiosk".***
# Configure WiFi if no ethernet available
# Enable SSH during installation

# Download and run the installer
wget https://github.com/outis1one/ubuntu-based-kiosk/raw/main/ubuntu-based-kiosk.sh
chmod +x ubuntu-based-kiosk.sh && ./ubuntu-based-kiosk.sh

The installer will guide you through configuration during setup.


Offline / Air-Gapped Download

If the kiosk machine can't reach GitHub directly (no browser, restrictive proxy, or you just prefer to grab the script on another computer and carry it over via USB), download it ahead of time instead of using the curl/wget one-liner above.

Note: This only avoids needing internet access to fetch the script. The installer itself still requires the kiosk machine to have internet access while it runs — it uses apt to install packages, pulls Node.js from NodeSource, and runs npm install to fetch Electron (~120MB). There is currently no fully air-gapped/offline package bundle.

On a machine with internet access:

# Option A: download just the installer script
wget https://github.com/outis1one/ubuntu-based-kiosk/raw/main/ubuntu-based-kiosk.sh

# Option B: download the whole repo as a ZIP (includes install.sh, addon scripts, and older archived installer versions)
wget https://github.com/outis1one/ubuntu-based-kiosk/archive/refs/heads/main.zip
unzip main.zip

Copy the downloaded .sh file (or the extracted ZIP contents) to a USB drive, then on the kiosk machine:

# Mount the USB drive and copy the script over, then:
chmod +x ubuntu-based-kiosk.sh
./ubuntu-based-kiosk.sh

The kiosk machine still needs a working internet connection (ethernet, or WiFi configured during Ubuntu install) for the script to complete.


Core Features

Multi-Site Management

  • Single or multiple sites with independent configurations
  • Named sites - Optional friendly names for easy identification in navigation menu
  • Auto-rotation - Sites rotate automatically based on duration
  • Manual sites - Duration = 0, accessible via swipe only, trigger inactivity timeout
  • Hidden sites - Duration = -1, PIN-protected access, trigger inactivity timeout
  • Home URL - Auto-return after inactivity on manual or hidden sites
  • Pause functionality - Temporarily pause rotation (configurable per-site)
  • Navigation menu - Quick access to all sites via key icon (top-left hot corner)

Touch Controls

  • 2-finger horizontal swipe - Switch between sites
  • 3-finger down swipe - Toggle hidden tabs (PIN required to show, swipe again to hide)
  • 1-finger swipe (dual mode) - Navigate within page (arrow keys)
  • On-screen keyboard - Auto-shows on text fields or click keyboard icon
  • Navigation menu - Click key icon (top-left) to access site list and gesture cheat sheet

On-Screen Keyboard

  • HTML-based keyboard with full QWERTY layout
  • Auto-show on text fields (optional)
  • 30-second auto-close after inactivity
  • Shift/Caps Lock support
  • Special characters via shift keys
  • Works alongside physical keyboard

Password Protection & Lockout (all are optional)

  • Session lockout after configured inactivity
  • Scheduled lockout at specific time daily
  • Display wake lockout - Require password after display schedule
  • Boot password option - Require password on system startup
  • Full screen blocking during lockout (no content visible)

Navigation Security

  • Restricted - Exact URL only, no link clicking
  • Same-origin - Links within same domain only (recommended)
  • Open - Unrestricted browsing (trusted environments only)

Scheduling System (all are optional)

  • Power schedule - Auto-shutdown and RTC wake (hardware dependent)
  • Display schedule - Turn display off/on at specific times
  • Quiet hours - Mute audio or stop Squeezelite during hours
  • Electron reload - Periodic restart to prevent memory leaks

Media Playback Intelligence

  • Auto-detects playing media (HTML5 video/audio, YouTube, Plex, Jellyfin, Emby)
  • Pauses rotation during media playback
  • Grace period after media stops
  • Respects user activity while watching

Optional Add-ons

Authentication

Authelia Auto-Login (SSO)

Automatically authenticates the kiosk against a self-hosted Authelia instance on every startup. The Authelia password is not stored in plain text — it is encrypted with AES-256-CBC using a key derived from the machine's unique /etc/machine-id, so the encrypted blob is useless on any other machine.

Access via menu: Addons → 5. Authelia Auto-Login

After running the addon it prints the full server-side setup, but the summary is below.

Kiosk side (SSH in and run the installer)
ssh user@kiosk-machine
./ubuntu-based-kiosk.sh
# Addons → 5. Authelia Auto-Login
# Enter your Authelia URL, username, and password when prompted
Authelia server side (Dockerized)

Step 1 — Generate the argon2 password hash (run on your Docker host):

docker run --rm authelia/authelia:latest \
  authelia crypto hash generate argon2 \
  --password 'yourpassword'

Copy the $argon2id$... output — that is your hash.

Step 2 — Add a kiosk user to ~/docker/authelia/config/users.yml:

kiosk:
  displayname: "Kiosk Display"
  password: '$argon2id$v=19$m=65536,t=3,p=4$<paste hash here>'
  email: kiosk@local.com
  groups:
    - kiosk

Step 3 — MERGE into ~/docker/authelia/config/configuration.yml (do not replace your existing config):

access_control — Find your existing access_control: block and add the kiosk rule as the first rule inside it.

Do NOT create a second access_control: block. YAML silently ignores duplicate keys — Authelia will never see the kiosk rule and the kiosk will get a white screen.

Authelia reads rules top-down — first match wins. The kiosk rule must sit above any two_factor wildcard rule, otherwise the wildcard matches first.

Why one_factor? The kiosk authenticates via the API (/api/firstfactor — password only). TOTP and WebAuthn require an interactive second step that is impossible from a script, so the kiosk group must use one_factor.

Before (your existing config):

access_control:
  default_policy: deny
  rules:
    - domain: '*.yourdomain.com'
      policy: two_factor

After (add kiosk rule above the two_factor rule — same block, not a new one):

access_control:
  default_policy: deny
  rules:
    - domain: '*.yourdomain.com'   # kiosk first — one_factor only
      subject: 'group:kiosk'
      policy: one_factor
    - domain: '*.yourdomain.com'   # existing rule stays below
      policy: two_factor

session — Keep your existing session block as-is; no changes needed. The kiosk re-authenticates via API on every startup so session expiry barely matters for it.

If you do not yet have a session block, add:

session:
  expiration: 8h
  inactivity: 1h
  remember_me: 7d
  cookies:
    - domain: yourdomain.com
      authelia_url: https://auth.yourdomain.com

Step 4 — Restart Authelia:

docker compose restart authelia
How it works

On every kiosk startup, Electron calls Authelia's /api/firstfactor endpoint with keepMeLoggedIn: true before any sites load. Authelia responds with a Set-Cookie header that Electron absorbs into its default session. All BrowserViews then load with that session cookie already present.

Because Electron's session persists to disk across reboots (/home/kiosk/.config/kiosk-app/), the cookie also survives restarts — the API call on startup just refreshes or extends it.

Authelia vs HTTP Basic Auth

Both can be used at the same time — they serve different purposes:

Method Where configured When to use
Authelia SSO Addons → Authelia Auto-Login (global) Sites protected by an Authelia reverse proxy
HTTP Basic Auth Per-site username/password in tab config Sites that show a browser popup asking for credentials

Communication

  • Easy Asterisk Intercom - Voice communication and intercom system
    • Downloads latest version from Easy Asterisk repository
    • Automatic update detection and installation
    • Configuration preservation during updates
    • Full Asterisk PBX integration
    • SIP/PJSIP support for IP phones and softphones

Audio

  • Lyrion Music Server (LMS) - Formerly Logitech Media Server
  • Squeezelite Player - Network audio player for LMS
  • PipeWire audio - Modern Linux audio stack
  • Volume controls - Hardware button support

Printing

  • CUPS printing system
  • Network printer sharing
  • IPP Everywhere support
  • PDF printing via cups-pdf

Remote Access

  • VNC - x11vnc for remote desktop
  • WireGuard VPN - Config paste support
  • Tailscale VPN - Auth key support
  • Netbird VPN - Setup key support

Advanced

  • Emergency WiFi Hotspot - Auto-starts if no internet after boot (configurable during install)
  • Virtual Console Access - Ctrl+Alt+F1-F8 terminal login (can enable/disable during install)
  • Complete Uninstall - Full system cleanup and kiosk removal
  • SSH remote access - For configuration and troubleshooting

What This Script Installs

Core Components

  • Electron v42.x (Chromium-based app framework)
  • Node.js v20.x with npm
  • Openbox - Lightweight window manager
  • LightDM - Display manager with autologin
  • xorg - X11 server and utilities
  • unclutter - Hide mouse cursor
  • Hardware acceleration - VAAPI, Mesa drivers

Audio Stack

  • PipeWire - Modern audio/video server
  • PipeWire-Pulse - PulseAudio compatibility
  • WirePlumber - Session manager
  • ALSA utilities

System Services

  • systemd-timesyncd - NTP time sync
  • acpid - Power button handling
  • ufw - Uncomplicated Firewall
  • Network Manager or netplan for networking

Development Tools

  • build-essential - GCC, make, etc.
  • Python 3 with evdev for PTT
  • jq - JSON processing
  • curl / git - Downloading and version control
  • net-tools - Legacy networking utilities (ifconfig, netstat, etc.)
  • ncdu - Disk usage analyzer for troubleshooting

System Behavior

Security & Lockdown

  • Autologin as kiosk user
  • Virtual consoles (Ctrl+Alt+F1-F8) - Optional, configurable during install
  • X server key combinations disabled (Ctrl+Alt+Backspace)
  • Right-click disabled in kiosk app
  • Screen blanking disabled with schedule awareness
  • DPMS management - Aggressive keep-alive with schedule respect

Audio Management

  • PipeWire watchdog - Auto-restart if audio fails
  • Volume persistence - Speakers 100%, Mic 100% and unmuted
  • Quiet hours aware - Respects audio schedules
  • User services - Audio runs under kiosk user

Network

  • WiFi configuration - WPA2, netplan-based
  • Multi-method WiFi scan - nmcli, iw, wpa_cli fallbacks
  • Watchdog support - Auto-revert bad WiFi configs
  • Emergency hotspot - Fallback if no internet

Maintenance & Troubleshooting

Service Management

# Restart kiosk display
sudo systemctl restart lightdm

# View Electron logs
sudo tail -f /home/kiosk/electron.log

# Check service status
systemctl status lightdm
systemctl status squeezelite
sudo systemctl --user -M kiosk@ status pipewire

Common Issues

No display after boot:

# Check LightDM status
sudo journalctl -u lightdm -n 50

# Verify kiosk user
id kiosk

# Check X11 authorization
sudo -u kiosk DISPLAY=:0 xdpyinfo

External monitor/TV (HDMI) shows nothing, or shows a cropped/scaled picture:

Any connected display beyond the primary is mirrored automatically at the primary's exact resolution (not the external display's own native resolution) — both at kiosk login/boot (via Openbox autostart) and live when plugged/unplugged afterward (via a udev rule that triggers kiosk-hotplug.service). Both paths call the same /usr/local/bin/kiosk-mirror-display.sh. If the external display doesn't natively list the primary's resolution (e.g. a 1366x768 laptop panel mirrored to a 1920x1080-native TV), a matching mode is generated on the fly with cvt and forced onto the output — most monitors/TVs accept a close, non-native CVT timing without issue, but a few strict ones may reject it (see below).

# List outputs and check if the external display is detected
sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr

# Look for your output (e.g. HDMI1/HDMI2/HDMI-1) as "connected" with a mode list.

# Check what the mirroring logic actually did (native mode vs. forced CVT mode, or failures)
journalctl | grep "KIOSK:" | tail -20

# Check whether the hotplug handler fired
sudo journalctl -u kiosk-hotplug.service -n 20

# Manually re-trigger it
sudo systemctl start kiosk-hotplug.service

# Run the mirroring logic by hand for more verbose output
sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority bash -x /usr/local/bin/kiosk-mirror-display.sh

If the output shows disconnected, it's a cabling/port/EDID issue, not software — try a different cable/port or a monitor known to work.

If a forced CVT mode is rejected by the display (blank screen only after mirroring runs, works fine before), that display's EDID doesn't accept out-of-spec timings — you'll need to manually pick one of its natively listed modes instead:

sudo -u kiosk DISPLAY=:0 XAUTHORITY=/home/kiosk/.Xauthority xrandr --output HDMI2 --mode 1360x768 --same-as eDP1

No sound over HDMI (audio only from laptop/built-in speakers):

Whenever an external display is connected/mirrored, kiosk-audio-route.sh switches PipeWire's default sink to whichever sink's name contains hdmi, and moves any already-playing audio stream onto it. It's called at kiosk login (after PipeWire is confirmed ready) and by kiosk-hotplug.service on every plug/unplug — see /usr/local/bin/kiosk-mirror-display.sh above for the display side of the same hotplug event.

# List sinks and confirm an HDMI one exists (name will contain "hdmi")
sudo -u kiosk pactl list sinks short

# Check what the routing logic actually did
journalctl | grep "KIOSK: audio routed\|KIOSK: failed to route" | tail -10

# Check current default sink
sudo -u kiosk pactl get-default-sink

# Manually re-trigger routing
sudo systemctl start kiosk-hotplug.service

# Force it by hand if needed (replace with your sink name from the list above)
sudo -u kiosk pactl set-default-sink alsa_output.pci-0000_00_1f.3.hdmi-stereo

If no sink name contains hdmi, the audio codec on that HDMI port either isn't exposed by ALSA/PipeWire on this hardware, or the monitor/TV doesn't report HDMI audio support in its EDID (common on monitors that only do video) — in that case there's no PipeWire-side fix, audio has to come from the laptop speakers or a separate cable.

Audio not working:

# Check PipeWire (use menu: Advanced → Audio Diagnostics)
sudo -u kiosk pactl info

# Restart audio
sudo systemctl restart lightdm

Touch not working:

# List input devices
xinput list

# Check Electron logs for touch events
sudo tail -f /home/kiosk/electron.log | grep TOUCH

# Test gestures (should show in logs):
# - 3-finger UP = "[TOUCH] 3-finger UP - show hidden tab"
# - 3-finger DOWN = "[TOUCH] 3-finger DOWN - return to normal tabs"
# - 2-finger HORIZONTAL = "[MANUAL] User switched tab..."

Hidden sites not showing:

# Check PIN file exists
ls -la /home/kiosk/kiosk-app/.jitsi-pin

# View current PIN
sudo cat /home/kiosk/kiosk-app/.jitsi-pin

# Check for hidden sites in config
sudo jq '.tabs[] | select(.duration == -1)' /home/kiosk/kiosk-app/config.json

# Check if inactivity timeout is working on hidden tabs
sudo tail -f /home/kiosk/electron.log | grep HOME
# Should show: "[HOME] HIDDEN IDLE: Xm Ys / Ym Ys"

Inactivity prompt not appearing:

# Check home tab configuration
sudo jq '.homeTabIndex, .inactivityTimeout' /home/kiosk/kiosk-app/config.json

# Watch for inactivity logging
sudo tail -f /home/kiosk/electron.log | grep HOME

# Manual site: "[HOME] 🏠 MANUAL IDLE: 1m 45s / 2m 0s"
# Hidden site: "[HOME] 🏠 HIDDEN IDLE: 1m 45s / 2m 0s"
# Prompt shown: "[HOME] 🔔 *** SHOWING PROMPT NOW (hidden tab) ***"

Keyboard not appearing:

# Check keyboard button setting
sudo grep enableKeyboardButton /home/kiosk/kiosk-app/config.json

# View keyboard events
sudo tail -f /home/kiosk/electron.log | grep KEYBOARD

Adding Printers to CUPS

1. Access CUPS Web Interface:

http://<kiosk-ip-address>:631/admin

Login with the username and password you used during Ubuntu installation.

2. Click "Add Printer"

3. Find Your Printer URI

CUPS needs a device URI to connect to your printer. Here's how to find it:

For Network Printers (Most Common):

From Windows, find the printer's URI:

  1. Right-click printer → Printer PropertiesPorts tab
  2. Look for the checked port, note the format:

HP Network Printers:

  • Windows shows: IP_192.168.1.100 or similar
  • CUPS URI: hp:/net/<printer-model>?ip=192.168.1.100
  • Alternative: socket://192.168.1.100:9100

Generic Network Printers (IPP):

  • Windows shows: http://192.168.1.100/ipp/print or similar
  • CUPS URI: ipp://192.168.1.100/ipp/print
  • Alternative: http://192.168.1.100:631/ipp/print

Generic Network Printers (Socket/JetDirect):

  • Windows shows: Standard TCP/IP Port on 192.168.1.100
  • CUPS URI: socket://192.168.1.100:9100
  • Port 9100 is standard for HP JetDirect protocol

USB Printers:

  • CUPS auto-detects these
  • URI looks like: usb://HP/LaserJet%20P1102
  • Select from "Local Printers" list in CUPS

4. Select Driver

After entering URI, CUPS will ask for a driver:

  • Search for your printer model
  • If not found, try "Generic PCL" or "Generic PostScript"
  • For HP printers, install hplip: sudo apt install hplip

5. Set as Default (Optional)

Administration → Set Default Printer

6. Print Test Page

Printers → Your Printer → Maintenance → Print Test Page

Quick Reference - Common URIs:

# HP Network Printer
hp:/net/HP_LaserJet_P3015?ip=192.168.1.100

# Generic Network (Socket/JetDirect - Port 9100)
socket://192.168.1.100:9100

# Generic Network (IPP)
ipp://192.168.1.100/ipp/print

# Shared Windows Printer
smb://WORKGROUP/COMPUTER/PrinterName

Troubleshooting:

  • Printer not responding: Check firewall, ensure kiosk can ping printer IP
  • Wrong driver: Try Generic PostScript or PCL drivers
  • Authentication failed: Verify Windows printer sharing is enabled
  • Can't find printer: Use lpinfo -v to list all available devices

Touch Gesture Quick Reference

Gesture Fingers Direction Action
Swipe 2 Left/Right Switch between sites
Swipe 1 Left/Right Navigate within page (arrow keys)
Swipe 3 Down Toggle hidden tabs (PIN required)

Keyboard Shortcuts:

  • Ctrl+Tab or Ctrl+] - Next tab
  • Ctrl+Shift+Tab or Ctrl+[ - Previous tab
  • Alt+Right/Left - Next/Previous tab
  • F10 or Ctrl+H - Toggle hidden tabs
  • Escape - Return to normal tabs (from hidden)
  • Ctrl+Alt+Delete or Ctrl+Alt+P - Power menu
  • Ctrl+K - Toggle keyboard

Menu System Access

# Run installer script again to access menu
./ubuntu-based-kiosk.sh

# Menu structure:
# 1. Core Settings - Sites, WiFi, schedules, passwords, full reinstall, complete uninstall
# 2. Addons - Authelia Auto-Login, Easy Asterisk Intercom, LMS, CUPS, VNC, VPNs
# 3. Advanced - Diagnostics, logs, Electron updates, virtual consoles, emergency hotspot
# 4. Restart Kiosk Display

Installing Easy Asterisk Intercom

The Easy Asterisk Intercom addon provides voice communication capabilities to your kiosk system.

Access the addon menu:

./ubuntu-based-kiosk.sh
# Select: 2) Addons
# Then: 4) Easy Asterisk Intercom

Features:

  • Automatic installation - Downloads and installs the latest version from the Easy Asterisk repository
  • Update detection - Checks for newer versions and prompts to update
  • Safe re-runs - Can be run multiple times without breaking existing configurations
  • Config preservation - Automatically backs up and restores configurations during updates
  • Full Asterisk PBX - Complete telephony features including SIP, extensions, voicemail

Installation behavior:

  • First install: Downloads latest version from https://github.com/outis1one/easy-asterisk
  • Already installed (latest): Prompts to re-run installation (preserves configs)
  • Update available: Prompts to update and shows version difference
  • All scenarios: Configuration files in /etc/asterisk/ and installation settings are preserved

Managing Easy Asterisk:

# Check installation status
systemctl status asterisk

# View Asterisk console
asterisk -rvvv

# Restart Asterisk
systemctl restart asterisk

# Configure intercom (rerun installation to update)
./ubuntu-based-kiosk.sh
# Select: 2) Addons → 4) Easy Asterisk Intercom

Installation location:

  • Installation files: /opt/easy-asterisk/
  • Configuration: /etc/asterisk/
  • Version tracking: /opt/easy-asterisk/.version
  • Config backups: /opt/easy-asterisk/config_backup/

Updating Electron

# Via menu: Advanced → Manual Electron Update (RECOMMENDED)
# The menu option automatically:
# - Creates backup before updating
# - Shows rollback instructions
# - Handles permissions correctly

# Or manually:
cd /home/kiosk/kiosk-app
sudo -u kiosk npm install electron@latest
sudo systemctl restart lightdm

Rollback if update fails:

The menu update creates automatic backups in /home/kiosk/electron-backup-<timestamp>/

# 1. Stop display
sudo systemctl stop lightdm

# 2. Find your backup (most recent)
ls -lt /home/kiosk/electron-backup-* | head -1

# 3. Restore backup files (replace timestamp with your backup)
BACKUP=/home/kiosk/electron-backup-<timestamp>
sudo cp $BACKUP/package.json /home/kiosk/kiosk-app/
sudo cp $BACKUP/package-lock.json /home/kiosk/kiosk-app/ 2>/dev/null || true

# 4. Remove failed install and reinstall previous version
sudo rm -rf /home/kiosk/kiosk-app/node_modules/electron
cd /home/kiosk/kiosk-app && sudo -u kiosk npm install --unsafe-perm

# 5. Fix permissions
sudo chown root:root /home/kiosk/kiosk-app/node_modules/electron/dist/chrome-sandbox
sudo chmod 4755 /home/kiosk/kiosk-app/node_modules/electron/dist/chrome-sandbox

# 6. Restart display
sudo systemctl start lightdm

Configuration Files

Main Config

/home/kiosk/kiosk-app/config.json

{
  "autoswitch": true,
  "swipeMode": "dual",
  "allowNavigation": "same-origin",
  "homeTabIndex": 0,
  "inactivityTimeout": 120,
  "enablePauseButton": true,
  "enableKeyboardButton": true,
  "enablePasswordProtection": false,
  "tabs": [
    {
      "url": "https://example.com",
      "duration": 180,
      "username": "",
      "password": ""
    }
  ]
}

Key Config Values

  • duration: >0 = auto-rotate (seconds), 0 = manual only, -1 = hidden
  • swipeMode: "dual" = 2-finger nav + 1-finger arrows, "standard" = 2-finger only
  • allowNavigation: "restricted" | "same-origin" | "open"
  • homeTabIndex: Tab to return to after inactivity (-1 = disabled)
  • inactivityTimeout: Seconds before showing "still here?" prompt
  • lockoutTimeout: Minutes of inactivity before lockout (0 = disabled)
  • lockoutAtTime: Daily lockout time in "HH:MM" format
  • requirePasswordOnBoot: true = password required on system startup

Note: Config files may contain lockoutActiveStart and lockoutActiveEnd fields from earlier versions. These are not currently functional and are ignored by the application.

Hidden Sites PIN

/home/kiosk/kiosk-app/.jitsi-pin

The PIN file controls access to hidden sites (duration = -1):

  • Default: 1234
  • Configure via: Main Menu → Core Settings → Sites → Configure Hidden Sites PIN
  • Disable PIN: Set content to NOPIN to allow any entry
  • Custom PIN: 4-8 digits
# Set custom PIN
echo "5678" | sudo -u kiosk tee /home/kiosk/kiosk-app/.jitsi-pin

# Disable PIN protection
echo "NOPIN" | sudo -u kiosk tee /home/kiosk/kiosk-app/.jitsi-pin

Advanced Features

Site Duration Modes

Auto-Rotate (duration > 0):

  • Site displays for specified seconds
  • Auto-advances to next rotation site
  • Pause button available
  • Respects media playback

Manual Only (duration = 0):

  • Site accessible via swipe
  • Never auto-rotates
  • No pause button (not needed)
  • Can be set as Home URL
  • Triggers inactivity timeout (returns to home after idle time)

Hidden (duration = -1):

  • Toggle visibility via 3-finger down swipe + PIN, or F10 key
  • Also use Escape key to return to normal tabs
  • PIN stored in /home/kiosk/kiosk-app/.jitsi-pin
  • Default PIN: 1234 (configurable via Sites menu)
  • PIN can be 4-8 digits or disabled completely
  • Hidden from normal rotation
  • Triggers inactivity timeout (returns to home after idle time, just like manual sites)

Why Use Hidden Tabs?

Hidden tabs are perfect for scenarios where you need access to sensitive or private content on a shared/public kiosk:

Private Communication:

  • Video conferencing (Jitsi Meet, Zoom, Google Meet)
  • Private messaging or chat applications
  • Internal communication tools for staff only
  • Conference room scheduling interfaces

Administrative Access:

  • Server administration panels (Proxmox, TrueNAS, router interfaces)
  • Security camera feeds
  • Home automation controls (Home Assistant, OpenHAB)
  • Network monitoring dashboards

Content Management:

  • Digital signage content editors
  • Photo album management (Immich, PhotoPrism)
  • Media server administration (Plex, Jellyfin)
  • Calendar and scheduling updates

Secure Entertainment:

  • Personal streaming accounts (prevent others from accessing your watch history)
  • Gaming platforms or cloud gaming services
  • Adult content controls (parental access only)
  • Personal social media (Facebook, Instagram, etc.)

Business Use Cases:

  • Employee time tracking systems
  • Inventory management interfaces
  • Point-of-sale backend access
  • Staff scheduling and shift management

Example Scenarios:

  1. Reception Kiosk: Public-facing sites rotate (directory, weather, news), but staff can swipe up with PIN to access appointment scheduling, visitor management, or internal messaging.

  2. Family Room Display: Displays photo slideshows, calendar, and weather, but parents can PIN-access streaming services, smart home controls, or security cameras.

  3. Digital Signage: Publicly shows announcements and menus, but managers can PIN-access the content management system to make updates.

  4. Conference Room Display: Shows meeting schedules and company news, but attendees can PIN-access video conferencing or presentation tools.

The hidden tab system provides a balance between public accessibility and private functionality without needing to physically access a terminal or reconfigure the system.

Why Use Named Sites?

Named sites provide user-friendly labels that make navigation and management easier, especially when dealing with multiple similar URLs or complex web addresses.

Benefits:

  • Easier Navigation: Click "Photo Gallery" instead of remembering "https://immich.mydomain.com:2283"
  • Better Organization: Quickly identify sites in the navigation menu without parsing URLs
  • User-Friendly: Non-technical users can find sites by name instead of domain
  • Cleaner Display: "Home Assistant" is more readable than "http://192.168.1.50:8123"
  • Professional Appearance: Business kiosks benefit from descriptive names over technical URLs

Use Cases:

Home/Family Kiosks:

Business Kiosks:

Digital Signage:

Multi-Location Setups:

  • "Building A Reception" and "Building B Reception" for identical URL structures
  • "Floor 1 Display" through "Floor 10 Display" for elevator kiosks
  • "East Wing" and "West Wing" for hospital navigation

Names are completely optional - if left blank, the URL will be displayed as usual. Configure names during initial setup or update them later via: Core Settings → Sites → Update site names.

Inactivity Extensions

When "Are you still here?" prompt appears (on manual or hidden sites):

  • "Yes, I'm still here" - Reset all timers, stay on current page
  • Time extensions (15m, 30m, 1h, 2h) - Pause rotation and inactivity
  • "No, go home" - Return to home URL immediately
  • Extensions pause BOTH rotation and lockout timers
  • Maximum extension: 4 hours (safety timeout)

Triggers on:

  • Manual sites (duration = 0) after inactivity timeout
  • Hidden sites (duration = -1) after inactivity timeout
  • Does NOT trigger on auto-rotating sites (duration > 0) - they use pause button instead

Lockout Behavior

Triggers:

  • Inactivity timeout expires (if configured)
  • Scheduled lockout time reached (if configured)
  • Display schedule wake-up (if password-on-wake enabled)
  • System boot (if requirePasswordOnBoot enabled)

During Lockout:

  • Full black screen (no content visible)
  • All browser views detached for security
  • Password prompt displayed
  • Limited power menu (no Reload option to prevent bypass)
  • Rotation and timers paused

After Unlock:

  • Returns to previous site
  • Timers reset
  • Normal operation resumes

Media Detection

Detects and pauses for:

  • HTML5 <video> and <audio> elements
  • YouTube embeds and direct links
  • Plex Web player
  • Jellyfin Web player
  • Emby Web player
  • Vimeo, Dailymotion, Twitch embeds

Display Schedule with Password

Example: Display off 22:00-06:00, optional password on wake

# Configure via menu: Core Settings → Power/Display/Quiet Hours
# Then: Core Settings → Password Protection

# Behavior:
# - Display turns off at 22:00 (hardware DPMS)
# - Display turns on at 06:00
# - If password protection enabled and configured for display wake:
#   - Password required to unlock
#   - Creates /home/kiosk/kiosk-app/.display-wake flag
#   - main.js detects flag and shows lockout screen
# - Otherwise, display just turns on normally

Installation & Management Features

Virtual Console Configuration

Virtual consoles provide terminal access via Ctrl+Alt+F1 through F8 keyboard combinations.

During Installation:

  • Prompted at the end of initial setup
  • Default: ENABLED (Ubuntu standard behavior)
  • Option to disable for enhanced security

Security Considerations:

Enabled (Default):

  • Allows manual terminal login for troubleshooting
  • Useful for SSH failures or network issues
  • Standard Ubuntu/Linux behavior
  • Can login with kiosk user credentials or main user account

Disabled (More Secure):

  • Blocks direct terminal access
  • Forces all access through SSH or menu system
  • Better for public kiosks or untrusted environments
  • Can still be re-enabled via Advanced menu (requires existing SSH access)

Post-Install Management:

# Access via menu: Advanced → Virtual Consoles (option 7)
# Note: Changes require restarting the display manager to take effect

# Manual enable
for i in {1..8}; do sudo systemctl unmask getty@tty$i.service; done
sudo systemctl daemon-reload
sudo systemctl restart lightdm

# Manual disable
for i in {1..8}; do sudo systemctl mask getty@tty$i.service; done
sudo systemctl daemon-reload
sudo systemctl restart lightdm

Important Notes:

  • Version 0.9.7-4 fixed Ctrl+Alt+F1-F8 key combinations (now properly enables/disables X11 VT switching)
  • If you enabled virtual consoles in an earlier version, you must re-enable them from the menu for keys to work
  • Changes require restarting lightdm: sudo systemctl restart lightdm

Typical TTY Layout:

  • TTY1-6: Login consoles (if enabled)
  • TTY7: Graphical kiosk display (X11/LightDM)
  • TTY8: Available for additional services

Emergency WiFi Hotspot

Automatically creates a WiFi access point when internet connectivity is lost.

During Installation:

  • Prompted at the end of initial setup
  • Optional configuration with custom SSID and password
  • Can be deferred and configured later

How It Works:

  1. Service monitors internet connectivity every 30 seconds
  2. When internet is lost, automatically:
    • Creates WiFi hotspot with configured credentials
    • Assigns IP address (default: 10.42.0.1)
    • Displays on-screen notification with connection details
    • Serves simple web page at http://10.42.0.1
  3. When internet returns, hotspot automatically shuts down

Use Cases:

  • Initial WiFi configuration without keyboard
  • Network troubleshooting when SSH is unavailable
  • Remote location setup without IT staff present
  • Automatic failover for temporary connectivity issues

Configuration:

# Access via menu: Advanced → Emergency Hotspot (option 9)

# Default credentials:
SSID: Kiosk-Emergency
Password: kioskhotspot123

# Once connected to hotspot:
# - SSH: ssh user@10.42.0.1
# - Web: http://10.42.0.1 (shows connection info)
# - Run installer script to reconfigure WiFi

Files Created:

  • /usr/local/bin/kiosk-emergency-hotspot - Main service script
  • /etc/systemd/system/kiosk-emergency-hotspot.service - Systemd service

Complete Uninstall

Full system cleanup that removes all kiosk components and restores the system to pre-installation state.

Access:

# Core Settings menu → option 11
./ubuntu-based-kiosk.sh
# Choose: Core Settings → Complete Uninstall

What Gets Removed:

  • Kiosk user and all user data
  • All Electron and Node.js installations
  • LightDM and Openbox window manager
  • All systemd services and timers
  • All kiosk scripts and configurations
  • CUPS printing system
  • VNC server
  • Emergency hotspot configuration
  • All scheduled tasks (power, display, quiet hours)

What Gets Preserved:

  • VPN configurations (if not manually removed)
  • System packages (Ubuntu base system)
  • Network configuration (WiFi, ethernet)
  • SSH server and settings
  • User accounts (except kiosk user)

Safety Features:

  • Requires typing "UNINSTALL" to confirm
  • Re-enables virtual consoles automatically
  • Stops all services before removal
  • Offers optional reboot after completion
  • No way to undo - creates clean slate

Use When:

  • Repurposing hardware for different use
  • Testing/development cleanup
  • Complete fresh start needed
  • Removing kiosk from production system

Third-Party Software Licenses

This project bundles or installs several open-source components under their respective licenses:

Electron

Chromium (bundled with Electron)

Node.js

npm Packages

  • Licenses: Vary by package (MIT, Apache-2.0, BSD, etc.)
  • Source: https://www.npmjs.com
  • Note: Check each package's LICENSE file individually

CUPS (Common Unix Printing System)

Squeezelite

Lyrion Music Server (formerly Logitech Media Server)

PipeWire

FFmpeg (if installed)

Unclutter-xfixes

Openbox

LightDM

x11vnc

WireGuard Tools

Tailscale

Netbird


Ubuntu Based Kiosk Project License

The Ubuntu Based Kiosk installer script and original code components are licensed under GPL-3.0-or-later.

See the LICENSE file in the repository for full terms.

Keep derivatives open source - Any modifications or derivative works must also be released under GPL-3.0-or-later.


Disclaimer

  • No warranty of any kind is provided
  • Use at your own risk
  • Not suitable for security-critical deployments
  • Aggregates open-source software governed by their respective licenses
  • The authors make no warranty regarding modifications by downstream integrators

Modular Management (new, in progress)

The 12,000+ line single-file installer works, but every menu lives in the same file as everything else, which makes small changes risky. We're pulling the menu system out into small, independently editable files as groundwork for the planned web-based GUI (same modules will back both the terminal menu and the web UI, so they can't drift apart).

What's here so far:

  • lib/menu.sh — a generic numbered-menu framework (auto-numbers entries, always offers 0 to exit/return, validated input helpers). Menu files just declare their labels and handler functions; they don't hand-roll echo/case loops.
  • lib/config.sh — the single place that reads/writes config.json.
  • menus/sites.shSites & Page Timing, fully migrated: add, edit, delete, and reorder pages, and set the duration/timing mode (auto-rotate / manual / hidden) and home page — as a working proof of concept for this approach.
  • menus/display.shDisplay & Interaction: touch gesture mode, link navigation security, and the on-screen pause/keyboard/navigation button toggles. A second proof of concept covering a different menu shape (settings toggles vs. the list CRUD in Sites).
  • install.sh — entry point for the modular tool. Run it against an already-installed kiosk:
    git clone https://github.com/outis1one/ubuntu-based-kiosk/
    cd ubuntu-based-kiosk
    ./install.sh
    

This does not yet replace first-time installation — that's still the single-file script above (Quick Install). The rest of Core Settings/Addons/Advanced will move into menus/*.sh the same way, one menu at a time, and install.sh will eventually take over the whole show_main_menu from the legacy script.


Project Status & Future Plans

Current Version: 2.0.0

Recent Updates (v2.0.0):

  • Modular management path: new lib/menu.sh (reusable numbered-menu framework: auto-numbered entries, 0 always exits/returns) and lib/config.sh (single load/save for config.json), with menus migrating into menus/*.sh one at a time — Sites & Page Timing and Display & Interaction are migrated so far. Run via ./install.sh after cloning the repo, against an already-installed kiosk (see "Modular Management" below). Groundwork for the planned web-based GUI, which will share this same lib/config.sh layer.
  • Bug fix: the old Sites menu could save config.json without first loading swipe/navigation/lockout settings, silently resetting them to script defaults.
  • Bug fix: reordering sites had an off-by-one that left the moved site one slot short of the requested position.
  • Renamed installer: the main script is now ubuntu-based-kiosk.sh (no version number in the filename), updated in place going forward. Released versions are tracked via git history and this changelog instead of the filename; older ubuntu-based-kiosk-v*.sh / install_kiosk_*.sh files remain in the repo as archived releases.

Previous (v1.0.3):

  • HDMI/external display mirroring: any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically at the primary's exact resolution — generating a custom cvt mode if the external display doesn't natively list it — both at kiosk login/boot and live on plug/unplug via a new udev-triggered kiosk-hotplug.service. Previously the external output was left inactive even when detected by X, and would otherwise mirror at its own native resolution instead of matching the kiosk panel
  • HDMI audio routing: audio now follows the same hotplug event — the default PipeWire sink automatically switches to the HDMI audio output when an external display is connected/mirrored, and back to the built-in sink when it's disconnected (kiosk-audio-route.sh)
  • Package install: installer now also installs net-tools and ncdu (alongside the already-installed curl and git)
  • Touch input fix (keyring): added --password-store=basic to the Electron launch. Under LightDM autologin the GNOME keyring stays locked; when Chromium accessed it, the keyring unlock dialog grabbed all keyboard/touch input — the kiosk rendered fine but ignored every tap and keypress. This flag stops Electron from using the keyring, so the dialog never appears.
  • Touch gesture fix (libinput): any touch screen is now forced to the libinput driver via /etc/X11/xorg.conf.d/99-finger-libinput.conf (matched by hardware capability, so it works on any brand and never affects keyboards, mice, or the pen/stylus). Some drivers — notably wacom — only do single-touch pointer emulation and never pass real multitouch to Chromium, so 1-finger and 2-finger swipe gestures could not fire. libinput delivers proper multitouch.
  • Upgrade reliability: start.sh is now refreshed on every upgrade alongside main.js and preload.js
  • Upgrade fix: node_modules no longer wiped during upgrade — preserves the ~120MB Electron binary so upgrades don't fail on slow/unreliable connections
  • Authelia fix: 10-second timeout on Authelia login fetch prevents white screen when Authelia is slow to respond
  • Authelia config: clearer YAML merge instructions with before/after examples to prevent duplicate access_control: block mistake
  • Upgrade fix: config backup cleanup now uses sudo rm to avoid "Operation not permitted" error

Previous (v1.0.2):

  • Authelia Auto-Login addon (Addons → 5) — authenticates with Authelia SSO on every startup; password stored encrypted (AES-256 keyed from machine ID, not plain text); prints full Dockerized Authelia server-side setup after configuration
  • Bug fix: PipeWire config dirs were created as root at step [5.5/27], causing "Permission denied" on fresh installs
  • README: install commands no longer hardcode version numbers — always fetch latest from GitHub

Previous (v1.0.1):

  • Node.js upgrade: 20 LTS → 22 LTS
  • Electron upgrade: v39/41 → v42.x

Previous (v1.0.0):

  • Upgrade fix: file extraction now correctly verifies written files on Ubuntu 22.04+ systems where the kiosk home directory has restrictive permissions (750)
  • Install fix: sudo credentials primed upfront before the long apt install step, preventing cache expiry at the timezone prompt
  • Timezone fix: fallback to direct /etc/localtime symlink when timedatectl fails via D-Bus

Previous (v0.9.9.1):

  • Silent upgrade: no user input required, extracts files directly from script
  • Power button fixes
  • Import allows selecting backup by number instead of typing path

Previous (v0.9.8):

  • Easy Asterisk Intercom addon with automatic updates
  • Smart version detection and configuration preservation
  • Named websites feature for user-friendly site identification
  • Navigation menu with key icon

Planned Features:

  • Web-based GUI configuration interface
  • All-in-one ISO installer
  • Enhanced Raspberry Pi support and testing

Known Limitations:

  • Raspberry Pi support untested in production
  • No web-based configuration (CLI menu only)
  • Extended desktop not supported — additional connected displays (e.g. HDMI-out to a monitor/TV) are automatically mirrored, not extended

Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Follow existing code style
  4. Keep GPL-3.0 compatibility
  5. Submit pull request with clear description

Support & Community


Credits

Built with assistance from Claude Sonnet 4.6 (Anthropic AI)

Special thanks to the maintainers of all upstream projects that make Ubuntu Based Kiosk possible.


Last Updated: June 15, 2026 Version: 1.0.3

S
Description
Mirror of outis1one/ubuntu-based-kiosk from GitHub
Readme GPL-3.0
4.1 MiB
Languages
Shell 94.1%
JavaScript 4.7%
HTML 0.9%
CSS 0.3%