Compare commits

...
3 Commits
Author SHA1 Message Date
Claude b0b558f015 Migrate Complete Uninstall, composed from each addon's own uninstall helper; bump to v2.12.0
New menus/complete_uninstall.sh (Core Settings), the last of the
"destructive trio". Rather than re-implementing every addon's teardown
a second time (the legacy shape), it composes the *_do_uninstall
helpers each addon already has - if an addon's removal logic changes,
Complete Uninstall picks it up automatically.

Every addon menu with an uninstall action (CUPS, VNC, WireGuard,
Tailscale, Netbird, LMS, Squeezelite, Asterisk Intercom) plus
power_schedule's "remove all schedules" and Emergency Hotspot's disable
action were each split into a confirm-and-call wrapper (unchanged from
the user's perspective) and a silent do-the-removal helper that both
the wrapper and Complete Uninstall call.

Bug fix found while composing these: several *_do_uninstall helpers
(CUPS's apt autoremove/apt clean, VNC/WireGuard/Tailscale/Netbird's apt
remove) had a bare, unguarded apt call as their second-to-last
statement. Previously this only risked aborting that one menu action if
the package was already gone. Composed together as sequential calls
inside Complete Uninstall, the same failure would have silently
truncated the entire uninstall sequence partway through. Guarded all of
them with `|| true`.

Non-addon teardown (kiosk user/files, Node.js, LightDM/Openbox,
remaining systemd units/scripts, polkit rules, re-enabling virtual
consoles, final package cleanup) stays inline in
menus/complete_uninstall.sh, since no single addon owns those paths.

Upgrade and Full Reinstall stay in ubuntu-based-kiosk.sh only - both
are coupled to its own heredoc self-extraction of main.js/preload.js/
etc, which has no modular equivalent yet.

Full command-level stubbed test suite exercising the full 12-step
teardown, confirmation-text validation, and reboot prompt. Full
19-suite regression + real end-to-end menu navigation via install.sh
all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e
2026-08-19 03:03:50 +00:00
Claude a3313aa9b8 Migrate 4 more Advanced items (Electron, Factory Reset, Virtual Consoles, Emergency Hotspot); bump to v2.11.0
New in install.sh's Advanced menu, alongside Diagnostics:
- menus/advanced_electron.sh: "Electron Maintenance" - the legacy
  "Manual Electron Update" and "Fix Blank Screen" combined into one
  submenu, sharing the binary-repair logic (electron_install_binary).
- menus/advanced_factory_reset.sh: "Factory Reset" - wipes config.json
  back to defaults only; addons are untouched.
- menus/advanced_virtual_consoles.sh: "Virtual Consoles" - toggles
  Ctrl+Alt+F1-F8 terminal login access.
- menus/advanced_emergency_hotspot.sh: "Emergency Hotspot" - auto-starts
  a WiFi hotspot if no internet is detected 60 seconds after boot. Its
  own runtime script and systemd unit now go through $BIN_DIR/
  $SYSTEMD_DIR like every other addon's own files, instead of the
  legacy's hardcoded /usr/local/bin and /etc/systemd/system.

That covers 8 of the legacy Advanced menu's 12 entries. Not migrated
this round: Export/Import Settings (pending a decision on rebuilding it
around actual paths vs. a hardcoded step list, or whether the future
web UI replaces the need for it) and Fix Squeezelite Audio (small
enough it may fold into the LMS addon instead of staying standalone).

Full command-level stubbed test suite per file, including set -e safety
checks (declined/failed paths never crash the session) and content
verification for every written file. Full 18-suite regression + real
end-to-end menu navigation via install.sh all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e
2026-08-19 02:52:43 +00:00
Claude 3eadcdb584 Migrate Asterisk Intercom, redesigned as SIP-extension-only; bump to v2.10.0
New menus/addon_asterisk_intercom.sh, wired into install.sh's Addons
menu. The legacy addon offered Client Only (Baresip SIP client), Server
Only, and Full (server + client), where Server/Full downloaded and ran
a third-party installer from a separate "Easy Asterisk" repository to
stand up a whole Asterisk PBX. That repository has since gone through a
major rework upstream, so this migration drops the PBX-install path
entirely: the addon now only installs Baresip and registers this kiosk
as a SIP extension against an Asterisk server the user already has
running elsewhere. It never installs or manages Asterisk itself. The
legacy script's own three-option version is untouched, same as every
other migrated menu.

Dropped the legacy client path's dependency on the Easy Asterisk repo's
GitHub API for version tracking - now reads the real installed baresip
package version via dpkg instead. Added an uninstall option, which the
legacy addon never had at all.

Bug fix found while testing: an unguarded `ver=$(baresip_installed_version)`
assignment crashed the whole session under set -e the first time status
was checked before Baresip was installed (dpkg-query legitimately fails
when the package isn't there). Guarded with `|| true`.

Full command-level stubbed test suite covering configure (manual/auto-
answer, TLS port bump, apt-install failure) and uninstall (keep/purge
config) for both fresh and already-configured states. Full 13-suite
regression + real end-to-end menu navigation via install.sh all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e
2026-08-19 02:28:05 +00:00
13 changed files with 1496 additions and 67 deletions
+103 -47
View File
@@ -1,6 +1,6 @@
# Ubuntu Based Kiosk # Ubuntu Based Kiosk
**Current Version:** 2.9.0 (check script header for latest version) **Current Version:** 2.12.0 (check script header for latest version)
**Built with Claude Sonnet 4.6 AI assistance** **Built with Claude Sonnet 4.6 AI assistance**
**License:** GPL v3 - Keep derivatives open source **License:** GPL v3 - Keep derivatives open source
**Repository:** https://github.com/outis1one/ubuntu-based-kiosk/ **Repository:** https://github.com/outis1one/ubuntu-based-kiosk/
@@ -251,12 +251,20 @@ Both can be used at the same time — they serve different purposes:
--- ---
### Communication ### Communication
- **Easy Asterisk Intercom** - Voice communication and intercom system - **Asterisk Intercom** (`./install.sh` → Addons) - connects this kiosk
- Downloads latest version from Easy Asterisk repository as a Baresip SIP extension to an Asterisk server you already have
- Automatic update detection and installation running elsewhere; does not install or manage Asterisk itself
- Configuration preservation during updates - Manual or auto-answer (intercom) mode
- Full Asterisk PBX integration - Optional TLS/SRTP transport
- SIP/PJSIP support for IP phones and softphones - Uninstall support (with or without removing saved credentials)
- **Legacy Easy Asterisk Intercom** (`./ubuntu-based-kiosk.sh` → Addons,
not yet retired) - the original three-option version: Client Only
(same Baresip client as above), Server Only, or Full, where Server/
Full download and run a third-party installer from a separate
"Easy Asterisk" repository to stand up a whole Asterisk PBX on this
device. That repository has since gone through a major rework
upstream, so the modular `./install.sh` version above only carries
the client/endpoint piece forward - see "Modular Management" below.
### Audio ### Audio
- **Lyrion Music Server (LMS)** - Formerly Logitech Media Server - **Lyrion Music Server (LMS)** - Formerly Logitech Media Server
@@ -594,51 +602,55 @@ smb://WORKGROUP/COMPUTER/PrinterName
# 4. Restart Kiosk Display # 4. Restart Kiosk Display
``` ```
### Installing Easy Asterisk Intercom ### Installing Asterisk Intercom
The Easy Asterisk Intercom addon provides voice communication capabilities to your kiosk system. The Asterisk Intercom addon connects this kiosk as a SIP extension to an
Asterisk server you already have running elsewhere (your own PBX, a
Docker container, another box on the network - anywhere). It installs
and configures Baresip as that extension; it does not install or manage
Asterisk itself.
**Access the addon menu:** **Access the addon menu:**
```bash ```bash
./ubuntu-based-kiosk.sh git clone https://github.com/outis1one/ubuntu-based-kiosk/
# Select: 2) Addons cd ubuntu-based-kiosk
# Then: 4) Easy Asterisk Intercom ./install.sh
# Select: 2) Addons → Asterisk Intercom (SIP Extension)
``` ```
**Features:** **What you'll be asked for** (must match what's already configured on
- **Automatic installation** - Downloads and installs the latest version from the Easy Asterisk repository the Asterisk server): server IP/hostname, SIP port (default 5060, or
- **Update detection** - Checks for newer versions and prompts to update 5061 if you enable TLS), extension number, SIP password, and whether to
- **Safe re-runs** - Can be run multiple times without breaking existing configurations auto-answer incoming calls (intercom mode) or ring for manual answer.
- **Config preservation** - Automatically backs up and restores configurations during updates
- **Full Asterisk PBX** - Complete telephony features including SIP, extensions, voicemail
**Installation behavior:** **Managing the client:**
- **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:**
```bash ```bash
# Check installation status # Check status (as the kiosk user)
systemctl status asterisk sudo -u kiosk systemctl --user status baresip
# View Asterisk console # Restart
asterisk -rvvv sudo -u kiosk systemctl --user restart baresip
# Restart Asterisk # View logs
systemctl restart asterisk sudo -u kiosk journalctl --user -u baresip -f
# Configure intercom (rerun installation to update) # Reconfigure or uninstall
./ubuntu-based-kiosk.sh ./install.sh
# Select: 2) Addons → 4) Easy Asterisk Intercom # Select: 2) Addons → Asterisk Intercom (SIP Extension)
``` ```
**Installation location:** **Installation location:**
- Installation files: `/opt/easy-asterisk/` - Baresip config: `~kiosk/.baresip/` (`accounts`, `config`)
- Configuration: `/etc/asterisk/` - systemd user unit: `~kiosk/.config/systemd/user/baresip.service`
- Version tracking: `/opt/easy-asterisk/.version`
- Config backups: `/opt/easy-asterisk/config_backup/` **Not covered here:** standing up the Asterisk PBX server itself. The
legacy `ubuntu-based-kiosk.sh` still offers a Server/Full option that
downloads and runs a third-party installer from a separate "Easy
Asterisk" repository - that repository has since gone through a major
rework upstream, so it isn't carried forward into this addon. If you
need a PBX, set one up separately (that same legacy option, a
FreePBX/Issabel image, a Dockerized Asterisk, etc.) and point this
addon at it as a plain SIP extension.
### Updating Electron ### Updating Electron
@@ -1221,6 +1233,28 @@ terminal menu and the web UI, so they can't drift apart).
start script and systemd unit go through `$BIN_DIR`/`$SYSTEMD_DIR` start script and systemd unit go through `$BIN_DIR`/`$SYSTEMD_DIR`
like every other addon; LMS's own apt repo/GPG key/ufw rules stay at like every other addon; LMS's own apt repo/GPG key/ufw rules stay at
their real fixed system paths, same as CUPS. their real fixed system paths, same as CUPS.
- `menus/addon_asterisk_intercom.sh`**Asterisk Intercom** (Addons):
installs Baresip and registers this kiosk as a SIP extension against
an Asterisk server you already have running elsewhere. Redesigned
during migration, not a straight port — see "Recent Updates (v2.10.0)"
below for why the legacy Server/Full PBX-install options didn't come
along.
- `menus/advanced_electron.sh`**Electron Maintenance** (Advanced):
manual update (with backup + rollback) and "fix blank screen" binary
repair, combined into one submenu since both share the same
binary-verification logic.
- `menus/advanced_factory_reset.sh`**Factory Reset** (Advanced):
wipes `config.json` back to defaults; addons are untouched.
- `menus/advanced_virtual_consoles.sh`**Virtual Consoles** (Advanced):
toggles Ctrl+Alt+F1-F8 terminal login access.
- `menus/advanced_emergency_hotspot.sh`**Emergency Hotspot**
(Advanced): auto-starts a WiFi hotspot if no internet is detected 60
seconds after boot. Its own runtime script/systemd unit go through
`$BIN_DIR`/`$SYSTEMD_DIR` like every other addon.
- `menus/complete_uninstall.sh`**Complete Uninstall** (Core
Settings): the last of the "destructive trio." Composed from every
addon's own `*_do_uninstall` helper instead of re-implementing
removal a second time — see "Recent Updates (v2.12.0)" below.
- `install.sh` — entry point for the modular tool, now grouped **Core - `install.sh` — entry point for the modular tool, now grouped **Core
Settings / Addons / Advanced** like the legacy menu. Run it against an Settings / Addons / Advanced** like the legacy menu. Run it against an
*already-installed* kiosk: *already-installed* kiosk:
@@ -1233,13 +1267,18 @@ terminal menu and the web UI, so they can't drift apart).
**Honest status:** this does not yet replace first-time installation, or **Honest status:** this does not yet replace first-time installation, or
most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000 most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000
lines and still contains its own unremoved, unmodified copies of every lines and still contains its own unremoved, unmodified copies of every
menu above (plus Upgrade, Reinstall, Uninstall, 1 more Addon — Easy menu above, including the legacy three-option (Client/Server/Full)
Asterisk Intercom — and the other 8 Advanced items — none of that has Easy Asterisk Intercom — the modular version only replaces the Client
moved yet). Both copies coexist deliberately: the old ones stay until option, by design (plus Upgrade, Full Reinstall, Export/Import
enough of Core Settings/Addons/Advanced is migrated to retire them in Settings, and Fix Squeezelite Audio — none of that has moved yet;
one pass, rather than leaving the legacy menu half-wired. Migration Complete Uninstall *is* now migrated, but Upgrade and Full Reinstall
continues one `menus/*.sh` file at a time; first-time installation are staying put — both are coupled to this file's own heredoc self-
itself is the last and largest piece to move, if it moves at all. extraction of main.js/preload.js/etc, which has no modular equivalent).
Both copies coexist deliberately: the old ones stay until enough of
Core Settings/Addons/Advanced is migrated to retire them in one pass,
rather than leaving the legacy menu half-wired. Migration continues one
`menus/*.sh` file at a time; first-time installation itself is the last
and largest piece to move, if it moves at all.
**Resolved (v2.9.0):** `is_service_enabled()` — shared by both scripts **Resolved (v2.9.0):** `is_service_enabled()` — shared by both scripts
— had a pre-check (`systemctl list-unit-files | grep -q "^${service}\s"`) — had a pre-check (`systemctl list-unit-files | grep -q "^${service}\s"`)
@@ -1266,9 +1305,26 @@ full migration pass.
## Project Status & Future Plans ## Project Status & Future Plans
**Current Version:** 2.9.0 **Current Version:** 2.12.0
**Recent Updates (v2.9.0):** **Recent Updates (v2.12.0):**
- **Complete Uninstall migrated** — the last of the "destructive trio." Rather than re-implementing every addon's teardown a second time (the legacy shape — CUPS/VNC/WireGuard/Tailscale/Netbird/LMS/Squeezelite removal all inlined again, independently of each addon's own uninstall action), `menus/complete_uninstall.sh` composes the `*_do_uninstall` helpers each addon already has. Every addon menu with an uninstall action was split into a confirm-and-call wrapper (unchanged from the user's perspective) plus a silent removal helper that both the wrapper and Complete Uninstall call — no duplicated logic anywhere, and if an addon's removal logic changes later, Complete Uninstall picks it up automatically.
- **Important bug found and fixed while composing these:** several `*_do_uninstall` helpers (CUPS's `apt autoremove`/`apt clean`, VNC/WireGuard/Tailscale/Netbird's `apt remove`) had a bare, unguarded `apt` call. Previously this only risked aborting that one menu action if the package was already gone. Composed together as sequential calls inside Complete Uninstall, the same failure would have silently truncated the *entire* uninstall partway through — e.g. the kiosk user might never get removed because an already-uninstalled VPN client's `apt remove` failed first. Guarded all of them with `|| true`.
- Non-addon teardown (kiosk user/files, Node.js, LightDM/Openbox, remaining systemd units/scripts, polkit rules, re-enabling virtual consoles, final package cleanup) stays inline in `menus/complete_uninstall.sh`, since no single addon owns those paths — same as the legacy script.
- Upgrade and Full Reinstall remain in `ubuntu-based-kiosk.sh` only — both are coupled to its own heredoc self-extraction of main.js/preload.js/etc, which has no modular equivalent yet.
**Previous (v2.11.0):**
- **4 more Advanced items migrated**, alongside Diagnostics: **Electron Maintenance** (`menus/advanced_electron.sh` — the legacy "Manual Electron Update" and "Fix Blank Screen" combined into one submenu, since both maintain the same installation and share the binary-repair logic), **Factory Reset** (`menus/advanced_factory_reset.sh` — wipes `config.json` only, addons untouched), **Virtual Consoles** (`menus/advanced_virtual_consoles.sh` — toggles Ctrl+Alt+F1-F8 terminal login), and **Emergency Hotspot** (`menus/advanced_emergency_hotspot.sh` — auto-starts a WiFi hotspot if no internet is detected 60 seconds after boot; its own runtime script and systemd unit now go through `$BIN_DIR`/`$SYSTEMD_DIR` like every other addon's own files).
- That's 8 of the legacy Advanced menu's 12 entries now covered. Not migrated this round: Export/Import Settings (pending a decision on whether to rebuild it around actual paths instead of a hardcoded per-addon step list, or whether the future web UI replaces the need for it) and Fix Squeezelite Audio (small enough that it may fold into the LMS addon instead of staying standalone — not decided yet).
- Complete Uninstall (the last of the "destructive trio") is next, composed from each addon's own uninstall action plus core teardown rather than rewriting removal logic a second time. Upgrade and Full Reinstall stay in the legacy script for now — both are coupled to its own heredoc self-extraction of main.js/preload.js/etc, which has no modular equivalent yet.
**Previous (v2.10.0):**
- **Asterisk Intercom migrated, and redesigned in the process.** The legacy addon offered Client Only (Baresip SIP client), Server Only, and Full (server + client) — the latter two downloaded and ran a third-party installer from a separate "Easy Asterisk" repository to stand up a whole Asterisk PBX. That repository has since gone through a major rework upstream, so the PBX-install path is dropped entirely rather than carrying a dependency on code that's moved on without it. The migrated addon (`menus/addon_asterisk_intercom.sh`) now does only the client/endpoint piece: install Baresip and register this kiosk as one SIP extension against an Asterisk server you already have running elsewhere. It never installs or manages Asterisk itself. The legacy script's own three-option version is untouched, same as every other migrated menu.
- Dropped the dependency on the (now-reworked) Easy Asterisk repo's GitHub API for version tracking — reads the real installed `baresip` package version via `dpkg` instead.
- **New capability:** an uninstall option for the Baresip client — the legacy addon never had one.
- **Bug fix:** an unguarded `ver=$(baresip_installed_version)` assignment would have crashed the whole session the first time status was checked before Baresip was installed (`dpkg-query` legitimately fails when the package isn't there). Guarded with `|| true` before it shipped.
**Previous (v2.9.0):**
- **LMS Server / Squeezelite Player migrated** — install/reconfigure/uninstall for both, in `./install.sh`. Squeezelite's own start script and systemd unit now go through `$BIN_DIR`/`$SYSTEMD_DIR` like every other addon instead of hardcoded `/usr/local/bin`/`/etc/systemd/system`; LMS's own apt repo/GPG key/ufw rules stay at their real fixed system paths, same approach as CUPS. - **LMS Server / Squeezelite Player migrated** — install/reconfigure/uninstall for both, in `./install.sh`. Squeezelite's own start script and systemd unit now go through `$BIN_DIR`/`$SYSTEMD_DIR` like every other addon instead of hardcoded `/usr/local/bin`/`/etc/systemd/system`; LMS's own apt repo/GPG key/ufw rules stay at their real fixed system paths, same approach as CUPS.
- **Bug fix:** the legacy `install_lms()` enabled/started the detected service via `sudo systemctl enable "$service_name" 2>&1 | tee /tmp/lms-enable.log` — piped through `tee`, the statement's exit status reflected `tee` (always 0), not `systemctl enable`, so a real enable/start failure was silently swallowed instead of falling through to a warning. Now uses the shared `enable_and_start_units()` helper. - **Bug fix:** the legacy `install_lms()` enabled/started the detected service via `sudo systemctl enable "$service_name" 2>&1 | tee /tmp/lms-enable.log` — piped through `tee`, the statement's exit status reflected `tee` (always 0), not `systemctl enable`, so a real enable/start failure was silently swallowed instead of falling through to a warning. Now uses the shared `enable_and_start_units()` helper.
- **Bug fix (shared, backported to the legacy script too):** `is_service_enabled()`'s pre-check never matched a bare service name against `list-unit-files`' `"$service.service"` lines, so it always reported "not enabled" regardless of the real state. Dropped the dead pre-check — see "Modular Management" below. - **Bug fix (shared, backported to the legacy script too):** `is_service_enabled()`'s pre-check never matched a bare service name against `list-unit-files`' `"$service.service"` lines, so it always reported "not enabled" regardless of the real state. Dropped the dead pre-check — see "Modular Management" below.
+32 -7
View File
@@ -15,13 +15,22 @@
# Migrated so far, grouped the same way the legacy menu groups them: # Migrated so far, grouped the same way the legacy menu groups them:
# Core Settings: Sites & Page Timing, Display & Interaction, Timezone, # Core Settings: Sites & Page Timing, Display & Interaction, Timezone,
# Hidden Site PIN, Password Protection & Lockout, WiFi, # Hidden Site PIN, Password Protection & Lockout, WiFi,
# Power/Display/Quiet Hours. # Power/Display/Quiet Hours, Complete Uninstall
# (menus/complete_uninstall.sh - composed from every addon's own
# uninstall helper rather than re-implementing removal a second
# time; Upgrade and Full Reinstall stay in the legacy script, both
# coupled to its heredoc self-extraction of main.js/preload.js/etc).
# Addons: CUPS Printing (menus/addon_cups.sh), Authelia Auto-Login # Addons: CUPS Printing (menus/addon_cups.sh), Authelia Auto-Login
# (menus/addon_authelia.sh), Remote Access - VNC/WireGuard/ # (menus/addon_authelia.sh), Remote Access - VNC/WireGuard/
# Tailscale/Netbird (menus/addon_remote_access.sh), LMS Server / # Tailscale/Netbird (menus/addon_remote_access.sh), LMS Server /
# Squeezelite Player (menus/addon_lms_squeezelite.sh). # Squeezelite Player (menus/addon_lms_squeezelite.sh), Asterisk
# Intercom - SIP extension client (menus/addon_asterisk_intercom.sh).
# Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/ # Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/
# audio/network). # audio/network), Electron Maintenance (menus/advanced_electron.sh -
# manual update, fix blank screen), Factory Reset
# (menus/advanced_factory_reset.sh), Virtual Consoles
# (menus/advanced_virtual_consoles.sh), Emergency Hotspot
# (menus/advanced_emergency_hotspot.sh).
# #
# Usage (once the kiosk has already been installed): # Usage (once the kiosk has already been installed):
# git clone <repo> # git clone <repo>
@@ -61,6 +70,20 @@ source "$SCRIPT_DIR/menus/addon_authelia.sh"
source "$SCRIPT_DIR/menus/addon_remote_access.sh" source "$SCRIPT_DIR/menus/addon_remote_access.sh"
# shellcheck source=menus/addon_lms_squeezelite.sh # shellcheck source=menus/addon_lms_squeezelite.sh
source "$SCRIPT_DIR/menus/addon_lms_squeezelite.sh" source "$SCRIPT_DIR/menus/addon_lms_squeezelite.sh"
# shellcheck source=menus/addon_asterisk_intercom.sh
source "$SCRIPT_DIR/menus/addon_asterisk_intercom.sh"
# shellcheck source=menus/advanced_electron.sh
source "$SCRIPT_DIR/menus/advanced_electron.sh"
# shellcheck source=menus/advanced_factory_reset.sh
source "$SCRIPT_DIR/menus/advanced_factory_reset.sh"
# shellcheck source=menus/advanced_virtual_consoles.sh
source "$SCRIPT_DIR/menus/advanced_virtual_consoles.sh"
# shellcheck source=menus/advanced_emergency_hotspot.sh
source "$SCRIPT_DIR/menus/advanced_emergency_hotspot.sh"
# shellcheck source=menus/complete_uninstall.sh
# Sourced last: composes the *_do_uninstall/*_do_remove_all/*_do_disable
# helpers defined in every file above it.
source "$SCRIPT_DIR/menus/complete_uninstall.sh"
################################################################################ ################################################################################
# Preflight # Preflight
@@ -107,6 +130,7 @@ core_settings_menu_builder() {
"Password Protection & Lockout" "Password Protection & Lockout"
"WiFi" "WiFi"
"Power/Display/Quiet Hours" "Power/Display/Quiet Hours"
"Complete Uninstall"
) )
MENU_HANDLERS=( MENU_HANDLERS=(
sites_menu sites_menu
@@ -116,6 +140,7 @@ core_settings_menu_builder() {
lockout_menu lockout_menu
wifi_menu wifi_menu
power_schedule_menu power_schedule_menu
complete_uninstall_menu
) )
} }
@@ -124,8 +149,8 @@ core_settings_menu() {
} }
addons_menu_builder() { addons_menu_builder() {
MENU_LABELS=("CUPS Printing" "Authelia Auto-Login" "Remote Access" "LMS Server / Squeezelite Player") MENU_LABELS=("CUPS Printing" "Authelia Auto-Login" "Remote Access" "LMS Server / Squeezelite Player" "Asterisk Intercom (SIP Extension)")
MENU_HANDLERS=(addon_cups_menu addon_authelia_menu remote_access_menu addon_lms_squeezelite_menu) MENU_HANDLERS=(addon_cups_menu addon_authelia_menu remote_access_menu addon_lms_squeezelite_menu addon_asterisk_intercom_menu)
} }
addons_menu() { addons_menu() {
@@ -133,8 +158,8 @@ addons_menu() {
} }
advanced_menu_builder() { advanced_menu_builder() {
MENU_LABELS=("Diagnostics") MENU_LABELS=("Diagnostics" "Electron Maintenance" "Factory Reset" "Virtual Consoles" "Emergency Hotspot")
MENU_HANDLERS=(diagnostics_menu) MENU_HANDLERS=(diagnostics_menu advanced_electron_menu advanced_factory_reset_menu advanced_virtual_consoles_menu advanced_emergency_hotspot_menu)
} }
advanced_menu() { advanced_menu() {
+295
View File
@@ -0,0 +1,295 @@
#!/bin/bash
################################################################################
# menus/addon_asterisk_intercom.sh - "Asterisk Intercom" addon: connect the
# kiosk as a SIP extension to an *existing* Asterisk server.
#
# The legacy addon offered three options: Client Only (a Baresip SIP
# client - what this file is), Server Only, and Full (server + client).
# Server/Full downloaded and ran a third-party installer from a separate
# "Easy Asterisk" repository to stand up a whole Asterisk PBX. That
# repository has since gone through a major rework upstream, so wiring a
# full PBX install through it here no longer makes sense to maintain -
# and most kiosk deployments don't need this device to *be* the PBX
# anyway. This addon now does only the client/endpoint piece: install
# Baresip and register it as one extension against an Asterisk server
# the user already has running somewhere else. It never installs or
# manages Asterisk itself.
#
# Two other things fixed while narrowing the scope:
# - The legacy client path tracked its own version by calling out to the
# (now-reworked) Easy Asterisk repo's GitHub API and stamping a
# "<repo-version>-client" string in a side file. That coupling is
# exactly what's being dropped, so version tracking now just reads the
# real installed `baresip` package version via dpkg - one less network
# dependency and one less thing to keep in sync with an external repo.
# - The legacy addon had no uninstall option for the client at all -
# added below.
#
# Real system state: apt package, a per-user config directory under
# $KIOSK_HOME, and a systemd --user unit for $KIOSK_USER (not a system
# service - Baresip needs the desktop session's PulseAudio/PipeWire
# socket). Every write goes through `sudo`/`sudo -u "$KIOSK_USER"`, all
# stubbed at the command level in tests - there's no real D-Bus user
# session to target in a test container regardless.
#
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
################################################################################
BARESIP_CONFIG_DIR="${KIOSK_HOME}/.baresip"
BARESIP_USER_SERVICE_DIR="${KIOSK_HOME}/.config/systemd/user"
# Runs `systemctl --user ...` as $KIOSK_USER with the runtime dir/D-Bus
# address it needs to find that user's session. Always call from an
# `if`/`&&`/`||` context - see run_menu's own comment on why a bare call
# that can legitimately fail must never be a standalone statement.
baresip_systemctl_user() {
local kiosk_uid
kiosk_uid=$(id -u "$KIOSK_USER" 2>/dev/null) || return 1
sudo -u "$KIOSK_USER" \
XDG_RUNTIME_DIR="/run/user/${kiosk_uid}" \
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${kiosk_uid}/bus" \
systemctl --user "$@"
}
baresip_installed_version() {
dpkg-query -W -f='${Version}' baresip 2>/dev/null || true
}
# "Installed" means both the package and a written account - a bare
# `apt install baresip` with no configured extension isn't something
# this menu should call done.
baresip_is_installed() {
command -v baresip &>/dev/null && [[ -f "$BARESIP_CONFIG_DIR/accounts" ]]
}
baresip_is_running() {
baresip_systemctl_user is-active --quiet baresip.service 2>/dev/null
}
addon_asterisk_intercom_status() {
if baresip_is_installed; then
local ver
ver=$(baresip_installed_version)
if baresip_is_running; then
echo "Asterisk Intercom: Installed (v${ver:-unknown}) - Running"
else
echo "Asterisk Intercom: Installed (v${ver:-unknown}) - Not running"
fi
if [[ -f "$BARESIP_CONFIG_DIR/accounts" ]]; then
local account
account=$(head -1 "$BARESIP_CONFIG_DIR/accounts" 2>/dev/null)
local extension="${account#<sip:}"
extension="${extension%%@*}"
[[ -n "$extension" ]] && echo " Extension: $extension"
fi
else
echo "Asterisk Intercom: Not installed"
fi
echo " Connects this kiosk as a SIP extension to an Asterisk server"
echo " you already have running elsewhere - it does not install or"
echo " manage Asterisk itself."
}
addon_asterisk_intercom_menu_builder() {
if baresip_is_installed; then
MENU_LABELS=("Reconfigure (new server/extension)" "Uninstall")
MENU_HANDLERS=(action_configure_asterisk_intercom action_uninstall_asterisk_intercom)
else
MENU_LABELS=("Connect to an Asterisk server")
MENU_HANDLERS=(action_configure_asterisk_intercom)
fi
}
addon_asterisk_intercom_menu() {
run_menu "ASTERISK INTERCOM (SIP EXTENSION)" addon_asterisk_intercom_menu_builder addon_asterisk_intercom_status
}
################################################################################
# Actions
################################################################################
action_configure_asterisk_intercom() {
echo
if baresip_is_installed; then
echo "Asterisk Intercom is already configured."
ask_yes_no "Reconfigure with a different server/extension?" "n" || { pause; return; }
fi
echo "Enter the details of the Asterisk server this kiosk should"
echo "register to as an extension (these must match what's already"
echo "configured on that server)."
echo
local server_ip=""
while [[ -z "$server_ip" ]]; do
server_ip=$(ask_text "Server IP or hostname" "")
[[ -z "$server_ip" ]] && log_error "Server address is required"
done
local server_port
server_port=$(ask_integer "Server port" 5060 1 65535)
local extension=""
while [[ -z "$extension" ]]; do
extension=$(ask_text "Extension number (e.g. 201)" "")
[[ -z "$extension" ]] && log_error "Extension is required"
done
local password=""
while [[ -z "$password" ]]; do
read -r -s -p "SIP password: " password
echo
[[ -z "$password" ]] && log_error "Password is required"
done
local answermode="manual"
if ask_yes_no "Auto-answer incoming calls (intercom mode)?" "n"; then
answermode="auto"
fi
local transport="udp"
local media_enc=""
if ask_yes_no "Use TLS encryption?" "n"; then
transport="tls"
media_enc=";mediaenc=srtp"
if [[ "$server_port" == "5060" ]]; then
server_port=5061
echo "Note: port changed to 5061 for TLS"
fi
fi
echo
echo "Configuration summary:"
echo " Server: ${server_ip}:${server_port}"
echo " Extension: ${extension}"
echo " Answer: $([[ "$answermode" == "auto" ]] && echo "Auto" || echo "Manual")"
echo " TLS: $([[ "$transport" == "tls" ]] && echo "Yes" || echo "No")"
echo
ask_yes_no "Proceed with installation?" "y" || { echo "Cancelled"; pause; return; }
echo
echo "Installing Baresip..."
if ! command -v baresip &>/dev/null; then
if ! sudo apt install -y baresip; then
log_error "Failed to install baresip package"
pause
return 1
fi
fi
sudo apt install -y pulseaudio-utils pipewire-pulse 2>/dev/null || true
sudo mkdir -p "$BARESIP_CONFIG_DIR"
sudo tee "$BARESIP_CONFIG_DIR/accounts" > /dev/null <<EOF
<sip:${extension}@${server_ip}:${server_port};transport=${transport}>;auth_pass=${password};answermode=${answermode}${media_enc}
EOF
if [[ ! -f "$BARESIP_CONFIG_DIR/config" ]]; then
sudo tee "$BARESIP_CONFIG_DIR/config" > /dev/null <<'BARESIPCONFIG'
# Baresip configuration for Asterisk Intercom
# Audio settings
audio_player pulse,default
audio_source pulse,default
audio_alert pulse,default
# Call settings
call_local_timeout 120
call_max_calls 4
# Network settings
net_interface
# SIP settings
sip_trans_bsize 128
sip_verify_server no
# Module loading
module pulse.so
module account.so
module contact.so
module menu.so
module stdio.so
module uuid.so
module debug_cmd.so
BARESIPCONFIG
fi
sudo chown -R "${KIOSK_USER}:${KIOSK_USER}" "$BARESIP_CONFIG_DIR"
sudo chmod 600 "$BARESIP_CONFIG_DIR/accounts"
sudo mkdir -p "$BARESIP_USER_SERVICE_DIR"
sudo tee "$BARESIP_USER_SERVICE_DIR/baresip.service" > /dev/null <<'BARESIPUNIT'
[Unit]
Description=Baresip SIP Client
After=pipewire.service pipewire-pulse.service
Wants=pipewire-pulse.service
[Service]
Type=simple
ExecStart=/usr/bin/baresip -f %h/.baresip
Restart=always
RestartSec=5
Environment=PULSE_SERVER=unix:/run/user/%U/pulse/native
[Install]
WantedBy=default.target
BARESIPUNIT
sudo chown -R "${KIOSK_USER}:${KIOSK_USER}" "${KIOSK_HOME}/.config"
if baresip_systemctl_user daemon-reload 2>/dev/null && \
baresip_systemctl_user enable baresip.service 2>/dev/null && \
baresip_systemctl_user start baresip.service 2>/dev/null; then
log_success "Baresip service enabled and started"
else
log_warning "Baresip files written, but enabling/starting the user service failed - it will start automatically on next login. Check: systemctl --user status baresip"
fi
echo
log_success "Asterisk Intercom configured"
echo " Config dir: ${BARESIP_CONFIG_DIR}"
echo " Server: ${server_ip}:${server_port}"
echo " Extension: ${extension}"
echo
echo "Management commands (as $KIOSK_USER):"
echo " Check status: systemctl --user status baresip"
echo " Restart: systemctl --user restart baresip"
echo " View logs: journalctl --user -u baresip -f"
pause
}
action_uninstall_asterisk_intercom() {
echo
ask_yes_no "Remove Asterisk Intercom (Baresip)?" "n" || { echo "Cancelled"; pause; return; }
asterisk_intercom_do_uninstall ask
pause
}
# The actual removal, no confirmation prompt - shared with Complete
# Uninstall so that operation doesn't need to re-implement this teardown
# a second time. $1: "ask" to prompt about config removal interactively
# (the normal case), "purge" to remove config without asking (Complete
# Uninstall).
asterisk_intercom_do_uninstall() {
local data_choice="${1:-ask}"
baresip_systemctl_user stop baresip.service 2>/dev/null || true
baresip_systemctl_user disable baresip.service 2>/dev/null || true
sudo rm -f "$BARESIP_USER_SERVICE_DIR/baresip.service"
sudo apt remove -y baresip 2>/dev/null || true
local purge_config=false
if [[ "$data_choice" == "purge" ]]; then
purge_config=true
elif [[ "$data_choice" == "ask" ]] && ask_yes_no "Remove saved SIP configuration too?" "n"; then
purge_config=true
fi
if $purge_config; then
sudo rm -rf "$BARESIP_CONFIG_DIR"
log_success "Asterisk Intercom removed (configuration deleted)"
else
log_success "Asterisk Intercom removed (configuration preserved)"
fi
}
+7 -2
View File
@@ -132,7 +132,12 @@ EOF
action_cups_uninstall() { action_cups_uninstall() {
echo echo
ask_yes_no "Completely remove CUPS, including all queues and settings (purge)?" "n" || { echo "Cancelled"; return; } ask_yes_no "Completely remove CUPS, including all queues and settings (purge)?" "n" || { echo "Cancelled"; return; }
cups_do_uninstall
}
# The actual removal, no prompt - shared with Complete Uninstall so that
# operation doesn't need to re-implement CUPS teardown a second time.
cups_do_uninstall() {
echo "Performing complete CUPS uninstall..." echo "Performing complete CUPS uninstall..."
sudo systemctl stop cups cups-browsed 2>/dev/null || true sudo systemctl stop cups cups-browsed 2>/dev/null || true
@@ -149,8 +154,8 @@ action_cups_uninstall() {
sudo rm -rf /etc/cups /var/cache/cups /var/spool/cups /var/log/cups /usr/share/cups 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 rm -f "$POLKIT_DIR/kiosk-printing.pkla"
sudo apt autoremove -y sudo apt autoremove -y 2>/dev/null || true
sudo apt clean sudo apt clean 2>/dev/null || true
log_success "CUPS completely removed" log_success "CUPS completely removed"
} }
+23 -5
View File
@@ -194,6 +194,16 @@ action_install_lms() {
action_uninstall_lms() { action_uninstall_lms() {
echo echo
ask_yes_no "Remove LMS Server?" "n" || { echo "Cancelled"; pause; return; } ask_yes_no "Remove LMS Server?" "n" || { echo "Cancelled"; pause; return; }
lms_do_uninstall ask
pause
}
# The actual removal, no confirmation prompt - shared with Complete
# Uninstall so that operation doesn't need to re-implement LMS teardown a
# second time. $1: "ask" to prompt about data removal interactively (the
# normal case), "purge" to remove data without asking (Complete Uninstall).
lms_do_uninstall() {
local data_choice="${1:-ask}"
local service_name local service_name
service_name=$(lms_service_name) service_name=$(lms_service_name)
@@ -212,15 +222,20 @@ action_uninstall_lms() {
sudo rm -f /etc/apt/sources.list.d/lms.list sudo rm -f /etc/apt/sources.list.d/lms.list
sudo rm -f /usr/share/keyrings/lms-keyring.gpg sudo rm -f /usr/share/keyrings/lms-keyring.gpg
if ask_yes_no "Remove LMS data and configuration?" "n"; then local purge_data=false
if [[ "$data_choice" == "purge" ]]; then
purge_data=true
elif [[ "$data_choice" == "ask" ]] && ask_yes_no "Remove LMS data and configuration?" "n"; then
purge_data=true
fi
if $purge_data; then
sudo rm -rf /var/lib/squeezeboxserver sudo rm -rf /var/lib/squeezeboxserver
sudo rm -rf /etc/squeezeboxserver sudo rm -rf /etc/squeezeboxserver
log_success "LMS and data removed" log_success "LMS and data removed"
else else
log_success "LMS removed (data preserved)" log_success "LMS removed (data preserved)"
fi fi
pause
} }
################################################################################ ################################################################################
@@ -332,13 +347,16 @@ EOF
action_uninstall_squeezelite() { action_uninstall_squeezelite() {
echo echo
ask_yes_no "Remove Squeezelite Player?" "n" || { echo "Cancelled"; pause; return; } ask_yes_no "Remove Squeezelite Player?" "n" || { echo "Cancelled"; pause; return; }
squeezelite_do_uninstall
pause
}
# Shared with Complete Uninstall - same reasoning as lms_do_uninstall.
squeezelite_do_uninstall() {
sudo systemctl stop squeezelite 2>/dev/null || true sudo systemctl stop squeezelite 2>/dev/null || true
sudo systemctl disable squeezelite 2>/dev/null || true sudo systemctl disable squeezelite 2>/dev/null || true
sudo rm -f "$SYSTEMD_DIR/squeezelite.service" sudo rm -f "$SYSTEMD_DIR/squeezelite.service"
sudo rm -f "$BIN_DIR/squeezelite-start.sh" sudo rm -f "$BIN_DIR/squeezelite-start.sh"
sudo apt remove -y squeezelite 2>/dev/null || true sudo apt remove -y squeezelite 2>/dev/null || true
log_success "Squeezelite removed" log_success "Squeezelite removed"
pause
} }
+20 -4
View File
@@ -130,11 +130,15 @@ action_vnc_change_password() {
action_vnc_uninstall() { action_vnc_uninstall() {
echo echo
ask_yes_no "Remove VNC?" "n" || { echo "Cancelled"; return; } ask_yes_no "Remove VNC?" "n" || { echo "Cancelled"; return; }
vnc_do_uninstall
}
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
vnc_do_uninstall() {
sudo systemctl stop x11vnc 2>/dev/null || true sudo systemctl stop x11vnc 2>/dev/null || true
sudo systemctl disable x11vnc 2>/dev/null || true sudo systemctl disable x11vnc 2>/dev/null || true
sudo rm -f "$SYSTEMD_DIR/x11vnc.service" sudo rm -f "$SYSTEMD_DIR/x11vnc.service"
sudo apt remove -y x11vnc sudo apt remove -y x11vnc 2>/dev/null || true
log_success "VNC removed" log_success "VNC removed"
} }
@@ -227,10 +231,14 @@ action_wireguard_paste_config() {
action_wireguard_uninstall() { action_wireguard_uninstall() {
echo echo
ask_yes_no "Remove WireGuard?" "n" || { echo "Cancelled"; return; } ask_yes_no "Remove WireGuard?" "n" || { echo "Cancelled"; return; }
wireguard_do_uninstall
}
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
wireguard_do_uninstall() {
sudo systemctl stop 'wg-quick@*' 2>/dev/null || true sudo systemctl stop 'wg-quick@*' 2>/dev/null || true
sudo systemctl disable 'wg-quick@*' 2>/dev/null || true sudo systemctl disable 'wg-quick@*' 2>/dev/null || true
sudo apt remove -y wireguard wireguard-tools sudo apt remove -y wireguard wireguard-tools 2>/dev/null || true
log_success "WireGuard removed" log_success "WireGuard removed"
} }
@@ -328,9 +336,13 @@ action_tailscale_show_status() {
action_tailscale_uninstall() { action_tailscale_uninstall() {
echo echo
ask_yes_no "Remove Tailscale?" "n" || { echo "Cancelled"; return; } ask_yes_no "Remove Tailscale?" "n" || { echo "Cancelled"; return; }
tailscale_do_uninstall
}
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
tailscale_do_uninstall() {
sudo tailscale down 2>/dev/null || true sudo tailscale down 2>/dev/null || true
sudo apt remove -y tailscale sudo apt remove -y tailscale 2>/dev/null || true
log_success "Tailscale removed" log_success "Tailscale removed"
} }
@@ -411,8 +423,12 @@ action_netbird_show_status() {
action_netbird_uninstall() { action_netbird_uninstall() {
echo echo
ask_yes_no "Remove Netbird?" "n" || { echo "Cancelled"; return; } ask_yes_no "Remove Netbird?" "n" || { echo "Cancelled"; return; }
netbird_do_uninstall
}
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
netbird_do_uninstall() {
sudo netbird down 2>/dev/null || true sudo netbird down 2>/dev/null || true
sudo apt remove -y netbird sudo apt remove -y netbird 2>/dev/null || true
log_success "Netbird removed" log_success "Netbird removed"
} }
+278
View File
@@ -0,0 +1,278 @@
#!/bin/bash
################################################################################
# menus/advanced_electron.sh - "Electron Maintenance" (Advanced): the
# legacy "Manual Electron Update" and "Fix Blank Screen" items, combined
# under one submenu since both maintain the same Electron installation
# and share the binary-repair logic (electron_install_binary).
#
# Real system state: $KIOSK_DIR/node_modules, package.json, lightdm.
# Every write goes through `sudo`/`sudo -u "$KIOSK_USER"`, stubbed at the
# command level in tests - there's no relocatable equivalent for another
# project's (npm/Electron's) own directory layout.
#
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
################################################################################
electron_installed_version() {
local package_json="$KIOSK_DIR/package.json"
if ! sudo test -f "$package_json" 2>/dev/null; then
echo "not installed"
return
fi
local version
version=$(sudo grep -oP '"electron"\s*:\s*"\^?\K[0-9.]+' "$package_json" 2>/dev/null || true)
if [[ -z "$version" ]]; then
local electron_pkg="$KIOSK_DIR/node_modules/electron/package.json"
if sudo test -f "$electron_pkg" 2>/dev/null; then
version=$(sudo grep -oP '"version"\s*:\s*"\K[0-9.]+' "$electron_pkg" 2>/dev/null || true)
fi
fi
echo "${version:-unknown}"
}
electron_is_running() {
pgrep -f "electron.*main.js" &>/dev/null || pgrep -f "node.*electron" &>/dev/null
}
# Re-verify/download the Electron binary and fix chrome-sandbox
# permissions, without touching package.json or reinstalling anything
# else. Shared by both actions below.
electron_install_binary() {
local electron_bin="$KIOSK_DIR/node_modules/electron/dist/electron"
if ! sudo -u "$KIOSK_USER" test -f "$electron_bin"; then
log_warning "Electron binary missing - retrying via install.js..."
sudo -u "$KIOSK_USER" bash -lc "cd '$KIOSK_DIR' && ELECTRON_FORCE_DOWNLOAD=true node node_modules/electron/install.js" || true
fi
if ! sudo -u "$KIOSK_USER" test -f "$electron_bin"; then
log_warning "Attempting direct download of Electron binary (~120MB)..."
local electron_ver
electron_ver=$(sudo -u "$KIOSK_USER" node -e \
"try{console.log(require('$KIOSK_DIR/node_modules/electron/package.json').version)}catch(e){}" 2>/dev/null || true)
if [[ -n "$electron_ver" ]]; then
local electron_url="https://github.com/electron/electron/releases/download/v${electron_ver}/electron-v${electron_ver}-linux-x64.zip"
log_info "Downloading Electron v${electron_ver} directly..."
local tmp_zip
tmp_zip=$(mktemp --suffix=.zip)
if wget --timeout=300 --tries=3 -O "$tmp_zip" "$electron_url"; then
command -v unzip &>/dev/null || sudo apt install -y unzip
chmod 644 "$tmp_zip"
sudo chown -R "$KIOSK_USER:$KIOSK_USER" "$KIOSK_DIR/node_modules/electron/" 2>/dev/null || true
sudo -u "$KIOSK_USER" mkdir -p "$KIOSK_DIR/node_modules/electron/dist"
sudo -u "$KIOSK_USER" unzip -o "$tmp_zip" -d "$KIOSK_DIR/node_modules/electron/dist/" || true
sudo -u "$KIOSK_USER" chmod +x "$electron_bin" || true
fi
rm -f "$tmp_zip"
fi
fi
if ! sudo -u "$KIOSK_USER" test -f "$electron_bin"; then
log_error "Electron binary download failed after all attempts."
log_error "Check your internet connection and try again."
return 1
fi
log_success "Electron binary verified"
# chrome-sandbox MUST be owned by root and setuid, or Electron shows a blank screen.
local sandbox="$KIOSK_DIR/node_modules/electron/dist/chrome-sandbox"
if sudo -u "$KIOSK_USER" test -f "$sandbox"; then
sudo chown root:root "$sandbox"
sudo chmod 4755 "$sandbox"
log_success "Chrome sandbox permissions set (required for display)"
fi
}
advanced_electron_status() {
local ver
ver=$(electron_installed_version)
echo "Electron: v${ver}"
if electron_is_running; then
echo " Running"
else
echo " Not running"
fi
}
advanced_electron_menu_builder() {
MENU_LABELS=(
"Check for updates / update Electron"
"Fix blank screen (repair Electron binary + sandbox)"
)
MENU_HANDLERS=(action_update_electron action_repair_electron)
}
advanced_electron_menu() {
run_menu "ELECTRON MAINTENANCE" advanced_electron_menu_builder advanced_electron_status
}
################################################################################
# Actions
################################################################################
action_update_electron() {
echo
if ! sudo test -d "$KIOSK_DIR" 2>/dev/null; then
log_error "Kiosk directory not found: $KIOSK_DIR"
pause
return 1
fi
local current_version
current_version=$(electron_installed_version)
log_info "Current Electron version: $current_version"
if electron_is_running; then
log_success "Electron app is running"
else
log_warning "Electron app does not appear to be running"
fi
echo
ask_yes_no "Check for latest Electron version?" "y" || { echo "Cancelled"; pause; return; }
local latest_version
latest_version=$(npm view electron version 2>/dev/null || true)
if [[ -z "$latest_version" ]]; then
latest_version=$(curl -s https://registry.npmjs.org/electron/latest 2>/dev/null | grep -oP '"version"\s*:\s*"\K[0-9.]+' || true)
fi
if [[ -z "$latest_version" ]]; then
latest_version=$(curl -s https://api.github.com/repos/electron/electron/releases/latest 2>/dev/null | grep -oP '"tag_name"\s*:\s*"v\K[0-9.]+' || true)
fi
if [[ -z "$latest_version" ]]; then
log_error "Could not fetch latest Electron version - check your internet connection"
pause
return 1
fi
log_success "Latest stable Electron version: $latest_version"
echo
if [[ "$current_version" == "$latest_version" ]]; then
log_success "Already running the latest version"
ask_yes_no "Reinstall Electron $latest_version anyway?" "n" || { echo "Cancelled"; pause; return; }
fi
echo "──────────────────────────────────────────────────────────"
echo "UPDATE SUMMARY"
echo "──────────────────────────────────────────────────────────"
echo "Current version: $current_version"
echo "Target version: $latest_version"
echo "Installation: $KIOSK_DIR"
echo
local current_major="${current_version%%.*}"
local latest_major="${latest_version%%.*}"
log_warning "Review breaking changes before updating:"
echo " https://www.electronjs.org/docs/latest/breaking-changes"
if [[ "$latest_major" != "$current_major" ]]; then
log_warning "MAJOR VERSION CHANGE (v${current_major} -> v${latest_major})"
fi
echo
ask_yes_no "Reviewed breaking changes and want to proceed?" "n" || { echo "Cancelled"; pause; return; }
echo
log_info "Creating backup..."
local kiosk_owner
kiosk_owner=$(sudo stat -c '%U' "$KIOSK_DIR" 2>/dev/null || echo "$KIOSK_USER")
local backup_dir="${KIOSK_DIR}/backups/electron_backup_$(date +%Y%m%d_%H%M%S)"
sudo -u "$kiosk_owner" mkdir -p "$backup_dir"
if sudo test -f "$KIOSK_DIR/package.json" 2>/dev/null; then
sudo -u "$kiosk_owner" cp "$KIOSK_DIR/package.json" "$backup_dir/"
fi
if sudo test -f "$KIOSK_DIR/package-lock.json" 2>/dev/null; then
sudo -u "$kiosk_owner" cp "$KIOSK_DIR/package-lock.json" "$backup_dir/"
fi
echo "$current_version" | sudo -u "$kiosk_owner" tee "$backup_dir/electron_version.txt" > /dev/null
log_success "Backup created at: $backup_dir"
echo
ask_yes_no "Proceed with Electron update to $latest_version?" "n" || {
log_info "Update cancelled - backup preserved at: $backup_dir"
pause
return
}
echo
log_info "Stopping kiosk display..."
sudo systemctl stop lightdm 2>/dev/null || true
sleep 2
sudo -u "$KIOSK_USER" sed -i "s/\"electron\": \".*\"/\"electron\": \"^${latest_version}\"/" "$KIOSK_DIR/package.json"
if sudo test -d "$KIOSK_DIR/node_modules/electron" 2>/dev/null; then
sudo -u "$KIOSK_USER" rm -rf "$KIOSK_DIR/node_modules/electron"
fi
log_info "Installing Electron $latest_version (this may take a few minutes)..."
if sudo -u "$KIOSK_USER" bash -c "cd '$KIOSK_DIR' && npm install electron@'$latest_version'"; then
log_success "Electron updated to $latest_version"
local sandbox="$KIOSK_DIR/node_modules/electron/dist/chrome-sandbox"
if sudo test -f "$sandbox" 2>/dev/null; then
sudo chown root:root "$sandbox"
sudo chmod 4755 "$sandbox"
fi
if ask_yes_no "Restart kiosk display now?" "y"; then
sudo systemctl start lightdm
sleep 3
if systemctl is-active --quiet lightdm; then
log_success "Kiosk display started"
else
log_error "Kiosk display failed to start - check: sudo journalctl -u lightdm -n 50"
fi
else
log_info "Start manually with: sudo systemctl start lightdm"
fi
log_success "Backup preserved at: $backup_dir (delete once confirmed working)"
else
log_error "Electron install failed - restoring from backup..."
if sudo test -f "$backup_dir/package.json" 2>/dev/null; then
sudo -u "$KIOSK_USER" cp "$backup_dir/package.json" "$KIOSK_DIR/"
fi
if sudo -u "$KIOSK_USER" bash -c "cd '$KIOSK_DIR' && npm install"; then
log_success "Restored original Electron installation"
sudo systemctl start lightdm
else
log_error "Failed to restore - manual intervention required"
fi
fi
pause
}
action_repair_electron() {
echo
echo "This will:"
echo " 1. Check if the Electron binary is present"
echo " 2. Download it if missing (~120MB)"
echo " 3. Fix chrome-sandbox permissions (setuid root)"
echo " 4. Restart the kiosk display"
echo
ask_yes_no "Continue?" "y" || { echo "Cancelled"; pause; return; }
sudo systemctl stop lightdm 2>/dev/null || true
sleep 1
if ! electron_install_binary; then
log_error "Could not install Electron. Check internet and retry."
pause
return 1
fi
log_info "Restarting kiosk display..."
sudo systemctl restart lightdm
sleep 3
if systemctl is-active --quiet lightdm && pgrep -f "electron.*main.js" &>/dev/null; then
log_success "Kiosk display is running"
else
log_warning "LightDM started but Electron may still be loading."
echo " Check: sudo tail -20 $KIOSK_DIR/../electron.log"
fi
pause
}
+306
View File
@@ -0,0 +1,306 @@
#!/bin/bash
################################################################################
# menus/advanced_emergency_hotspot.sh - "Emergency Hotspot" (Advanced):
# auto-starts a WiFi hotspot if no internet is detected 60 seconds after
# boot, so the kiosk can be reached and reconfigured remotely.
#
# Writes a standalone runtime script ($BIN_DIR/kiosk-emergency-hotspot)
# plus a oneshot systemd unit ($SYSTEMD_DIR) that runs it at boot - both
# of those paths are ours to place, so (like power_schedule and every
# other addon) they're parameterized instead of hardcoded. hostapd/
# dnsmasq/iptables themselves are real apt packages with their own fixed
# config locations, stubbed at the command level in tests like CUPS.
#
# The runtime script itself is a template: everything written with `\$`
# below stays literal and only resolves when the script actually runs at
# boot (on the real machine, not in this tool); only the un-escaped
# $wifi_iface/$hotspot_ssid/$hotspot_pass/$hotspot_ip/$KIOSK_USER/
# $KIOSK_DIR are substituted once, at configuration time.
#
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
################################################################################
EMERGENCY_HOTSPOT_SCRIPT="$BIN_DIR/kiosk-emergency-hotspot"
emergency_hotspot_is_configured() {
[[ -f "$EMERGENCY_HOTSPOT_SCRIPT" ]]
}
emergency_hotspot_ssid() {
grep '^HOTSPOT_SSID=' "$EMERGENCY_HOTSPOT_SCRIPT" 2>/dev/null | cut -d'=' -f2 | tr -d '"' || true
}
advanced_emergency_hotspot_status() {
if emergency_hotspot_is_configured; then
local ssid
ssid=$(emergency_hotspot_ssid)
echo "Emergency Hotspot: Configured (SSID: ${ssid:-unknown})"
else
echo "Emergency Hotspot: Not configured"
fi
echo " Auto-starts a WiFi hotspot if no internet is detected 60"
echo " seconds after boot, so you can connect and reconfigure remotely."
}
advanced_emergency_hotspot_menu_builder() {
if emergency_hotspot_is_configured; then
MENU_LABELS=("Reconfigure" "Disable")
MENU_HANDLERS=(action_configure_emergency_hotspot action_disable_emergency_hotspot)
else
MENU_LABELS=("Enable emergency hotspot")
MENU_HANDLERS=(action_configure_emergency_hotspot)
fi
}
advanced_emergency_hotspot_menu() {
run_menu "EMERGENCY HOTSPOT" advanced_emergency_hotspot_menu_builder advanced_emergency_hotspot_status
}
################################################################################
# Actions
################################################################################
action_configure_emergency_hotspot() {
echo
if ! sudo apt install -y hostapd dnsmasq iptables; then
log_error "Failed to install hostapd/dnsmasq/iptables"
pause
return 1
fi
sudo systemctl stop hostapd dnsmasq 2>/dev/null || true
sudo systemctl disable hostapd dnsmasq 2>/dev/null || true
local wifi_iface
wifi_iface=$(ls /sys/class/net 2>/dev/null | grep -E "^wl" | head -1 || true)
if [[ -z "$wifi_iface" ]]; then
log_error "No WiFi interface found"
pause
return 1
fi
echo "WiFi interface: $wifi_iface"
echo
local hotspot_ssid
hotspot_ssid=$(ask_text "Hotspot SSID" "Kiosk-Emergency")
local hotspot_pass=""
while [[ ${#hotspot_pass} -lt 8 ]]; do
read -r -s -p "Hotspot password (8+ chars): " hotspot_pass
echo
[[ ${#hotspot_pass} -lt 8 ]] && log_error "Password must be at least 8 characters"
done
local hotspot_ip="192.168.50.1"
sudo mkdir -p "$BIN_DIR"
sudo tee "$EMERGENCY_HOTSPOT_SCRIPT" > /dev/null <<EOF
#!/bin/bash
################################################################################
### KIOSK EMERGENCY HOTSPOT
### Auto-starts if no internet connection 60 seconds after boot
################################################################################
WIFI_IFACE="$wifi_iface"
HOTSPOT_SSID="$hotspot_ssid"
HOTSPOT_PASS="$hotspot_pass"
HOTSPOT_IP="$hotspot_ip"
KIOSK_USER="$KIOSK_USER"
# Wait 60 seconds after boot
sleep 60
# Check for internet connectivity
if ping -c 3 -W 5 8.8.8.8 >/dev/null 2>&1; then
logger "KIOSK: Internet connected - emergency hotspot not needed"
exit 0
fi
logger "KIOSK: No internet detected - starting emergency hotspot"
# Stop any conflicting services
systemctl stop wpa_supplicant 2>/dev/null || true
ip link set \$WIFI_IFACE down 2>/dev/null || true
sleep 2
# Configure static IP for hotspot
ip addr flush dev \$WIFI_IFACE
ip addr add \${HOTSPOT_IP}/24 dev \$WIFI_IFACE
ip link set \$WIFI_IFACE up
# Configure dnsmasq
cat > /tmp/dnsmasq-hotspot.conf <<DNSMASQ
interface=\$WIFI_IFACE
dhcp-range=192.168.50.10,192.168.50.50,12h
dhcp-option=3,\$HOTSPOT_IP
dhcp-option=6,\$HOTSPOT_IP
server=8.8.8.8
log-queries
log-dhcp
DNSMASQ
# Start dnsmasq
dnsmasq -C /tmp/dnsmasq-hotspot.conf
# Configure hostapd
cat > /tmp/hostapd-hotspot.conf <<HOSTAPD
interface=\$WIFI_IFACE
driver=nl80211
ssid=\$HOTSPOT_SSID
hw_mode=g
channel=6
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=\$HOTSPOT_PASS
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
HOSTAPD
# Start hostapd
hostapd -B /tmp/hostapd-hotspot.conf
# Enable IP forwarding (optional - for internet sharing if wired connection exists)
echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true
# Show notification on kiosk display
sudo -u \$KIOSK_USER DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/\$(id -u \$KIOSK_USER)/bus \\
notify-send -u critical -t 0 "Emergency Hotspot Active" \\
"SSID: \$HOTSPOT_SSID\\nPassword: \$HOTSPOT_PASS\\nConnect to: http://\$HOTSPOT_IP" 2>/dev/null || true
logger "KIOSK: Emergency hotspot started - SSID: \$HOTSPOT_SSID, IP: \$HOTSPOT_IP"
# Create on-screen notification HTML
sudo -u \$KIOSK_USER tee /tmp/hotspot-notification.html > /dev/null <<'NOTIFY'
<!DOCTYPE html>
<html>
<head>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: rgba(0,0,0,0.95);
color: white;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.container {
text-align: center;
padding: 40px;
background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
max-width: 600px;
}
h1 { font-size: 48px; margin-bottom: 20px; }
.icon { font-size: 72px; margin-bottom: 20px; }
.info { font-size: 24px; margin: 20px 0; line-height: 1.6; }
.credential {
background: rgba(0,0,0,0.3);
padding: 15px;
border-radius: 10px;
margin: 10px 0;
font-family: monospace;
font-size: 20px;
}
.dismiss {
margin-top: 30px;
padding: 15px 40px;
font-size: 18px;
background: white;
color: #e74c3c;
border: none;
border-radius: 10px;
cursor: pointer;
font-weight: bold;
}
.dismiss:hover { background: #ecf0f1; }
</style>
</head>
<body>
<div class="container">
<div class="icon">📡</div>
<h1>Emergency Hotspot Active</h1>
<div class="info">No internet connection detected<br>Hotspot created for remote access</div>
<div class="credential">SSID: <strong>\$HOTSPOT_SSID</strong></div>
<div class="credential">Password: <strong>\$HOTSPOT_PASS</strong></div>
<div class="credential">Connect to: <strong>http://\$HOTSPOT_IP</strong></div>
<button class="dismiss" onclick="window.close()">Dismiss</button>
</div>
<script>
// Auto-dismiss after 5 minutes
setTimeout(() => window.close(), 300000);
</script>
</body>
</html>
NOTIFY
# Show notification window if Electron is running
if pgrep -f "electron.*main.js" >/dev/null 2>&1; then
sudo -u \$KIOSK_USER DISPLAY=:0 \\
"$KIOSK_DIR/node_modules/electron/dist/electron" \\
/tmp/hotspot-notification.html &
fi
exit 0
EOF
sudo chmod +x "$EMERGENCY_HOTSPOT_SCRIPT"
sudo mkdir -p "$SYSTEMD_DIR"
sudo tee "$SYSTEMD_DIR/kiosk-emergency-hotspot.service" > /dev/null <<UNITEOF
[Unit]
Description=Kiosk Emergency Hotspot
After=network.target lightdm.service
Wants=network.target
[Service]
Type=oneshot
ExecStart=${EMERGENCY_HOTSPOT_SCRIPT}
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
UNITEOF
sudo systemctl daemon-reload 2>/dev/null || true
# Enable only, not start: this is a boot-time oneshot that waits 60s
# and checks connectivity - starting it right now would just run that
# wait/check immediately, which isn't what "configure" means here.
if ! sudo systemctl enable kiosk-emergency-hotspot.service 2>/dev/null; then
log_warning "Hotspot files written, but 'systemctl enable' failed - check 'systemctl status kiosk-emergency-hotspot.service'"
fi
echo
log_success "Emergency hotspot configured"
echo " SSID: $hotspot_ssid"
echo " Password: $hotspot_pass"
echo " IP: $hotspot_ip"
echo
echo "Hotspot auto-starts if no internet is detected 60 seconds after boot."
pause
}
action_disable_emergency_hotspot() {
echo
ask_yes_no "Disable emergency hotspot?" "n" || { echo "Cancelled"; pause; return; }
emergency_hotspot_do_disable
pause
}
# Shared with Complete Uninstall - same reasoning as cups_do_uninstall.
emergency_hotspot_do_disable() {
sudo systemctl stop kiosk-emergency-hotspot.service 2>/dev/null || true
sudo systemctl disable kiosk-emergency-hotspot.service 2>/dev/null || true
sudo rm -f "$SYSTEMD_DIR/kiosk-emergency-hotspot.service"
sudo rm -f "$EMERGENCY_HOTSPOT_SCRIPT"
sudo systemctl daemon-reload 2>/dev/null || true
log_success "Emergency hotspot disabled"
}
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
################################################################################
# menus/advanced_factory_reset.sh - "Factory Reset" (Advanced): wipe
# config.json back to script defaults without touching anything else.
#
# Deliberately narrow - this only removes $CONFIG_PATH. Installed addons
# (CUPS, LMS, VPNs, etc.), the kiosk user, and the system itself are left
# alone; that's what Complete Uninstall is for.
#
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
################################################################################
advanced_factory_reset_status() {
if sudo -u "$KIOSK_USER" test -f "$CONFIG_PATH" 2>/dev/null; then
echo "Config: $CONFIG_PATH exists"
else
echo "Config: not found (already at defaults)"
fi
}
advanced_factory_reset_menu_builder() {
MENU_LABELS=("Reset configuration to defaults")
MENU_HANDLERS=(action_factory_reset)
}
advanced_factory_reset_menu() {
run_menu "FACTORY RESET" advanced_factory_reset_menu_builder advanced_factory_reset_status
}
################################################################################
# Actions
################################################################################
action_factory_reset() {
echo
echo "This resets $CONFIG_PATH to defaults - sites, schedules,"
echo "password protection, and every other setting stored there are"
echo "cleared. Installed addons (CUPS, LMS, VPNs, etc.) are not touched."
echo
ask_yes_no "Continue?" "n" || { echo "Cancelled"; pause; return; }
sudo -u "$KIOSK_USER" rm -f "$CONFIG_PATH"
log_success "Configuration reset - reconfigure via Core Settings"
pause
}
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
################################################################################
# menus/advanced_virtual_consoles.sh - "Virtual Consoles" (Advanced): toggle
# Ctrl+Alt+F1-F8 terminal login access for troubleshooting.
#
# Real system state: masks/unmasks the getty@ttyN systemd units and writes
# a fixed-path X11 server-flags file. Neither is relocatable (X11 only
# reads /etc/X11/xorg.conf.d/, and getty units are always system units),
# so tests use full command-level `sudo` stubbing, same approach as CUPS.
#
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
################################################################################
vconsoles_are_disabled() {
local getty_masked=false
local vt_switch_disabled=false
if systemctl is-masked --quiet getty@tty1.service 2>/dev/null; then
getty_masked=true
fi
if [[ -f /etc/X11/xorg.conf.d/10-serverflags.conf ]] && \
grep -q 'Option.*"DontVTSwitch".*"true"' /etc/X11/xorg.conf.d/10-serverflags.conf 2>/dev/null; then
vt_switch_disabled=true
fi
[[ "$getty_masked" == "true" || "$vt_switch_disabled" == "true" ]]
}
advanced_virtual_consoles_status() {
if vconsoles_are_disabled; then
echo "Virtual consoles: Disabled"
else
echo "Virtual consoles: Enabled"
fi
}
advanced_virtual_consoles_menu_builder() {
if vconsoles_are_disabled; then
MENU_LABELS=("Enable virtual consoles (Ctrl+Alt+F1-F8 for manual login)")
MENU_HANDLERS=(action_enable_virtual_consoles)
else
MENU_LABELS=("Disable virtual consoles (more secure, kiosk only)")
MENU_HANDLERS=(action_disable_virtual_consoles)
fi
}
advanced_virtual_consoles_menu() {
run_menu "VIRTUAL CONSOLES" advanced_virtual_consoles_menu_builder advanced_virtual_consoles_status
}
################################################################################
# Actions
################################################################################
action_enable_virtual_consoles() {
echo
echo "Enabling virtual consoles..."
for i in {1..8}; do
sudo systemctl unmask "getty@tty${i}.service" 2>/dev/null || true
done
sudo systemctl daemon-reload 2>/dev/null || true
sudo mkdir -p /etc/X11/xorg.conf.d
sudo tee /etc/X11/xorg.conf.d/10-serverflags.conf > /dev/null <<'EOF'
Section "ServerFlags"
# Disable Ctrl+Alt+Backspace (X server kill)
Option "DontZap" "true"
# ALLOW VT switching (Ctrl+Alt+F1-F12)
Option "DontVTSwitch" "false"
# Don't allow clients to disconnect on exit
Option "AllowClosedownGrabs" "false"
EndSection
EOF
log_success "Virtual consoles enabled"
echo " Access with Ctrl+Alt+F1 through Ctrl+Alt+F8"
echo " (Ctrl+Alt+F7 typically returns to the kiosk)"
echo
if ask_yes_no "Restart kiosk display now to apply?" "n"; then
sudo systemctl restart lightdm
else
log_warning "Remember to restart: sudo systemctl restart lightdm"
fi
pause
}
action_disable_virtual_consoles() {
echo
ask_yes_no "Disable all virtual consoles?" "n" || { echo "Cancelled"; pause; return; }
echo "Disabling virtual consoles..."
for i in {1..8}; do
sudo systemctl mask "getty@tty${i}.service" 2>/dev/null || true
done
sudo systemctl daemon-reload 2>/dev/null || true
sudo mkdir -p /etc/X11/xorg.conf.d
sudo tee /etc/X11/xorg.conf.d/10-serverflags.conf > /dev/null <<'EOF'
Section "ServerFlags"
# Disable Ctrl+Alt+Backspace (X server kill)
Option "DontZap" "true"
# DISABLE VT switching (Ctrl+Alt+F1-F12)
Option "DontVTSwitch" "true"
# Don't allow clients to disconnect on exit
Option "AllowClosedownGrabs" "false"
EndSection
EOF
log_success "Virtual consoles disabled"
echo " You can re-enable them from this menu at any time."
echo
if ask_yes_no "Restart kiosk display now to apply?" "n"; then
sudo systemctl restart lightdm
else
log_warning "Remember to restart: sudo systemctl restart lightdm"
fi
pause
}
+151
View File
@@ -0,0 +1,151 @@
#!/bin/bash
################################################################################
# menus/complete_uninstall.sh - "Complete Uninstall" (Core Settings): full
# teardown, returning the machine to its pre-kiosk state.
#
# Composed from every other addon's own silent uninstall helper
# (cups_do_uninstall, vnc_do_uninstall, wireguard_do_uninstall,
# tailscale_do_uninstall, netbird_do_uninstall, lms_do_uninstall,
# squeezelite_do_uninstall, asterisk_intercom_do_uninstall,
# power_schedule_do_remove_all, emergency_hotspot_do_disable) instead of
# re-implementing removal logic for each addon a second time here - if an
# addon's uninstall logic changes, this picks it up automatically. Only
# the pieces no single addon owns - the kiosk user/files, Node.js/
# LightDM/Openbox, polkit rules, leftover systemd units - are handled
# directly below, same as the legacy script.
#
# Ordering matters: every addon teardown runs before the kiosk user is
# removed, because asterisk_intercom_do_uninstall still needs
# `id -u "$KIOSK_USER"` to resolve that user's systemd --user session.
#
# After this runs, the kiosk user (and therefore is_kiosk_installed) is
# gone - install.sh itself will refuse to start against this machine
# again until a fresh install re-provisions it. That's intentional:
# there is nothing left here for this tool to manage.
#
# Depends on: lib/menu.sh, lib/config.sh, and every menus/addon_*.sh /
# menus/power_schedule.sh / menus/advanced_emergency_hotspot.sh being
# sourced first (for the *_do_uninstall helpers above).
################################################################################
complete_uninstall_status() {
echo "⚠ Removes the kiosk user, every addon, and returns this machine"
echo " to its pre-kiosk state. Cannot be undone."
}
complete_uninstall_menu_builder() {
MENU_LABELS=("Completely uninstall the kiosk")
MENU_HANDLERS=(action_complete_uninstall)
}
complete_uninstall_menu() {
run_menu "COMPLETE UNINSTALL" complete_uninstall_menu_builder complete_uninstall_status
}
################################################################################
# Actions
################################################################################
action_complete_uninstall() {
echo
echo "⚠️ This will COMPLETELY REMOVE:"
echo " • Kiosk user and all data"
echo " • All kiosk configuration and sites"
echo " • All Electron/Node.js installations"
echo " • All browser caches and data"
echo " • CUPS printer system"
echo " • Squeezelite and LMS (Lyrion Music Server)"
echo " • Remote access (VNC, WireGuard, Tailscale, Netbird)"
echo " • Asterisk Intercom (Baresip)"
echo " • LightDM and Openbox"
echo " • All kiosk schedules and services"
echo " • Emergency hotspot configuration"
echo
echo "⚠️ This CANNOT be undone!"
echo
local confirm
confirm=$(ask_text "Type UNINSTALL to confirm" "")
if [[ "$confirm" != "UNINSTALL" ]]; then
echo "Cancelled"
pause
return
fi
echo
echo "Beginning complete uninstall..."
echo "[1/12] Stopping kiosk display..."
sudo systemctl stop lightdm 2>/dev/null || true
echo "[2/12] Removing addons..."
cups_do_uninstall
vnc_do_uninstall
wireguard_do_uninstall
tailscale_do_uninstall
netbird_do_uninstall
lms_do_uninstall purge
squeezelite_do_uninstall
asterisk_intercom_do_uninstall purge
echo "[3/12] Removing schedules and emergency hotspot..."
power_schedule_do_remove_all
emergency_hotspot_do_disable
# Must come after every addon teardown above - Asterisk Intercom's
# helper still needs this user to resolve its systemd --user session.
echo "[4/12] Removing kiosk user..."
if id "$KIOSK_USER" &>/dev/null; then
sudo pkill -u "$KIOSK_USER" 2>/dev/null || true
sudo userdel -r "$KIOSK_USER" 2>/dev/null || true
log_success "Kiosk user removed"
fi
echo "[5/12] Removing kiosk files..."
sudo rm -rf "$KIOSK_DIR"
sudo rm -rf "$KIOSK_HOME"
echo "[6/12] Removing remaining systemd units..."
sudo rm -f "$SYSTEMD_DIR"/kiosk-*.service
sudo rm -f "$SYSTEMD_DIR"/kiosk-*.timer
sudo systemctl daemon-reload 2>/dev/null || true
echo "[7/12] Removing remaining scripts..."
sudo rm -f "$BIN_DIR"/kiosk-*
sudo rm -f /etc/udev/rules.d/99-kiosk-hotplug.rules
sudo udevadm control --reload-rules 2>/dev/null || true
echo "[8/12] Removing Node.js..."
sudo apt-get purge -y nodejs npm 2>/dev/null || true
sudo rm -rf /usr/local/lib/node_modules
sudo rm -rf /usr/local/bin/node
sudo rm -rf /usr/local/bin/npm
echo "[9/12] Removing LightDM and Openbox..."
sudo systemctl disable lightdm 2>/dev/null || true
sudo apt-get purge -y lightdm openbox 2>/dev/null || true
echo "[10/12] Removing polkit rules..."
sudo rm -f "$POLKIT_DIR/kiosk-power.pkla"
sudo rm -f "$POLKIT_DIR/kiosk-printing.pkla"
echo "[11/12] Re-enabling virtual consoles..."
for i in {1..8}; do
sudo systemctl unmask "getty@tty${i}.service" 2>/dev/null || true
done
sudo systemctl daemon-reload 2>/dev/null || true
echo "[12/12] Cleaning up packages..."
sudo apt-get autoremove -y 2>/dev/null || true
sudo apt-get autoclean 2>/dev/null || true
echo
log_success "Kiosk completely uninstalled"
echo "The system has been returned to its pre-kiosk state."
echo "You may want to reboot to ensure all changes take effect."
echo
if ask_yes_no "Reboot now?" "n"; then
echo "Rebooting in 3 seconds..."
sleep 3
sudo reboot
fi
}
+5
View File
@@ -628,7 +628,12 @@ action_disable_electron_reload() {
action_remove_all_schedules() { action_remove_all_schedules() {
echo echo
ask_yes_no "Remove ALL power/display/quiet/reload schedules?" "n" || { echo "Cancelled"; return; } ask_yes_no "Remove ALL power/display/quiet/reload schedules?" "n" || { echo "Cancelled"; return; }
power_schedule_do_remove_all
}
# The actual removal, no prompt - shared with Complete Uninstall so that
# operation doesn't need to re-implement schedule teardown a second time.
power_schedule_do_remove_all() {
for timer in kiosk-shutdown kiosk-display-off kiosk-display-on kiosk-quiet-start kiosk-quiet-end kiosk-electron-reload; do for timer in kiosk-shutdown kiosk-display-off kiosk-display-on kiosk-quiet-start kiosk-quiet-end kiosk-electron-reload; do
sudo systemctl stop "${timer}.timer" 2>/dev/null || true sudo systemctl stop "${timer}.timer" 2>/dev/null || true
sudo systemctl disable "${timer}.timer" 2>/dev/null || true sudo systemctl disable "${timer}.timer" 2>/dev/null || true
+103 -2
View File
@@ -1,8 +1,109 @@
#!/bin/bash #!/bin/bash
################################################################################ ################################################################################
### Ubuntu Based Kiosk v2.9.0 ### ### Ubuntu Based Kiosk v2.12.0 ###
################################################################################ ################################################################################
# #
# RELEASE v2.12.0 - Complete Uninstall Migrated (Last of the
# "Destructive Trio"); Composed, Not Re-Implemented
# - New in ./install.sh's Core Settings menu: Complete Uninstall
# (menus/complete_uninstall.sh). Rather than re-implementing every
# addon's teardown a second time (the shape this function had in the
# legacy script - CUPS/VNC/WireGuard/Tailscale/Netbird/LMS/Squeezelite
# removal logic all inlined again, independently of the same logic in
# each addon's own uninstall action), it composes the *_do_uninstall
# helpers each addon already has. If an addon's removal logic changes,
# Complete Uninstall picks it up automatically instead of silently
# drifting out of sync.
# - Every addon menu that had an uninstall action (CUPS, VNC, WireGuard,
# Tailscale, Netbird, LMS, Squeezelite, Asterisk Intercom) plus
# power_schedule's "remove all schedules" and the Emergency Hotspot
# disable action were each split into a confirm-and-call wrapper (the
# existing interactive action, unchanged from the user's perspective)
# and a silent do-the-removal helper that both the wrapper and
# Complete Uninstall call - no duplicated removal logic anywhere.
# - IMPORTANT bug found and fixed while composing these: several
# *_do_uninstall helpers (CUPS's `apt autoremove`/`apt clean`, and
# VNC/WireGuard/Tailscale/Netbird's `apt remove`) had a bare, unguarded
# `apt` call as their second-to-last statement. Previously this only
# risked aborting that one menu action if the package was already
# gone (silently caught by run_menu's own guard) - a minor UX
# blemish. Composed together as bare sequential calls inside Complete
# Uninstall, the same failure would have silently truncated the
# *entire* uninstall sequence partway through - e.g. the kiosk user
# might never get removed because an already-uninstalled VPN client's
# `apt remove` failed first. Guarded all of them with `|| true`,
# fixing the risk in both the standalone action and the composition.
# - Non-addon teardown (kiosk user/files, Node.js, LightDM/Openbox,
# remaining systemd units/scripts, polkit rules, re-enabling virtual
# consoles, final package cleanup) stays inline in
# menus/complete_uninstall.sh, same as the legacy script, since no
# single addon owns those paths.
# - Upgrade and Full Reinstall remain in ubuntu-based-kiosk.sh only -
# both are fundamentally coupled to this file's own heredoc self-
# extraction of main.js/preload.js/etc, which has no equivalent in the
# modular system yet. This closes out the "destructive trio."
#
# RELEASE v2.11.0 - 4 More Advanced Items Migrated (Electron Maintenance,
# Factory Reset, Virtual Consoles, Emergency Hotspot)
# - New in ./install.sh's Advanced menu, alongside Diagnostics:
# - menus/advanced_electron.sh - "Electron Maintenance": the legacy
# "Manual Electron Update" and "Fix Blank Screen" combined under one
# submenu, since both maintain the same installation and share the
# binary-repair logic (electron_install_binary).
# - menus/advanced_factory_reset.sh - "Factory Reset": wipes
# config.json back to defaults only - addons are untouched.
# - menus/advanced_virtual_consoles.sh - "Virtual Consoles": toggles
# Ctrl+Alt+F1-F8 terminal login access.
# - menus/advanced_emergency_hotspot.sh - "Emergency Hotspot": auto-
# starts a WiFi hotspot if no internet is detected 60 seconds after
# boot. Its own runtime script and systemd unit now go through
# $BIN_DIR/$SYSTEMD_DIR like every other addon's own files, instead
# of the legacy's hardcoded /usr/local/bin and /etc/systemd/system.
# - That leaves Diagnostics' original 4 items plus these 4 covering 8 of
# the legacy Advanced menu's 12 entries. Not migrated this round:
# Export/Import Settings (kept in the legacy script pending a decision
# on whether it's worth rebuilding around actual paths instead of a
# hardcoded per-addon step list, or whether the future web UI replaces
# the need for it) and Fix Squeezelite Audio (small and specific
# enough that it may fold into menus/addon_lms_squeezelite.sh instead
# of staying a standalone Advanced entry - not decided yet).
# - Complete Uninstall (the last of the "destructive trio") is next,
# composed from each addon's own uninstall action plus core teardown
# rather than rewriting removal logic a second time. Upgrade and Full
# Reinstall stay in this script for now: both are fundamentally
# coupled to this file's own heredoc self-extraction of main.js/
# preload.js/etc, which has no equivalent yet in the modular system.
#
# RELEASE v2.10.0 - Asterisk Intercom Migrated, Redesigned as a SIP
# Extension Client (No More PBX Server Install)
# - New in ./install.sh: Asterisk Intercom (menus/addon_asterisk_intercom.sh).
# The legacy addon offered three options: Client Only (a Baresip SIP
# client), Server Only, and Full (server + client) - the latter two
# downloaded and ran a third-party installer from a separate "Easy
# Asterisk" repository to stand up a whole Asterisk PBX. That
# repository has since gone through a major rework upstream, so this
# migration drops the PBX-install path entirely rather than carrying
# a dependency on code that's moved on without it. The addon now does
# only the client/endpoint piece: install Baresip and register it as
# one SIP extension against an Asterisk server the user already has
# running somewhere else. It never installs or manages Asterisk
# itself. The legacy script's own three-option version is untouched -
# both copies coexist deliberately, same as every other migrated menu.
# - Dropped the legacy client path's dependency on the (now-reworked)
# Easy Asterisk repo's GitHub API for version tracking. It now reads
# the real installed `baresip` package version via dpkg instead - one
# less network dependency and one less thing to keep in sync with an
# external repo.
# - New capability: an uninstall option for the Baresip client, which
# the legacy addon never had at all.
# - Bug fix (found while porting): `baresip_installed_version()`'s
# `dpkg-query` call fails (as expected) when the package isn't
# installed, and the unguarded `ver=$(...)` assignment around it would
# have crashed the whole session under this tool's `set -e` the first
# time status was checked before Baresip was installed. Guarded with
# `|| true` - the same class of bug hunted throughout this migration,
# caught by testing before it shipped.
#
# RELEASE v2.9.0 - LMS Server / Squeezelite Player Migrated; # RELEASE v2.9.0 - LMS Server / Squeezelite Player Migrated;
# is_service_enabled() Dead Pre-Check Fixed # is_service_enabled() Dead Pre-Check Fixed
# - New in ./install.sh: LMS Server / Squeezelite Player # - New in ./install.sh: LMS Server / Squeezelite Player
@@ -349,7 +450,7 @@ set -euo pipefail
### SECTION 1: CONSTANTS & GLOBALS ### SECTION 1: CONSTANTS & GLOBALS
################################################################################ ################################################################################
SCRIPT_VERSION="2.9.0" SCRIPT_VERSION="2.12.0"
# Resolve the real path to this script file. # Resolve the real path to this script file.
# When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor, # When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,