diff --git a/Readme.md b/Readme.md index 160b7fd..9a7a95f 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # Ubuntu Based Kiosk -**Current Version:** 2.4.0 (check script header for latest version) +**Current Version:** 2.5.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/ @@ -1205,7 +1205,12 @@ terminal menu and the web UI, so they can't drift apart). - `menus/diagnostics.sh` — **Diagnostics**: system status, log viewing, audio diagnostics, network test — 4 of the legacy Advanced menu's 12 items, all read-only. -- `install.sh` — entry point for the modular tool. Run it against an +- `menus/addon_cups.sh` — **CUPS Printing** (Addons): install, + reconfigure for network access, complete uninstall (purge). The first + Addon migrated — genuinely mutates real system state (apt packages, + `/etc/cups`, ufw) rather than this project's own files. +- `install.sh` — entry point for the modular tool, now grouped **Core + Settings / Addons / Advanced** like the legacy menu. Run it against an *already-installed* kiosk: ```bash git clone https://github.com/outis1one/ubuntu-based-kiosk/ @@ -1216,7 +1221,7 @@ terminal menu and the web UI, so they can't drift apart). **Honest status:** this does not yet replace first-time installation, or most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000 lines and still contains its own unremoved, unmodified copies of every -menu above (plus Upgrade, Reinstall, Uninstall, all Addons, and the +menu above (plus Upgrade, Reinstall, Uninstall, 4 more Addons, and the other 8 Advanced items — none of that has moved yet). Both copies coexist deliberately: the old ones stay until enough of Core Settings/Addons/Advanced is migrated to @@ -1229,9 +1234,15 @@ at all. ## Project Status & Future Plans -**Current Version:** 2.4.0 +**Current Version:** 2.5.0 -**Recent Updates (v2.4.0):** +**Recent Updates (v2.5.0):** +- **First Addon migrated:** CUPS Printing — install/reconfigure/complete uninstall, in `./install.sh`. Genuinely mutates real system state (`apt install`/`remove --purge`, `/etc/cups`, `ufw`) at fixed paths CUPS itself doesn't let us relocate, so every test uses full command-level `sudo` stubbing rather than the scratch-directory approach used for this project's own files. +- **Menu restructured:** `install.sh`'s top level is now grouped Core Settings / Addons / Advanced, matching the legacy tool, instead of one flat list — done now while it's cheap, ahead of the list getting unwieldy. +- **Bug fix:** a "wait for service to start" retry loop used a bare `cmd1 && cmd2 && break` as its body — that's not made safe by being inside a loop; a bare `&&`/`||` list used as a standalone statement is fully subject to `set -e`, and the first command failing on an early iteration (near-certain right after a fresh install) would have killed the whole session. Restored the `if cmd1 && cmd2; then break; fi` form. +- **Resolved:** real uncertainty about how far `run_menu`'s `handler || true` guard (added in v2.1.0) actually reaches — confirmed with an isolated test that it protects against a failing command no matter how many function calls deep, so the session-crash risk chased since v2.1.0 is already covered end-to-end by that one fix. Per-statement guards still matter for a different reason: without them, a deep failure bubbles past the menu actually responsible for it to wherever the nearest `|| true` happens to catch it. + +**Previous (v2.4.0):** - **Diagnostics migrated** — system status, log viewing (Electron/LightDM/journal), an 8-step audio diagnostic, and a ping+DNS network test, from the legacy Advanced menu. A change of pace: everything here is read-only, no destructive-action risk to manage. - **Bug fix (set -e safety):** every diagnostic whose failure is the expected case — no lightdm running, no audio hardware, no network, missing logs, `ping`/`nslookup` not even installed — was a bare unguarded statement that would have crashed the whole session instead of reporting "not found" and moving on. Fixed throughout; a diagnostics tool has to survive exactly the broken states it exists to diagnose. - Manual Electron Update, Factory Reset, Export/Import Settings, Emergency Hotspot, and Fix Blank Screen are staying in the legacy script for now — destructive/mutating, and some share Upgrade's coupling to the legacy script's self-extraction mechanism (see v2.3.0 notes). diff --git a/install.sh b/install.sh index 8bc4ee1..2e50f7b 100755 --- a/install.sh +++ b/install.sh @@ -12,12 +12,13 @@ # at a time, so a change to (say) the Sites menu can't accidentally break # WiFi setup or the uninstaller three thousand lines away. # -# Migrated so far: Sites & Page Timing (menus/sites.sh), Display & -# Interaction (menus/display.sh), Timezone (menus/timezone.sh), Hidden -# Site PIN (menus/hidden_pin.sh), Password Protection & Lockout -# (menus/lockout.sh), WiFi (menus/wifi.sh), Power/Display/Quiet Hours -# (menus/power_schedule.sh), Diagnostics (menus/diagnostics.sh - system -# status/logs/audio/network from the legacy Advanced menu). +# Migrated so far, grouped the same way the legacy menu groups them: +# Core Settings: Sites & Page Timing, Display & Interaction, Timezone, +# Hidden Site PIN, Password Protection & Lockout, WiFi, +# Power/Display/Quiet Hours. +# Addons: CUPS Printing (menus/addon_cups.sh). +# Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/ +# audio/network). # # Usage (once the kiosk has already been installed): # git clone @@ -49,6 +50,8 @@ source "$SCRIPT_DIR/menus/wifi.sh" source "$SCRIPT_DIR/menus/power_schedule.sh" # shellcheck source=menus/diagnostics.sh source "$SCRIPT_DIR/menus/diagnostics.sh" +# shellcheck source=menus/addon_cups.sh +source "$SCRIPT_DIR/menus/addon_cups.sh" ################################################################################ # Preflight @@ -81,10 +84,12 @@ if ! is_kiosk_installed; then fi ################################################################################ -# Top-level menu +# Top-level menu - grouped the same way the legacy menu groups them +# (Core Settings / Addons / Advanced), so the structure stays familiar +# and the flat list doesn't grow unwieldy as more menus migrate in. ################################################################################ -main_menu_builder() { +core_settings_menu_builder() { MENU_LABELS=( "Sites & Page Timing" "Display & Interaction" @@ -93,7 +98,6 @@ main_menu_builder() { "Password Protection & Lockout" "WiFi" "Power/Display/Quiet Hours" - "Diagnostics" ) MENU_HANDLERS=( sites_menu @@ -103,10 +107,36 @@ main_menu_builder() { lockout_menu wifi_menu power_schedule_menu - diagnostics_menu ) } +core_settings_menu() { + run_menu "CORE SETTINGS" core_settings_menu_builder +} + +addons_menu_builder() { + MENU_LABELS=("CUPS Printing") + MENU_HANDLERS=(addon_cups_menu) +} + +addons_menu() { + run_menu "ADDONS" addons_menu_builder +} + +advanced_menu_builder() { + MENU_LABELS=("Diagnostics") + MENU_HANDLERS=(diagnostics_menu) +} + +advanced_menu() { + run_menu "ADVANCED" advanced_menu_builder +} + +main_menu_builder() { + MENU_LABELS=("Core Settings" "Addons" "Advanced") + MENU_HANDLERS=(core_settings_menu addons_menu advanced_menu) +} + main_menu_status() { echo "Managing kiosk at: ${KIOSK_DIR}" } diff --git a/lib/config.sh b/lib/config.sh index 3fd763c..c4ce7b5 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -29,6 +29,12 @@ : "${CRON_D_DIR:=/etc/cron.d}" : "${BIN_DIR:=/usr/local/bin}" : "${NETPLAN_DIR:=/etc/netplan}" +: "${POLKIT_DIR:=/etc/polkit-1/localauthority/50-local.d}" + +# The admin account actually running this tool (as opposed to $KIOSK_USER, +# the kiosk's own restricted account) - used where an addon needs to grant +# *this* user a group membership (e.g. lpadmin for CUPS). +: "${BUILD_USER:=${SUDO_USER:-$(whoami)}}" # Site/tab arrays declare -a URLS=() diff --git a/menus/addon_cups.sh b/menus/addon_cups.sh new file mode 100644 index 0000000..c3a7db1 --- /dev/null +++ b/menus/addon_cups.sh @@ -0,0 +1,156 @@ +#!/bin/bash +################################################################################ +# menus/addon_cups.sh - "CUPS Printing" addon (from the legacy Addons menu). +# +# First Addon migrated. Genuinely mutates real system state - installs/ +# purges apt packages, writes /etc/cups/cupsd.conf and a polkit rule, +# touches ufw - at fixed paths CUPS itself doesn't let us relocate the +# way $SYSTEMD_DIR/$CRON_D_DIR/etc let us relocate our own files. Only +# the polkit rule's directory is parameterized ($POLKIT_DIR, since that's +# ours to place); everything else (cupsd.conf, apt, systemctl, ufw) gets +# full command-level `sudo` stubbing in every test - there is no scratch +# equivalent for a real apt-managed subsystem's own file layout. +# +# Depends on: lib/menu.sh, lib/config.sh being sourced first. +################################################################################ + +cups_is_installed() { + dpkg -l 2>/dev/null | grep -q "^ii\s\+cups\s" +} + +cups_is_active() { + systemctl is-active --quiet cups +} + +addon_cups_status() { + if cups_is_installed && cups_is_active; then + echo "CUPS: installed and running (http://$(get_ip_address):631)" + elif cups_is_installed; then + echo "CUPS: installed but not running" + else + echo "CUPS: not installed" + fi +} + +addon_cups_menu_builder() { + if cups_is_installed && cups_is_active; then + MENU_LABELS=("Reconfigure for network access" "Complete uninstall (purge)") + MENU_HANDLERS=(action_reconfigure_cups action_cups_uninstall) + elif cups_is_installed; then + MENU_LABELS=("Start CUPS" "Complete uninstall (purge)") + MENU_HANDLERS=(action_start_cups action_cups_uninstall) + else + MENU_LABELS=("Install CUPS printing") + MENU_HANDLERS=(action_install_cups) + fi +} + +addon_cups_menu() { + run_menu "CUPS PRINTING SUPPORT" addon_cups_menu_builder addon_cups_status +} + +################################################################################ +# Actions +################################################################################ + +action_install_cups() { + echo + ask_yes_no "Install CUPS printing?" "n" || { echo "Cancelled"; return; } + + echo "Installing CUPS from scratch..." + if ! sudo apt update; then + log_error "apt update failed - check network/package sources and try again" + return 1 + fi + if ! sudo apt install -y cups cups-client cups-filters printer-driver-all \ + printer-driver-cups-pdf hplip printer-driver-gutenprint \ + foomatic-db-compressed-ppds openprinting-ppds; then + log_error "CUPS package installation failed" + return 1 + fi + + sudo systemctl enable cups 2>/dev/null || true + sudo systemctl start cups 2>/dev/null || true + + echo "Waiting for CUPS to start..." + for _ in {1..30}; do + # Must stay in an `if` - a bare `cmd1 && cmd2` statement is + # subject to set -e itself when cmd1 fails, which is virtually + # guaranteed on early iterations right after install. + if cups_is_active && lpstat -r &>/dev/null 2>&1; then + break + fi + sleep 1 + done + + # $BUILD_USER already resolves to $SUDO_USER when the tool was run via + # sudo, so a single usermod covers it - the legacy code ran this twice + # (once for a hardcoded computed user, once again for $SUDO_USER + # directly), which was harmless but genuinely redundant. + sudo usermod -aG lpadmin "$BUILD_USER" + + action_reconfigure_cups + + log_success "CUPS installed" + echo " Web interface: http://$(get_ip_address):631" +} + +action_start_cups() { + sudo systemctl enable cups + sudo systemctl start cups + log_success "CUPS started" +} + +action_reconfigure_cups() { + if [[ -f /etc/cups/cupsd.conf ]]; then + sudo cp /etc/cups/cupsd.conf "/etc/cups/cupsd.conf.backup-$(date +%Y%m%d-%H%M%S)" + fi + + if command -v cupsctl &>/dev/null; then + sudo cupsctl --remote-admin --remote-any --share-printers 2>/dev/null || true + fi + + sudo sed -i 's/^Listen localhost:631/Port 631/' /etc/cups/cupsd.conf 2>/dev/null || true + sudo sed -i 's/^Listen 127.0.0.1:631/Port 631/' /etc/cups/cupsd.conf 2>/dev/null || true + + sudo mkdir -p "$POLKIT_DIR" + sudo tee "$POLKIT_DIR/kiosk-printing.pkla" > /dev/null </dev/null || true + sudo systemctl restart cups 2>/dev/null || true + + log_success "CUPS configured for network access" +} + +action_cups_uninstall() { + echo + ask_yes_no "Completely remove CUPS, including all queues and settings (purge)?" "n" || { echo "Cancelled"; return; } + + echo "Performing complete CUPS uninstall..." + + sudo systemctl stop cups cups-browsed 2>/dev/null || true + sudo systemctl disable cups cups-browsed 2>/dev/null || true + + sudo apt remove --purge -y cups cups-daemon cups-client cups-filters \ + cups-common cups-core-drivers cups-server-common cups-browsed \ + cups-ppdc cups-bsd libcups2 libcupsimage2 2>/dev/null || true + + sudo apt remove --purge -y printer-driver-all printer-driver-cups-pdf \ + hplip printer-driver-gutenprint foomatic-db-compressed-ppds \ + openprinting-ppds 2>/dev/null || true + + sudo rm -rf /etc/cups /var/cache/cups /var/spool/cups /var/log/cups /usr/share/cups + sudo rm -f "$POLKIT_DIR/kiosk-printing.pkla" + + sudo apt autoremove -y + sudo apt clean + + log_success "CUPS completely removed" +} diff --git a/ubuntu-based-kiosk.sh b/ubuntu-based-kiosk.sh index 433d4a5..c6d0581 100644 --- a/ubuntu-based-kiosk.sh +++ b/ubuntu-based-kiosk.sh @@ -1,8 +1,46 @@ #!/bin/bash ################################################################################ -### Ubuntu Based Kiosk v2.4.0 ### +### Ubuntu Based Kiosk v2.5.0 ### ################################################################################ # +# RELEASE v2.5.0 - First Addon Migrated (CUPS), Menu Restructured +# - New in ./install.sh: CUPS Printing (menus/addon_cups.sh) - the first +# Addon migrated. Install/reconfigure/complete uninstall (purge), +# genuinely mutating 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 controls, there is no +# scratch equivalent for a real apt-managed subsystem's own file +# layout, so every test uses full command-level `sudo` stubbing +# instead. Only the polkit rule's directory is parameterized +# ($POLKIT_DIR, since that one is ours to place). +# - install.sh's top-level menu is now grouped the same way the legacy +# menu groups things - Core Settings / Addons / Advanced - instead of +# one flat list, ahead of that list getting unwieldy as more Addons +# and Advanced items migrate in. +# - Two bugs caught and fixed before they ever shipped, both instructive +# beyond this one file: +# - A "wait for service to start" retry loop used a bare `cmd1 && +# cmd2 && break` as its body. That's not safe merely because it's +# inside a loop - a bare &&/|| list used as a standalone statement +# (not the condition of if/while/until) is fully subject to set -e, +# and cmd1 failing on an early iteration (near-certain right after +# a fresh install) would have killed the whole session. Restored +# the `if cmd1 && cmd2; then break; fi` form the legacy script +# already used correctly, rather than "simplifying" it away. +# - Resolved real uncertainty about how far run_menu's `handler || +# true` guard (added in v2.1.0) actually reaches: verified with a +# minimal isolated test that it protects against a bare failing +# command no matter how many function calls deep it occurs - bash's +# errexit exemption for the left side of `||` covers the entire +# evaluation, not just the immediately-called 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 matter for a different reason: +# without them a deep failure silently bubbles up past the menu +# that's actually responsible for it to wherever the nearest `|| +# true` happens to catch it, which may be several menu levels +# higher than where the user actually was. +# # RELEASE v2.4.0 - Diagnostics Migrated # - New in ./install.sh: Diagnostics (menus/diagnostics.sh) - system # status, log viewing (Electron/LightDM/journal), an 8-step audio @@ -188,7 +226,7 @@ set -euo pipefail ### SECTION 1: CONSTANTS & GLOBALS ################################################################################ -SCRIPT_VERSION="2.4.0" +SCRIPT_VERSION="2.5.0" # Resolve the real path to this script file. # When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,