Commit Graph
374 Commits
Author SHA1 Message Date
Claude 1b16bcf3ee Migrate CUPS Printing addon; restructure install.sh into Core Settings/Addons/Advanced; bump to v2.5.0
First Addon migrated: menus/addon_cups.sh (install, reconfigure for
network access, complete uninstall/purge). Different risk profile from
everything migrated so far - it genuinely mutates real system state
(apt install/remove --purge, /etc/cups, ufw) at fixed paths CUPS itself
doesn't let us relocate, unlike the systemd/cron/bin paths this project
already controls via $SYSTEMD_DIR etc. Only the polkit rule's directory
is parameterized ($POLKIT_DIR, lib/config.sh, since that one is ours to
place); everything else gets full command-level `sudo` stubbing in
every test - there is no scratch equivalent for a real apt-managed
subsystem's own file layout. Also added $BUILD_USER (the admin account
actually running the tool, as opposed to $KIOSK_USER) since CUPS needs
to grant it lpadmin group membership.

Restructured install.sh's top-level menu into Core Settings / Addons /
Advanced (matching the legacy tool) instead of one flat list, now that
Addons exists as its own category - cheap to do with one item in it,
much more annoying to retrofit once the flat list has fifteen.

Two bugs caught and fixed before they shipped:

- A "wait for CUPS to start" retry loop used a bare `cmd1 && cmd2 &&
  break` as its body while "simplifying" the legacy script's `if cmd1
  && cmd2; then break; fi`. Being inside a loop doesn't protect a bare
  &&/|| list from set -e - only if/while/until conditions and the
  protected side of &&/|| do that - so the first command failing on an
  early iteration (near-certain right after a fresh install, before
  CUPS has actually started) would have crashed the entire session.
  Restored the `if` form; noted the lesson in the file's own header
  comment since it's a general trap, not CUPS-specific.

- Resolved real uncertainty, rather than assuming: how far does
  run_menu's `handler || true` guard (v2.1.0) actually protect? Wrote a
  minimal isolated test (a bare `false` three function calls deep,
  called via `outer || true` at the top) and confirmed bash's errexit
  exemption for the left side of `||` covers the *entire* evaluation,
  arbitrarily deep through function calls - not just the immediately
  invoked function. So the session-crash risk this project has been
  chasing since v2.1.0 is already covered end-to-end by that one fix.
  Per-statement guards (`|| true`, explicit `if`) still earn their keep
  for a different reason: without them a deep failure bubbles silently
  past the menu actually responsible for it to wherever the nearest
  `|| true` happens to sit, which can be several menu levels above
  where the user actually was - not a crash, but a confusing jump.

Verified:
- Full regression: re-ran every existing scratch-config/stub test suite
  after the lib/config.sh change (new $BUILD_USER/$POLKIT_DIR) and
  after the install.sh restructuring - all still clean.
- New scratch/stub test for addon_cups.sh: full state-machine coverage
  (not installed -> decline -> install -> running -> reconfigure ->
  stopped -> start -> uninstall decline -> uninstall confirm -> not
  installed again) with every `sudo` call intercepted and only `rm`
  targeting the scratch $POLKIT_DIR ever actually executed; confirmed
  the polkit rule's content and that declining install makes zero sudo
  calls. Added both apt-failure paths (update fails, install fails)
  and confirmed the tool reports clearly and returns to the menu
  instead of dying, exercising the exact bug class just fixed.
- End-to-end: ran the real install.sh as a genuine non-root, non-
  "kiosk" user through the full new three-level structure - Core
  Settings -> Sites -> back -> back, Addons -> CUPS -> declined install
  (using this container's real, unstubbed dpkg check, correctly
  reporting "not installed" and making no apt/systemctl calls) -> back
  -> back, Advanced -> Diagnostics -> System status -> back -> back ->
  Exit. Zero invalid-choice errors, clean exit code 0 throughout.
2026-08-18 19:10:43 +00:00
Claude 459da53182 Migrate Diagnostics menu; bump to v2.4.0
Deliberately skipped Upgrade/Full Reinstall/Complete Uninstall for now:
all three are large (130-250 lines), genuinely destructive (wipe/
reinstall the kiosk), and Upgrade specifically is coupled to the legacy
script's own self-extraction mechanism (it greps its own running source
for embedded heredocs to pull out main.js/preload.js) - there's no
modular equivalent to migrate it to yet, since those files don't exist
as separate assets outside the monolith. Migrated Diagnostics instead:
4 of the legacy Advanced menu's 12 items (System Status, View Logs,
Audio Diagnostics, Network Test), all read-only except one optional
"play a test sound?" prompt - a deliberate change of pace with no
destructive-action risk to design around, after Sites/WiFi/Power.

- lib/menu.sh: ported get_vpn_ips alongside get_ip_address.
- menus/diagnostics.sh: straight port, using $KIOSK_USER/$KIOSK_HOME
  throughout instead of the legacy code's mix of the variable and a
  hardcoded "kiosk" literal.

Bug fixed, same set -e-safety class as v2.1.0's run_menu fix and
v2.3.0's netplan/systemctl fixes, but a bigger batch this time: nearly
every diagnostic command here was a bare unguarded statement whose
*expected, common* failure - no lightdm running, no audio hardware, no
network, missing log files, ping/nslookup not even installed - would
have crashed the entire session instead of reporting "not found" and
continuing. A diagnostics tool has to be the most crash-proof code in
the project, since it exists to run when something is already broken.
Fixed at every call site: systemctl status | head, tail on lightdm's
log, journalctl, ping, nslookup, and three pactl-backed variable
assignments.

Also noted for future menus in this migration: writing `local var;` and
`var=$(cmd)` as separate statements (good practice, and how several
earlier real bugs were caught) removes an accidental safety net -
`local x=$(cmd)` on one line masks the substitution's exit code with
`local`'s own always-success status. Splitting them is correct, but
each split assignment needs an explicit `|| true` (or real fallback)
where failure is expected and non-fatal, rather than relying on that
masking by accident. Caught three instances of exactly this while
writing this file fresh, not just porting old bugs.

Verified:
- Full regression: re-ran every existing scratch-config/stub test suite
  (sites, display, timezone/pin, lockout, power schedule + RTC, wifi) -
  all still clean after the lib/menu.sh change.
- New test for diagnostics.sh, exercised mostly for real (no
  destructive-mutation risk here, so minimal stubbing needed): system
  status, all three log views (including the "no such file" paths for
  lightdm log and electron log), full 8-step audio diagnostic with test
  sound declined, and network test - all report gracefully instead of
  crashing, confirmed by re-running after each fix until every bare
  unguarded statement was accounted for.
- End-to-end: ran the real install.sh as a genuine non-root, non-
  "kiosk" user, navigating Diagnostics -> System status -> View Logs ->
  System journal -> Audio diagnostics (declined test sound) -> Network
  test -> exit. Confirmed every diagnostic path completes and returns
  to its menu cleanly (exit code 0) even with ping/nslookup missing and
  no audio hardware/network present in this environment.
2026-08-18 18:48:26 +00:00
Claude 2375bf5eab Migrate WiFi and Power/Display/Quiet Hours menus; bump to v2.3.0
By far the riskiest menus migrated so far. Both can affect real system
state outside config.json in ways that are hard to reverse: WiFi
rewrites live netplan config and, over SSH, can disconnect the very
session configuring it; power scheduling can shut the physical machine
down and wake it via RTC.

- lib/config.sh: new $SYSTEMD_DIR/$CRON_D_DIR/$BIN_DIR/$NETPLAN_DIR,
  same `: "${VAR:=default}"` pattern as $KIOSK_DIR. Nothing under
  menus/ hardcodes /etc/systemd/system, /etc/cron.d, /usr/local/bin, or
  /etc/netplan directly, so every test in this change points them at
  scratch space instead of ever touching this sandbox's real systemd
  units, cron, or network config.
- lib/menu.sh: ported get_ip_address (also fixing its "No IP" fallback,
  which never actually fired before - `hostname -I | awk` always exits
  0 even on empty output).
- menus/wifi.sh: apply_wifi_config split out from wifi_menu specifically
  so tests can drive the netplan-writing logic without needing real
  scan hardware. Preserves the legacy netplan backup, 60s SSH watchdog,
  and restore-on-failure behavior exactly.
- menus/power_schedule.sh: power schedule (+ RTC wake), display
  schedule, quiet hours, and an Electron reload timer (with its own
  nested run_menu, mirroring the legacy configured/not-configured
  dispatch), plus remove-all. Deliberately excludes the legacy
  dispatcher's "Test schedules & system" - a shared diagnostics submenu
  (audio/network/keyboard tests) that isn't specific to scheduling and
  belongs with a future Advanced/Diagnostics migration instead.

Bugs found and fixed along the way, none papered over:
- The legacy dispatcher refused to open "Configure power schedule" at
  all without RTC hardware, even though shutdown-only mode never needed
  RTC. Now always available.
- None of the six HH:MM prompts across these menus (shutdown, wake,
  display off/on, quiet start/end, custom Electron reload time) were
  validated before - plain `read`, no format check. All now go through
  ask_time.
- set -e safety (same class as the v2.1.0 run_menu fix), three more
  instances: `ls *.yaml` when no netplan file exists still fails under
  pipefail even with stderr silenced (masked in practice by cloud-init
  usually leaving a file behind); the restore-and-reapply `netplan
  apply` after an initial failure was a bare unguarded statement; and
  `systemctl enable`/`start` after writing each of the four timer pairs
  was unguarded too - caught only by testing in an environment without
  a live systemd, but a real enable/start failure on actual hardware
  (bad unit, daemon-reload skipped, ...) would hit the exact same crash.
  Added a shared enable_and_start_timers() helper used at all four call
  sites; all now report a clear warning and return to the menu instead
  of taking the session down.

Testing discipline for this round, given the risk:
- No automated test calls the real netplan/nmcli/iw/wpa_cli/systemctl -
  confirmed no WiFi tools or `wl*` interface exist in this sandbox, so
  wifi_menu's own tools-check safely short-circuits before touching
  anything; apply_wifi_config's actual YAML/backup/failure-recovery
  logic is tested with sudo/netplan/get_ip_address stubbed instead.
- One stubbing pitfall caught and fixed in the test itself: `nohup sudo
  bash "$watchdog" ... &` execs nohup as a real external binary, which
  then execs the real sudo - a bash function stub named `sudo` does NOT
  intercept that, only stubbing `nohup` itself does. Verified via pgrep
  that no real watchdog process or `sleep 60` was ever spawned.
- power_schedule.sh tested with SYSTEMD_DIR/CRON_D_DIR/BIN_DIR pointed
  at scratch dirs and only `sudo systemctl` stubbed (tee/rm/chmod/cp
  left real, since they only ever touch scratch paths): full lifecycle
  for all four schedule types plus remove-all, the RTC-available branch
  (including the wake-time-before-shutdown-time hour/day wraparound
  arithmetic) via a stubbed rtc_wake_available, and the new
  enable_and_start_timers failure path via a stub that fails `enable`
  specifically.
- End-to-end: ran the real install.sh as a genuine non-root, non-
  "kiosk" user for both menus. WiFi correctly short-circuits on missing
  tools without crashing. Power/Display/Quiet Hours (SYSTEMD_DIR/
  CRON_D_DIR/BIN_DIR redirected to scratch space) configured all four
  schedule types in sequence including the nested Electron Reload menu,
  survived four consecutive real "systemctl enable/start failed"
  warnings (this container has no live systemd) without the session
  dying, then removed everything - confirmed the scratch dirs ended up
  empty and config.json was never touched (correctly out of scope for
  this menu).
2026-08-18 18:28:49 +00:00
Claude 8672c15469 Migrate Password Protection & Lockout menu; add missing ask_time helper; bump to v2.2.0
Fifth menu migrated onto lib/menu.sh + lib/config.sh: menus/lockout.sh
covers enable/disable, changing the password, inactivity timeout, daily
lock time, and boot password. The password is SHA-256 hashed before
it's ever assigned to LOCKOUT_PASSWORD (matching main.js's comparison
logic) - verified by test that the stored value is the correct hash and
never plaintext.

Rewrote the legacy configure_password_protection's linear "ask
everything, confirm save at the end" wizard as the same immediate-save
pattern used by every other migrated menu: each action (change
password, change timeout, toggle boot password, ...) is a complete,
standalone change, consistent with Sites/Display/Timezone/Hidden PIN.
LOCKOUT_ACTIVE_START/END are deliberately left untouched - per the
Readme they're inert leftover fields the app ignores, so lib/config.sh
just carries whatever is already in config.json through unchanged.

Testing this menu surfaced a real gap before it ever shipped: lib/menu.sh
never had ask_time/validate_time at all (only validate_integer/ask_integer,
ask_url, etc were ported when the framework was first built) - "set a
daily lock time" would have failed for every single user with
"ask_time: command not found". Ported both from the legacy script.

Also promoted the ON/OFF toggle-label helper (previously private to
menus/display.sh as display_onoff) to a shared onoff() in lib/menu.sh,
since menus/lockout.sh needed the same thing and menu files should only
ever depend on lib/, never on each other.

Bumped SCRIPT_VERSION to 2.2.0 with matching changelog entries in the
script header and Readme, and updated "Modular Management" to list the
new menu and drop Password Protection & Lockout from the "not yet
migrated" list.

Verified:
- Full regression: re-ran the Sites, Display, Timezone/PIN scratch-config
  suites after every change in this round (the onoff refactor, and again
  after adding ask_time) - all still clean.
- New scratch-config test for lockout.sh: enable (password+timeout+daily
  lock+boot toggle), independently recomputed the expected SHA-256 hash
  and confirmed it matches config.json exactly, change password, change
  timeout, clear daily lock, toggle boot password, disable (confirmed
  every field clears), and that the menu builder's options correctly
  differ between the enabled and disabled states.
- End-to-end: ran the real install.sh as a genuine non-root, non-"kiosk"
  user - Lockout menu -> enable protection with a real password entered
  via the masked prompt -> set 20m timeout, 23:00 daily lock, boot
  password on -> confirmed the menu redraws with the new state -> clean
  exit (code 0). Checked the resulting config.json and file permissions
  on disk.
2026-08-18 16:48:30 +00:00
Claude 074b2ec2e3 Migrate Timezone and Hidden Site PIN menus; harden menu framework against set -e; bump to v2.1.0
Two more menus migrated onto lib/menu.sh + lib/config.sh, chosen
specifically because neither touches config.json - a third and fourth
shape for the framework (a system command via timedatectl, and a flat
PIN file), on top of Sites' list CRUD and Display's JSON toggles.

- menus/timezone.sh: also replaces the legacy script's hand-numbered
  18-entry case statement with a plain data list (TIMEZONE_COMMON_ZONES)
  plus one handler that reads the number run_menu hands it - adding or
  removing a zone never touches numbering anywhere else. Required a
  small run_menu addition: handlers now receive the chosen 1-based
  number as $1, so one handler can serve a whole data-driven list
  instead of needing a wrapper function per entry.
- menus/hidden_pin.sh: set/disable/reset the PIN gating hidden pages.

Testing menus/timezone.sh surfaced a real bug before it ever shipped:
this whole tool runs under `set -e`, and set_timezone() rejecting an
invalid zone via a bare `return 1` as its last statement took down the
*entire* install.sh session, not just that one action - a single typo
would silently drop the user back to their shell. Fixed at the
framework level in lib/menu.sh (run_menu now absorbs a failed handler's
exit code) rather than patching set_timezone alone, since any future
menu could hit the same trap. Verified against the real install.sh as a
genuine non-root user: an invalid timezone now logs an error and
redraws the Timezone menu instead of killing the session (confirmed
exit code 0 at the end of the run). Note this specific hazard was
introduced by this session's own return-1 idiom, not inherited from the
legacy script, which never uses a bare return 1 in these functions.

Also per the user: left the old configure_sites/configure_touch_controls/
configure_navigation_security/configure_optional_features functions in
ubuntu-based-kiosk.sh untouched for now (still carrying the v2.0.0
settings-clobber and reorder bugs) rather than removing them - they'll
be retired in one pass once enough of Core Settings/Addons/Advanced is
migrated. Bumped SCRIPT_VERSION to 2.1.0 with matching changelog entries
in the script header and Readme, and updated the Readme's "Modular
Management" section to state plainly what is and isn't migrated yet.

Verified:
- Full regression: re-ran the Sites and Display scratch-config test
  suites against the updated run_menu signature - both still clean.
- New scratch-config tests for hidden_pin.sh (set/mismatch/reject/
  disable/reset, correct file permissions) and timezone.sh (builder
  entry count, common-zone pick by index, manual entry with legacy
  US/* alias normalization, region search + cancel, invalid-zone
  rejection) - all correct, with timedatectl/sudo stubbed only where
  needed to avoid mutating this sandbox's real system clock/timezone.
- End-to-end: ran the real install.sh as a genuine non-root, non-"kiosk"
  user, navigating Timezone -> manual entry -> invalid zone -> confirmed
  no crash and a normal return to the menu, then Hidden Site PIN -> set
  a PIN -> confirmed the file on disk (mode 600, correct content) ->
  clean exit (code 0).
2026-08-18 16:33:12 +00:00
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
Claude 21a0768c8c Add modular menu framework, migrate Sites & Page Timing onto it
Start of pulling the menu system out of the 12k-line single-file
installer so individual menus can change without risking the rest of
the script (network, VNC, addons, etc). This is groundwork for the
planned web-based management UI, which will share the same lib/config.sh
read/write layer instead of duplicating it.

- lib/menu.sh: generic numbered-menu framework (auto-numbered entries,
  "0" always exits/returns) plus the validated input helpers menus need.
- lib/config.sh: single load/save for config.json. Fixes a latent bug
  where the old Sites menu wrote config.json without first loading
  swipe/navigation/lockout settings, silently resetting them to
  defaults on save.
- menus/sites.sh: Sites & Page Timing fully migrated - add/edit/delete/
  reorder pages, set duration (auto-rotate/manual/hidden) and home page.
  Also fixes an off-by-one in the ported reorder logic (moving an item
  landed one slot short of the requested position) caught by testing.
- install.sh: new entry point for managing an already-installed kiosk
  via `git clone` + `./install.sh`, wired to the Sites menu. Does not
  yet replace first-time provisioning, which still uses the existing
  single-file installer.

All new site CRUD/reorder/home-page paths were exercised against a
scratch config.json (add with/without basic auth, edit duration, set
home + timeout, 2- and 3-item reorders in both directions, delete) to
confirm the resulting config.json matches expectations.
2026-08-18 15:26:16 +00:00
Outis ccf73d41bd Merge pull request #110 from outis1one/claude/offline-install-readme-kfpr5u
Claude/offline install readme kfpr5u
2026-08-02 19:46:41 -04:00
Claude 10c4c22f44 Route audio to HDMI automatically when an external display is connected
Previously nothing switched PipeWire's default sink, so audio always stayed on the laptop/built-in speakers regardless of HDMI mirroring. Adds /usr/local/bin/kiosk-audio-route.sh, which finds a sink whose name contains 'hdmi' and makes it the default (moving any already-playing streams onto it) whenever an external display is connected, falling back to the non-HDMI sink when it isn't. Called from autostart once PipeWire is confirmed ready, and from kiosk-hotplug.sh alongside the existing display mirroring on every plug/unplug event. Documents the behavior and manual fallback in the README.
2026-08-02 22:54:23 +00:00
Claude 6f6d34512e Mirror external display at the kiosk's exact resolution, not its own native mode
Previously mirroring used 'xrandr --auto', which picks the external output's own native resolution (e.g. a TV's 1920x1080) rather than matching the kiosk/laptop panel's resolution. Extracts the shared mirroring logic (previously duplicated between autostart and the hotplug handler) into /usr/local/bin/kiosk-mirror-display.sh, which now checks whether the external output natively lists the primary's resolution and uses it directly, or generates a matching mode with cvt and forces it via --newmode/--addmode when it doesn't. Documents the behavior and a manual fallback for displays that reject non-native CVT timings in the README.
2026-08-02 22:51:08 +00:00
Claude f01781de41 Add live HDMI/display hotplug handling via udev + systemd
Adds a udev rule (DRM 'change' events) that triggers a new kiosk-hotplug.service, which re-runs the same xrandr mirroring logic as the Openbox autostart script. External displays plugged in after boot now get mirrored without requiring a login or lightdm restart. Named kiosk-hotplug (not kiosk-display-*) to avoid collision with the existing kiosk-display-* wildcard cleanup in remove_all_schedules(). Also removes the udev rule during Complete Uninstall and documents the behavior in the README.
2026-08-02 22:37:39 +00:00
Claude 68a7345f5b Mirror external HDMI/monitor output onto primary display automatically
Openbox autostart now detects any connected output beyond the primary and mirrors the kiosk content onto it via xrandr, instead of leaving detected-but-unused external displays dark. Also documents the behavior, its limitation (applies at session start, not live hotplug), and adds an HDMI troubleshooting section to the README.
2026-08-02 22:34:19 +00:00
Claude b5d37fa646 Install net-tools and ncdu; curl/git already covered
apt install already includes curl and git; adds net-tools and ncdu to the same idempotent apt install so they're present on fresh installs without duplicating already-installed packages.
2026-08-02 22:15:15 +00:00
Outis de9c97aa06 Merge pull request #109 from outis1one/claude/offline-install-readme-kfpr5u
Add offline/air-gapped download instructions to README
2026-08-02 18:11:50 -04:00
Claude ca00674e4c Add offline/air-gapped download instructions to README
Clarifies that the installer script can be downloaded on another machine and transferred via USB, while noting the kiosk machine still needs internet access during install for apt/npm packages.
2026-08-02 22:06:48 +00:00
Outis 0c809d7e53 Merge pull request #108 from outis1one/claude/brave-tesla-hwf1m8
Claude/brave tesla hwf1m8
2026-06-16 14:17:35 -04:00
Claude 29b5783052 Fix LightDM autologin on fresh server installs (nopasswdlogin group + missing config)
Two bugs caused the login screen to appear on new Ubuntu 24.04 server hardware:

1. Ubuntu 24.04's PAM config checks 'user ingroup nopasswdlogin', not 'autologin'.
   Add kiosk user to nopasswdlogin group (and autologin for older versions).

2. The upgrade path never wrote /etc/lightdm/lightdm.conf.d/10-kiosk.conf,
   so on new hardware running through upgrade the file simply didn't exist.

Refactor: extract configure_lightdm_autologin() shared helper called from
both fresh install (step 19/27) and upgrade, so both paths are consistent.
Also use [Seat:*] instead of [SeatDefaults] for forward compatibility.

https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
2026-06-16 18:15:05 +00:00
Claude 91d905f45a Fix LightDM autologin on Ubuntu 22.04+ (new hardware)
[SeatDefaults] is silently ignored by LightDM on newer Ubuntu versions.
Replace with [Seat:*] which is the correct section name for Ubuntu 22.04+.

Also add the kiosk user to the autologin group, which newer Ubuntu
requires for passwordless autologin to work.

Without these fixes, LightDM shows the login screen instead of
auto-logging in and launching the kiosk app.

https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
2026-06-16 18:04:36 +00:00
Outis de197f77d3 Merge pull request #107 from outis1one/claude/brave-tesla-hwf1m8
Fix all file existence checks to run as kiosk user
2026-06-16 13:40:01 -04:00
Claude 879e2ecd14 Fix all file existence checks to run as kiosk user
/home/kiosk is mode 700 so root cannot traverse it. Every [[ -f ]] or
[[ ! -f ]] check on paths inside /home/kiosk was silently returning
'not found' even after the kiosk user had successfully written the file.

Replace all three [[ ! -f "$electron_bin" ]] checks and the
[[ -f "$sandbox" ]] check in install_electron_binary with
sudo -u "$KIOSK_USER" test -f so they run in the kiosk user's
security context and can actually see the files.

https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
2026-06-16 17:32:32 +00:00
Outis 8116fdf258 Merge pull request #106 from outis1one/claude/brave-tesla-hwf1m8
Restore chmod 644 on tmp zip so kiosk user can read it for unzip
2026-06-16 13:26:25 -04:00
Claude 56e3d17459 Restore chmod 644 on tmp zip so kiosk user can read it for unzip
Accidentally dropped this line when rewriting the extraction block.
mktemp creates the file as root:root 600, so sudo -u kiosk unzip
gets 'Permission denied' trying to open the zipfile.

https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
2026-06-16 17:25:36 +00:00
Outis 35350d9908 Merge pull request #105 from outis1one/claude/brave-tesla-hwf1m8
Fix Electron binary extraction: chown dist/ before extracting as kios…
2026-06-16 13:21:17 -04:00
Claude 43266a4cf0 Fix Electron binary extraction: chown dist/ before extracting as kiosk user
The previous fix incorrectly ran unzip as root, which fails because
/home/kiosk is not accessible to root. The kiosk user is the right
actor for the extraction, but two things blocked it:

1. node_modules/electron/dist/ can be owned by root when npm's electron
   postinstall runs with --unsafe-perm, so the kiosk user gets
   'Permission denied' trying to write there. Fix: sudo chown -R the
   electron directory to the kiosk user before extracting.

2. With set -euo pipefail active (upgrade call had no || guard), a
   failed unzip or chmod would abort the script silently before the
   diagnostic error messages could print. Fix: add || true to both
   commands so the function always reaches the explicit -f check which
   prints the real error and returns 1. The upgrade call already has
   || { log_error ...; return 1; } from the previous commit.

https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
2026-06-16 17:17:37 +00:00
Outis 6621f9221b Merge pull request #104 from outis1one/claude/brave-tesla-hwf1m8
Fix Electron binary extraction failing silently during upgrade
2026-06-16 13:13:09 -04:00
Claude 670b32f53c Fix Electron binary extraction failing silently during upgrade
Two bugs combined to cause the 'Electron binary download failed' error
even though the zip downloaded and unzip reported inflating all files:

1. The upgrade path called install_electron_binary bare (no ||), so
   set -euo pipefail was active inside the function. Any failing command
   (e.g. chmod on a file that wasn't written) killed the script before
   the error messages printed. Fresh install used || exit 1, which
   disables set -e inside the function body. Upgrade now uses
   || { log_error ...; return 1; } to match.

2. The unzip ran as the kiosk user, but node_modules/electron/dist/ can
   be owned by root when npm's electron postinstall script runs with
   --unsafe-perm. The kiosk user can't write there, so unzip's write
   errors go to stderr (not visible in the log) while inflating: lines
   still appear on stdout. The binary is never actually written.
   Fix: run mkdir/unzip/chmod as root, then chown -R to kiosk.

https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
2026-06-16 17:11:56 +00:00
Outis c3ea52dda0 Merge pull request #103 from outis1one/claude/gifted-bohr-dfzdig
Fix permission denied on Electron zip: chmod 644 before kiosk-user unzip
2026-06-16 12:56:55 -04:00
Claude 174bbe46ae Fix permission denied on Electron zip: chmod 644 before kiosk-user unzip
mktemp creates the tmp zip owned by root with mode 600.
sudo -u kiosk unzip then fails with "Permission denied".
Add chmod 644 immediately after download so the kiosk user can read it.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 16:54:51 +00:00
Outis 572f0ed01b Merge pull request #102 from outis1one/claude/gifted-bohr-dfzdig
Detect pipe execution; show clear upgrade error instead of cryptic pa…
2026-06-16 12:50:00 -04:00
Claude b7e35c84c9 Detect pipe execution; show clear upgrade error instead of cryptic path failure
When the script is run via curl|bash or wget|bash, BASH_SOURCE[0] is a
pipe descriptor, not a real file. The upgrade function grep-extracts
heredocs from the script file, so it fails with a confusing path error.

Fixes:
- Set SCRIPT_FILE global at startup (empty string when piped)
- upgrade_kiosk() checks SCRIPT_FILE before asking "Continue?" and shows
  a clear message explaining how to download the script to a file first
- Removes the silent failure path (no more cryptic "Cannot find script at
  /proc/.../pipe:[...]" error)

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 16:45:58 +00:00
Outis 9a9352a9c2 Merge pull request #101 from outis1one/claude/gifted-bohr-dfzdig
Add unzip to apt packages; guard install_electron_binary against miss…
2026-06-16 12:40:25 -04:00
Claude 440b074245 Add unzip to apt packages; guard install_electron_binary against missing unzip
Fresh Ubuntu 24.04 minimal installs don't include unzip. The wget fallback
in install_electron_binary() downloaded the 120MB Electron zip successfully
but then failed on the unzip call. Two fixes:
1. Add unzip to the main apt install step so it's always present.
2. Auto-install unzip inside install_electron_binary() as a safety net for
   upgrades on existing systems that may not have it.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 16:39:36 +00:00
Outis 46d42b46c6 Merge pull request #100 from outis1one/claude/gifted-bohr-dfzdig
feat: match any touch screen for libinput (universal touch support)
2026-06-16 11:05:53 -04:00
Claude 27aaf5a15c feat: match any touch screen for libinput, not just Wacom "Finger" devices
Drop the MatchProduct "Finger" restriction from the xorg libinput rule,
leaving only MatchIsTouchscreen "on". MatchIsTouchscreen is set by udev
from hardware capabilities, so it matches finger touch screens of any
brand (ELAN, Goodix, eGalax, Wacom, etc.) while never matching keyboards,
mice, or pen/stylus digitizers (which are tagged as tablets, not
touchscreens). This makes the script work on any touch hardware without
hardcoding device names. Behavior on existing Wacom machines is unchanged
since their finger device matched either way.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 15:05:28 +00:00
Outis 981aaf2b6b Merge pull request #99 from outis1one/claude/gifted-bohr-dfzdig
fix: touchscreen input — keyring grab + libinput multitouch driver
2026-06-16 10:53:13 -04:00
Claude 66a633b22c fix: resolve touch input with keyring flag + libinput driver
Two genuinely separate root causes were behind the dead touchscreen:

1. GNOME keyring grab — under LightDM autologin the keyring stays locked.
   When Chromium accessed it, the gcr-prompter unlock dialog grabbed all
   keyboard and touch input at the X level. The app rendered (timers ran)
   but ignored every tap and keypress. Fix: --password-store=basic stops
   Electron from using the keyring, so the dialog never appears.

2. Wacom driver single-touch emulation — the wacom X driver only does
   single-touch pointer emulation and never passes real multitouch to
   Chromium, so 1-finger and 2-finger swipe gestures could not fire.
   Fix: force the finger touch device to the libinput driver via
   /etc/X11/xorg.conf.d/99-finger-libinput.conf. libinput delivers proper
   XI2 multitouch which Chromium turns into real JS touch events. The
   pen/stylus stays on the wacom driver.

Removed the earlier dead-end attempts (xsetwacom MapToOutput / CTM reset,
Wacom Enable Touch Gesture, 99-wacom-touch.conf) which were all chasing the
wrong cause while the keyring grab masked any real testing. The upgrade path
removes the stale 99-wacom-touch.conf so it can't override libinput.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 14:52:31 +00:00
Claude 4e7fbe497c fix: use xsetwacom MapToOutput instead of xinput set-prop for CTM
The Wacom driver owns the Coordinate Transformation Matrix and silently
overrides any xinput set-prop changes. xsetwacom MapToOutput tells the
driver to recalculate the CTM for the primary connected output, which is
the correct API and persists across driver resets.

Dynamically detects the primary output (eDP1, HDMI1, DP1, etc.) so the
fix works on any machine without hardcoding a display name.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 13:55:09 +00:00
Outis f7f1624e02 Merge pull request #98 from outis1one/claude/gifted-bohr-dfzdig
fix: reset Coordinate Transformation Matrix so touches land on correct screen coordinates
2026-06-16 09:45:08 -04:00
Claude 3315ccc44a fix: reset Coordinate Transformation Matrix to identity in start.sh
The Wacom driver can initialise the CTM to all-zeros, which maps every
touch event to screen coordinate (0,0). The touchscreen appears completely
dead even though the hardware and kernel are working correctly.

Reset the CTM to the identity matrix for every touch/finger device at
startup, before launching Electron, so coordinates are always correct.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 13:44:50 +00:00
Outis c58addcecb Merge pull request #97 from outis1one/claude/gifted-bohr-dfzdig
fix: correct xorg MatchProduct glob pattern for Wacom touch config
2026-06-16 09:13:53 -04:00
Claude 14172782c9 fix: correct xorg MatchProduct glob — Wacom*Finger* not Wacom.*Finger
xorg uses fnmatch (shell glob) for MatchProduct, where . is a literal
dot. Wacom.*Finger never matched "Wacom HID 48E3 Finger touch" because
there is no literal dot in that string. Wacom*Finger* matches correctly.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 13:13:41 +00:00
Outis 279dcd2b66 Merge pull request #96 from outis1one/claude/gifted-bohr-dfzdig
fix: xorg.conf.d Wacom touch mode — driver starts in XI2 touch event mode
2026-06-16 09:08:50 -04:00
Claude 5cbade03d7 fix: add xorg.conf.d Wacom touch config so driver starts in touch event mode
Without /etc/X11/xorg.conf.d/99-wacom-touch.conf the Wacom driver initialises
the finger touch device in pointer emulation mode (generating RawButtonPress/
RawButtonRelease/RawMotion). Electron never sees TouchBegin/TouchEnd events so
touchstart/pointerdown(touch) never fire in the renderer.

Setting Option "Gesture" "on" and Option "Touch" "on" at the driver level means
the device initialises in XI2 touch mode on every X server start, regardless of
any post-init xinput set-prop calls.

Added to both fresh install (step 18) and upgrade function.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 13:08:38 +00:00
Outis 73eed4a123 Merge pull request #95 from outis1one/claude/gifted-bohr-dfzdig
fix: export XAUTHORITY in start.sh so touch init never fails with auth error
2026-06-16 08:57:35 -04:00
Claude 95027fe6fe fix: export XAUTHORITY in start.sh so xinput touch init never hits auth error
Without XAUTHORITY set, xinput can fail with "Authorization required"
if the display manager doesn't propagate it through the session environment.
Hardcode the kiosk user's .Xauthority path to guarantee xinput works.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 12:57:21 +00:00
Outis 8b9d688993 Merge pull request #94 from outis1one/claude/gifted-bohr-dfzdig
chore: restore original ubuntu-based-kiosk-v1.0.2.sh alongside v1.0.3
2026-06-15 22:29:29 -04:00
Claude 55039c510b chore: restore original ubuntu-based-kiosk-v1.0.2.sh alongside v1.0.3
Keep v1.0.2 in the repo as a stable fallback while v1.0.3 touch screen
support is being validated on different hardware.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 02:27:13 +00:00
Outis 7f54944ad8 Merge pull request #93 from outis1one/claude/gifted-bohr-dfzdig
Claude/gifted bohr dfzdig
2026-06-15 22:24:24 -04:00
Claude 00dd806e9c chore: bump version to v1.0.3 and rename script file
Update all version strings (header, SCRIPT_VERSION, JS VERSION constant)
and rename ubuntu-based-kiosk-v1.0.2.sh → ubuntu-based-kiosk-v1.0.3.sh.
Update README with v1.0.3 change log and archive v1.0.2 as previous.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 02:23:43 +00:00
Claude 6f18a2f897 fix: detect touch screens dynamically instead of hardcoding Wacom device name
The xinput loop now scans all input devices at startup, matches anything
with "touch" or "finger" in the name (excluding touchpads/trackpads), and
attempts to enable Wacom touch gestures on each match. Non-Wacom devices
silently ignore the set-prop call, so the loop is safe on any hardware.

https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
2026-06-16 02:21:09 +00:00