Two bugs caused the login screen to appear on new Ubuntu 24.04 server hardware:
1. Ubuntu 24.04's PAM config checks 'user ingroup nopasswdlogin', not 'autologin'.
Add kiosk user to nopasswdlogin group (and autologin for older versions).
2. The upgrade path never wrote /etc/lightdm/lightdm.conf.d/10-kiosk.conf,
so on new hardware running through upgrade the file simply didn't exist.
Refactor: extract configure_lightdm_autologin() shared helper called from
both fresh install (step 19/27) and upgrade, so both paths are consistent.
Also use [Seat:*] instead of [SeatDefaults] for forward compatibility.
https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
[SeatDefaults] is silently ignored by LightDM on newer Ubuntu versions.
Replace with [Seat:*] which is the correct section name for Ubuntu 22.04+.
Also add the kiosk user to the autologin group, which newer Ubuntu
requires for passwordless autologin to work.
Without these fixes, LightDM shows the login screen instead of
auto-logging in and launching the kiosk app.
https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
/home/kiosk is mode 700 so root cannot traverse it. Every [[ -f ]] or
[[ ! -f ]] check on paths inside /home/kiosk was silently returning
'not found' even after the kiosk user had successfully written the file.
Replace all three [[ ! -f "$electron_bin" ]] checks and the
[[ -f "$sandbox" ]] check in install_electron_binary with
sudo -u "$KIOSK_USER" test -f so they run in the kiosk user's
security context and can actually see the files.
https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
Accidentally dropped this line when rewriting the extraction block.
mktemp creates the file as root:root 600, so sudo -u kiosk unzip
gets 'Permission denied' trying to open the zipfile.
https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
The previous fix incorrectly ran unzip as root, which fails because
/home/kiosk is not accessible to root. The kiosk user is the right
actor for the extraction, but two things blocked it:
1. node_modules/electron/dist/ can be owned by root when npm's electron
postinstall runs with --unsafe-perm, so the kiosk user gets
'Permission denied' trying to write there. Fix: sudo chown -R the
electron directory to the kiosk user before extracting.
2. With set -euo pipefail active (upgrade call had no || guard), a
failed unzip or chmod would abort the script silently before the
diagnostic error messages could print. Fix: add || true to both
commands so the function always reaches the explicit -f check which
prints the real error and returns 1. The upgrade call already has
|| { log_error ...; return 1; } from the previous commit.
https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
Two bugs combined to cause the 'Electron binary download failed' error
even though the zip downloaded and unzip reported inflating all files:
1. The upgrade path called install_electron_binary bare (no ||), so
set -euo pipefail was active inside the function. Any failing command
(e.g. chmod on a file that wasn't written) killed the script before
the error messages printed. Fresh install used || exit 1, which
disables set -e inside the function body. Upgrade now uses
|| { log_error ...; return 1; } to match.
2. The unzip ran as the kiosk user, but node_modules/electron/dist/ can
be owned by root when npm's electron postinstall script runs with
--unsafe-perm. The kiosk user can't write there, so unzip's write
errors go to stderr (not visible in the log) while inflating: lines
still appear on stdout. The binary is never actually written.
Fix: run mkdir/unzip/chmod as root, then chown -R to kiosk.
https://claude.ai/code/session_01VQ13Fwq4MXxwThLfCXBeGr
mktemp creates the tmp zip owned by root with mode 600.
sudo -u kiosk unzip then fails with "Permission denied".
Add chmod 644 immediately after download so the kiosk user can read it.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
When the script is run via curl|bash or wget|bash, BASH_SOURCE[0] is a
pipe descriptor, not a real file. The upgrade function grep-extracts
heredocs from the script file, so it fails with a confusing path error.
Fixes:
- Set SCRIPT_FILE global at startup (empty string when piped)
- upgrade_kiosk() checks SCRIPT_FILE before asking "Continue?" and shows
a clear message explaining how to download the script to a file first
- Removes the silent failure path (no more cryptic "Cannot find script at
/proc/.../pipe:[...]" error)
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Fresh Ubuntu 24.04 minimal installs don't include unzip. The wget fallback
in install_electron_binary() downloaded the 120MB Electron zip successfully
but then failed on the unzip call. Two fixes:
1. Add unzip to the main apt install step so it's always present.
2. Auto-install unzip inside install_electron_binary() as a safety net for
upgrades on existing systems that may not have it.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Drop the MatchProduct "Finger" restriction from the xorg libinput rule,
leaving only MatchIsTouchscreen "on". MatchIsTouchscreen is set by udev
from hardware capabilities, so it matches finger touch screens of any
brand (ELAN, Goodix, eGalax, Wacom, etc.) while never matching keyboards,
mice, or pen/stylus digitizers (which are tagged as tablets, not
touchscreens). This makes the script work on any touch hardware without
hardcoding device names. Behavior on existing Wacom machines is unchanged
since their finger device matched either way.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Two genuinely separate root causes were behind the dead touchscreen:
1. GNOME keyring grab — under LightDM autologin the keyring stays locked.
When Chromium accessed it, the gcr-prompter unlock dialog grabbed all
keyboard and touch input at the X level. The app rendered (timers ran)
but ignored every tap and keypress. Fix: --password-store=basic stops
Electron from using the keyring, so the dialog never appears.
2. Wacom driver single-touch emulation — the wacom X driver only does
single-touch pointer emulation and never passes real multitouch to
Chromium, so 1-finger and 2-finger swipe gestures could not fire.
Fix: force the finger touch device to the libinput driver via
/etc/X11/xorg.conf.d/99-finger-libinput.conf. libinput delivers proper
XI2 multitouch which Chromium turns into real JS touch events. The
pen/stylus stays on the wacom driver.
Removed the earlier dead-end attempts (xsetwacom MapToOutput / CTM reset,
Wacom Enable Touch Gesture, 99-wacom-touch.conf) which were all chasing the
wrong cause while the keyring grab masked any real testing. The upgrade path
removes the stale 99-wacom-touch.conf so it can't override libinput.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The Wacom driver owns the Coordinate Transformation Matrix and silently
overrides any xinput set-prop changes. xsetwacom MapToOutput tells the
driver to recalculate the CTM for the primary connected output, which is
the correct API and persists across driver resets.
Dynamically detects the primary output (eDP1, HDMI1, DP1, etc.) so the
fix works on any machine without hardcoding a display name.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The Wacom driver can initialise the CTM to all-zeros, which maps every
touch event to screen coordinate (0,0). The touchscreen appears completely
dead even though the hardware and kernel are working correctly.
Reset the CTM to the identity matrix for every touch/finger device at
startup, before launching Electron, so coordinates are always correct.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
xorg uses fnmatch (shell glob) for MatchProduct, where . is a literal
dot. Wacom.*Finger never matched "Wacom HID 48E3 Finger touch" because
there is no literal dot in that string. Wacom*Finger* matches correctly.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Without /etc/X11/xorg.conf.d/99-wacom-touch.conf the Wacom driver initialises
the finger touch device in pointer emulation mode (generating RawButtonPress/
RawButtonRelease/RawMotion). Electron never sees TouchBegin/TouchEnd events so
touchstart/pointerdown(touch) never fire in the renderer.
Setting Option "Gesture" "on" and Option "Touch" "on" at the driver level means
the device initialises in XI2 touch mode on every X server start, regardless of
any post-init xinput set-prop calls.
Added to both fresh install (step 18) and upgrade function.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Without XAUTHORITY set, xinput can fail with "Authorization required"
if the display manager doesn't propagate it through the session environment.
Hardcode the kiosk user's .Xauthority path to guarantee xinput works.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Update all version strings (header, SCRIPT_VERSION, JS VERSION constant)
and rename ubuntu-based-kiosk-v1.0.2.sh → ubuntu-based-kiosk-v1.0.3.sh.
Update README with v1.0.3 change log and archive v1.0.2 as previous.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The xinput loop now scans all input devices at startup, matches anything
with "touch" or "finger" in the name (excluding touchpads/trackpads), and
attempts to enable Wacom touch gestures on each match. Non-Wacom devices
silently ignore the set-prop call, so the loop is safe on any hardware.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Add xinput call to start.sh so Wacom HID 48E3 touch gesture support is
initialized every time the kiosk starts, not just after lightdm restarts.
Also add start.sh to the upgrade extraction list so it is updated in place
instead of keeping the stale version from the original install.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
touchstart/touchend never fire on this device (confirmed by zero [TOUCH]
log entries). The activity tracker already uses pointerdown/pointerup and
works fine, proving PointerEvents reach the preload. Added pointer event
handlers that mirror the touch handlers for all gestures (2-finger swipe,
3-finger toggle, 1-finger arrow keys). A 500ms debounce on the IPC send
prevents double-firing on devices where both event types fire.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
capture:true and --touch-events=enabled were added to handle Authelia's
login page blocking touch events. Authelia now auto-logs in on startup
so the login page never shows. Reverting to the v1.0.0 approach (passive:true
only, no --touch-events flag) which had working two-finger swipe.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
With --ozone-platform=x11, Chromium defaults touch event detection to
'auto' and may not identify the hardware as a touchscreen, so touchstart/
touchend never fire in the renderer. --touch-events=enabled forces W3C
touch events on unconditionally, restoring two-finger swipe navigation.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The upgrade was wiping node_modules then relying on npm to re-download the
~120MB Electron binary. npm returns exit 0 even when the download times out,
leaving the kiosk with no Electron binary and a blank screen on next boot.
node_modules only needs to be deleted on a fresh install or when explicitly
changing Electron version. For a JS-file-only upgrade, npm install without
a wipe is either a no-op (no changes) or applies dependency updates cleanly.
The install_electron_binary fallback remains as a safety net.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Touch handlers used bubble phase (no capture:true), so any page script that
called stopPropagation() on touchstart/touchend — e.g. Authelia's login form
or scroll containers — silently blocked the preload's swipe detection.
Using capture:true fires the preload's listeners in the capture phase (before
any element-level handlers), so swipe works even on pages with their own
touch handling. Applied to both preloads (standard and auto-show keyboard).
Also adds missing [TOUCH] 2-finger HORIZONTAL console.log to the standard
preload so swipe events are visible in electron.log for debugging.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The duplicate-block pitfall (YAML silently ignores duplicate keys, causing
a white screen) is now called out explicitly in both the script's printed
output and the README. Added a before/after example showing the correct
merged result with the kiosk one_factor rule above the two_factor wildcard.
Also explains why one_factor is required (TOTP/WebAuthn need interactive
second step, impossible via API).
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Without a timeout, session.defaultSession.fetch() hangs for 1-2 minutes
on TCP timeout when Authelia is unreachable (wrong URL, server down,
firewall). Since createWindow() awaits autheliaAuthenticate(), the main
window is visible but no BrowserView is attached during that wait —
causing a persistent white screen with ibeam cursor.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
- Step 3 now says MERGE (not replace/append) with clear warning to keep existing config
- access_control: kiosk one_factor rule must go ABOVE any existing two_factor rule,
with explanation that Authelia applies rules top-down (first match wins)
- session block: keep existing values; only add the block if none exists yet
- Kiosk can only do one_factor — TOTP/WebAuthn via API is not possible
- Updated in both configure_authelia() printed output and README Authentication section
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The upgrade_kiosk() path deleted node_modules then ran npm install but
never checked the binary or set chrome-sandbox permissions — so every
upgrade produced a blank screen.
Changes:
- Extract electron binary verification, fallback downloads, and
chrome-sandbox chmod 4755 into a shared install_electron_binary()
function called by both step 17/27 (fresh install) and step 5/6
(upgrade_kiosk) so neither path can silently skip the permission fix
- Add repair_electron() function: stops display, re-runs
install_electron_binary, restarts lightdm — no SSH needed
- Wire repair_electron as Advanced menu option 12 "Fix Blank Screen"
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
The ~120MB Electron binary download was silently failing because npm's
default 60s fetch timeout is too short on slower connections.
Changes to step 17/27:
- Set npm fetch-timeout to 600s and retries to 5 before running npm install
- If binary still missing after npm install, retry via install.js with
ELECTRON_FORCE_DOWNLOAD=true
- If still missing, fall back to direct wget download of the exact
versioned zip from GitHub releases (300s timeout, 3 tries, shows progress)
- Exit 1 with clear message if all three attempts fail
- Log chrome-sandbox permission step for visibility
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
/home/kiosk/ has 750 permissions so the install user can't read inside
it. The -f check and both jq reads were running as the current user and
failing silently, causing the false "config.json not found" error.
Changed:
[[ ! -f "$config_file" ]] → sudo test -f "$config_file"
jq -r ... "$config_file" → sudo -u kiosk jq -r ... "$config_file"
jq ... > "$tmp" → sudo -u kiosk jq ... > "$tmp" && sudo mv
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
npm install returns 0 even when Electron's postinstall binary download
fails, leaving node_modules/electron/dist/electron missing and causing
a blank screen with no useful error.
After npm install, explicitly check for the binary. If absent, retry
via ELECTRON_FORCE_DOWNLOAD=true node install.js. If still missing,
print a clear error and exit 1 instead of silently continuing to a
broken install.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
New file ubuntu-based-kiosk-v1.0.2.sh containing all changes made
since v1.0.1:
- Authelia auto-login addon (Addons → 5): AES-256 encrypted credentials,
startup API auth, full Dockerized server-side setup printed on save
- Fix: PipeWire .config dirs created as root caused Permission denied
at step [5.5/27] on fresh Ubuntu 24.04 minimal installs
- README install commands now pull latest script dynamically via
GitHub contents API (no more hardcoded version numbers)
SCRIPT_VERSION and VERSION constants updated to 1.0.2.
README changelog and current version updated to 1.0.2.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
After configure_authelia() saves credentials, it now prints the full
Dockerized Authelia server-side checklist: argon2 hash generation
command, users.yml kiosk user template, configuration.yml session
duration and access_control rules, and a docker compose restart step.
README gains a new Authentication section under Optional Add-ons
covering the same steps in Markdown with a table comparing Authelia
SSO vs HTTP Basic Auth (both can coexist).
Also clarifies that the Authelia password is encrypted at rest and
not stored in plain text.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Adds an Authelia Auto-Login addon (Addons menu → 5) that:
- Prompts for Authelia URL, username, and password
- Encrypts the password with AES-256-CBC keyed from /etc/machine-id
via scrypt (the encrypted blob is machine-specific and useless elsewhere)
- Stores autheliaURL, autheliaUsername, autheliaEncryptedPassword in config.json
On every kiosk startup, main.js decrypts the password and calls
Authelia's /api/firstfactor with keepMeLoggedIn:true before any
BrowserViews are created. Electron's session.defaultSession handles
the Set-Cookie response automatically, so all sites load already
authenticated.
To set up credentials via SSH:
ssh user@kiosk
./ubuntu-based-kiosk-v*.sh → Addons → 5. Authelia Auto-Login
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
sudo mkdir -p created the .config/pipewire/pipewire.conf.d directories
owned by root, causing the subsequent sudo -u kiosk tee to fail with
"Permission denied" at step [5.5/27] on a fresh install.
Switching to sudo -u kiosk mkdir -p ensures the directories are owned
by the kiosk user before the tee writes into them.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
Quick Install section now queries the GitHub contents API to find and
download the latest ubuntu-based-kiosk-v*.sh script dynamically, so the
README never needs a manual version bump when a new release is pushed.
Post-install "run again" references use `ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1`
for the same reason.
Also bumps version references from 1.0.0 → 1.0.1, Electron 41 → 42,
Node.js 20 → 22, and adds the v1.0.1 changelog entry.
https://claude.ai/code/session_01EyjEQLWbTXcZgbMDarf7NU
NetBird v0.60 changed SSH to JWT/IdP-based auth by default.
Using --disable-ssh-auth keeps access controlled purely by NetBird
ACL policies (machine-level, like pre-v0.60 behaviour) without
requiring an identity provider or OIDC flow.
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
All 6 popup BrowserWindows (lockout, prompt, pause, pin, keyboard ×2)
now use contextIsolation:true + popup-preload.js instead of the
deprecated nodeIntegration:true pattern. A new popup-preload.js file
exposes crypto.hashPassword, fs.readPin, ipcRenderer.send/on/once to
the renderer via contextBridge. All affected HTML files updated to use
window.electronAPI.* instead of direct require('electron') calls.
The popup-preload.js heredoc is also added to the upgrade extract_file
list so upgrades re-extract it correctly.
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
BrowserView has been deprecated since Electron 29 and will be removed
in a future major release. This beta migrates all usage to the
WebContentsView API introduced in Electron 28.
Changes in main.js:
- Import WebContentsView instead of BrowserView
- Add bringViewToTop() helper (remove+re-add as last child = on top)
- createWindow: new WebContentsView / contentView.addChildView
- attachView: contentView.removeChildView + bringViewToTop
- showLockoutScreen: contentView.removeChildView for all views
- unlockScreen: bringViewToTop to restore hidden view
- returnToTabs: bringViewToTop instead of setTopBrowserView
- showHiddenTab: bringViewToTop instead of setTopBrowserView
v1.0.0 is kept unchanged. The legacy update_mainjs_keyboard() patch
function is guarded by a grep check that prevents it running against
the new WebContentsView-based main.js.
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
No breaking changes affecting the kiosk app between these versions:
- BrowserView still present (deprecated but not removed until future release)
- WebContentsView destroyed-event change does not apply (app uses BrowserView)
- Session.clearStorageData quotas removal not used
- PDF OOPIF change not relevant
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
- Bump version references from 0.9.9.1/0.9.8 to 1.0.0
- Update script filename to ubuntu-based-kiosk-v1.0.0.sh throughout
- Add v1.0.0 changelog entries (upgrade fix, sudo/timezone fixes)
- Preserve prior version history (v0.9.9.1, v0.9.8) as changelog
- Update Claude model reference to Sonnet 4.6
- Update last-updated date
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
Two issues caused the timezone step to fail on first run:
1. Sudo credential cache (default 15 min) can expire during the long
apt install step before configure_timezone runs. Added `sudo -v`
immediately after the install confirmation prompt to prime the cache
as late as possible, just before the first long-running step.
2. `sudo timedatectl set-timezone` can fail with "Access denied" if
polkit/D-Bus is not yet fully ready in the install environment.
Added a direct fallback (ln -sf localtime + tee /etc/timezone)
that bypasses D-Bus entirely.
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
On Ubuntu 22.04+, useradd creates home directories with 750 permissions,
so the non-root user running the script cannot traverse /home/kiosk to
check file existence with [[ -s ]]. sudo tee (running as root) writes the
files successfully, but the bash test always returned false, falsely
reporting all extractions as failed.
Switch to `sudo test -s` to match the pattern already used elsewhere in
the script (line ~10320) when checking files under /home/kiosk.
https://claude.ai/code/session_01M3tiofbGfmTddeMcXr8nXr
- Rename install_kiosk_v0.9.9.1.sh to ubuntu-based-kiosk.sh
- Update all repository URLs from ubk to ubuntu-based-kiosk
- Remove UBK abbreviation from documentation and script headers
- Update installation instructions in Readme.md
- Power menu: Send IPC to views[] instead of mainWindow.webContents
(preload.js runs in BrowserViews, not mainWindow)
- Export: Add chmod 777 to temp dir, use sudo for all file operations,
use sudo tar and fix archive ownership
- Import: Use sudo tar, add proper permissions to temp directory
- PIN entry window: auto-closes after 30 seconds of inactivity
- Pause dialog: auto-closes after 30 seconds of inactivity
- Power menu: converted from native dialog to custom overlay with
30-second timeout (lockout mode still uses native dialog)
- Nav menu already had 30-second timeout
All modal windows and overlays now automatically dismiss after
30 seconds to prevent screens being left open indefinitely.
- Power button now starts hidden and appears on user interaction
- Auto-hides after 5 seconds of inactivity (matching nav button)
- Increased size to 60px with 3px border for consistency
- Removed hover transitions for simpler, consistent behavior
- Add red power icon button in top-right corner of UI
- Click power button triggers showPowerMenu via IPC
- Add EPIPE error suppression for stdout/stderr (no more error dialogs)
- Fix display on/off scripts: add XAUTHORITY, hardcode kiosk user
- Add logging to display scripts for debugging
- Simplified power button script runs as root from acpid
- No longer needs DISPLAY/XAUTHORITY (just sends signal)
- Finds all Electron processes and sends SIGUSR1 to each
- Updated ACPI event handlers to call /usr/local/bin/kiosk-power-button.sh
- Updated test-power-button to actually trigger and test
- Upgrade function now installs simplified handler
When pressing Ctrl or Alt on the virtual keyboard, the literal text
"Control" or "Alt" was being inserted into text fields. Now these
modifier keys are properly ignored since they don't function as
standalone keys in input fields.
- Create kiosk directory if it doesn't exist before extraction
- Use 'sudo tee' instead of 'sudo -u kiosk tee' for reliable writes
- Add line numbers to output for debugging
- chown at end fixes permissions
The awk-based extraction had variable scoping issues when run
as a nested function. Switch to grep for finding line numbers
and sed for extraction - more reliable approach.
- Replace sed with awk for more reliable heredoc extraction
- Add debug output showing script path
- Fix restart to use lightdm instead of non-existent kiosk.service
- Kill electron process before upgrade, check electron after
- Add script path validation before extraction
- Silent upgrade: extracts app files from script without user input
- Import now shows numbered list, user can select by number
- Improved power button handler with better Electron process detection
- SIGUSR1 now primary method for power menu (more reliable)
- Upgrade automatically regenerates power button handler
- Added XAUTHORITY export for X11 authentication
- New upgrade_kiosk() function in Core Settings menu (option 10)
- Auto-exports config, timers, and addon configs before reinstall
- Auto-imports everything after reinstall completes
- User just needs to press Enter through installer prompts
- Also fixed SUDO_USER unbound variable issues
- Use ${SUDO_USER:-} to avoid unbound variable error with set -u
- Fall back to USER, then /tmp if neither is set
- Validate home_dir exists and is writable
- Use whoami instead of $USER in scp hint
The script uses 'set -euo pipefail' which causes ((var++)) to exit
when var is 0, since the expression evaluates to 0 (falsey).
Changed to var=$((var + 1)) which always succeeds.
Export now includes:
- WireGuard: /etc/wireguard/*.conf files
- Netbird: config.json, state directory (machine keys), user config
- OpenVPN: /etc/openvpn/ directory
- Tailscale: notes installation status (requires re-auth)
Import restores all VPN configs and auto-enables services.
Warns if VPN software not installed after restore.
- Add SIGUSR1 signal handler for power button trigger script fallback
- Add 30-second auto-close timeout to pause dialog popup
- Configure PipeWire noise cancellation/echo suppression for microphone
- Bump version to 0.9.8.1
- Add sub-menu to Easy Asterisk addon with Server/Client/Both options
- Implement Baresip SIP client installation for client-only mode
- Add configuration prompts for server connection details (IP, port, extension, password)
- Create systemd user service for automatic Baresip startup
- Support TLS encryption and auto-answer mode options
- Update status display to show both server and client installation status
Added to install_kiosk_v0.9.8.sh:
- SECTION 14.5: Easy Asterisk Intercom addon functions
- get_latest_easy_asterisk_version() - Fetches latest version from GitHub API
- get_installed_easy_asterisk_version() - Checks installed version
- backup_easy_asterisk_configs() - Backs up configs before updates
- restore_easy_asterisk_configs() - Restores configs after install
- download_and_install_easy_asterisk() - Downloads and runs installer
- addon_easy_asterisk_intercom() - Main menu function with update logic
- Added option 4 "Easy Asterisk Intercom" to Addons menu
- Updated show_addon_status() to display Intercom installation status
- Integration uses stable v0.9.8 as base (v0.9.9 was broken)
README updates:
- Fixed all references from v0.9.9 to v0.9.8
- Updated menu paths (2) Addons → (4) Easy Asterisk Intercom
- Corrected version number throughout
- Updated Quick Install section
Features:
- Downloads latest easy-asterisk-v*.sh from GitHub repo
- Automatic version detection and update checking
- Configuration preservation during updates/reruns
- Safe to run multiple times
- User prompts for install/update/rerun decisions
Removed:
- install_easy_asterisk() function that installed Ubuntu Asterisk packages
- configure_easy_asterisk() function for non-existent addon
- Menu option 1 "Install Easy Asterisk" (broken implementation)
- Menu option 5 "Configure Easy Asterisk" (broken implementation)
The removed code would have:
- Installed default Ubuntu Asterisk (apt-get install asterisk)
- Created fake local config files
- Conflicted with actual Easy Asterisk from GitHub repo
Updated:
- Menu renumbered: Intercom is now option 1 (was option 2)
- Configure Intercom is now option 4 (was option 6)
- Updated README to reflect new menu numbers
- Updated error messages with correct option references
Result: Clean implementation with only the working GitHub-based
Easy Asterisk Intercom installer that properly manages versions
and preserves configurations.
Features:
- Download and install latest Easy Asterisk from GitHub repository
- Automatic version detection using GitHub API
- Smart update checking with user confirmation
- Configuration preservation during updates and reruns
- Backup and restore functionality for configs
- Safe re-run capability without breaking existing setup
Menu changes:
- Added "Install/Update Intercom (Easy Asterisk)" option
- Updated configure_intercom to work with Easy Asterisk installation
- Enhanced configuration interface with file editing support
README updates:
- Added Communication section with Easy Asterisk Intercom
- Documented installation, update, and configuration workflows
- Updated version to 0.9.9
- Added installation locations and management commands
The intercom addon integrates with outis1one/easy-asterisk repository
and follows the pattern of easy-asterisk-v*.sh version files.
- Fixed power menu showing VPN address twice by excluding VPN interfaces
(tailscale, wg, netbird, tun, wt) when detecting local IP
- Fixed site bleeding through during navigation by removing all other
BrowserViews before attaching new one
- Fixed dim navigation popup by forcing reflow and adding fade-in effect
- Added webContents.invalidate() to ensure proper view rendering after switch
These changes improve the visual clarity and reliability of the navigation
system and power menu display.
Fixed three navigation menu issues:
1. Nav button timeout - now auto-hides after 5 seconds like pause/keyboard buttons
2. Better site selection visual feedback - bolder text, white border, lift effect on hover
3. Independent column scrolling - only sites list scrolls, cheat sheet stays fixed
Navigation button changes:
- Added navButtonHideTimer and NAV_BUTTON_HIDE_DELAY (5 seconds)
- Updated showNavButton() to set auto-hide timer
- Updated hideNavButton() to clear timer
- Matches pause button behavior for consistent UX
Site button styling improvements:
- Hover: bold text, bright white border, lifts up 2px, brighter background
- Click: even darker background, pressed down effect
- Default: subtle border and shadow
- Smooth 0.3s transitions for modern feel
- Much more obvious visual feedback when hovering/selecting
Column scroll changes:
- Content overflow changed from auto to hidden
- Sites column: overflow-y auto, padding for scrollbar
- Cheat sheet column: overflow-y hidden (stays fixed)
- Columns container: max-height 70vh
- Only URL list scrolls, gestures/shortcuts remain visible
Navigation menu now jumps to selected site but allows rotation to continue.
The pause button remains the only control for stopping rotation.
Changes:
- Removed manualNavigationMode=true from navigate-to-tab handler
- Removed inactivityExtensionUntil=0 (related to manual mode)
- Updated logging to reflect that rotation continues
Users can now use the navigation menu to quickly jump to any site while
keeping rotation active, or use the pause button if they want to stop on
a specific site.
Resolved ReferenceError: showView is not defined when clicking sites in nav menu.
Changes:
- Changed showView() to attachView() - the correct function name
- Added manualNavigationMode=true to stop auto-rotation on manual navigation
- Added markActivity() to reset inactivity timer
- Added inactivityExtensionUntil=0 to clear extensions
- Added additional logging for debugging
- Added error logging for invalid view indices
This makes navigation menu behavior consistent with nextTab() and
previousTab() functions throughout the codebase.
Resolved ReferenceError: config is not defined in the 'get-config' IPC handler.
Changes:
- Modified IPC handler to read config.json directly using fs.readFileSync
- Added proper error handling with try/catch block
- Added fallback to send empty config if file doesn't exist or can't be read
- Added console logging for debugging config transmission
This fixes the JavaScript error that was displaying on screen and prevents
the navigation menu from loading sites properly.
Fixed Issues:
1. Icon rendering - Replaced emoji 🔑 with SVG key icon for better compatibility
2. JavaScript errors - Added try/catch blocks throughout navigation menu code
3. Sites not loading - Fixed IPC communication with extensive error logging
4. Menu becoming part of rotation - Ensured proper overlay with z-index and pointer-events
5. Missing auto-dismiss - Added 30-second timeout that auto-closes menu
6. Improved close button positioning - Moved to top-right with better visibility
Technical Changes:
- Changed navButton.innerHTML from emoji to SVG path for key icon
- Added NAV_MENU_TIMEOUT constant (30000ms)
- Added navMenuTimer variable for timeout management
- Enhanced createNavMenu() with console logging and error handling
- Fixed content positioning with 'position:relative'
- Added pointer-events:auto to ensure menu captures events
- Enhanced showNavMenu() with try/catch and 30-second auto-dismiss timer
- Enhanced hideNavMenu() with timer cleanup and error handling
- Enhanced toggleNavMenu() with logging
- Fixed loadSitesIntoNav() with error handling
- Enhanced config-data IPC handler with extensive logging and validation
- Changed siteBtn.innerHTML to siteBtn.textContent to prevent XSS
- Added user-select:none to prevent text selection on buttons
- Added stopPropagation to content to prevent background clicks from closing
- Fixed close button event handler with proper logging
Console Output:
- All navigation menu actions now log to console with [NAV] prefix
- Helps diagnose issues: button clicks, menu show/hide, config requests, site loading
- Error messages clearly identify failure points
This should resolve all reported issues with the navigation menu.
Bug Fixes:
- Fixed virtual console menu display (now checks both getty and X11 DontVTSwitch)
- Fixed complete_uninstall to properly remove LMS/Squeezelite services
- Fixed full_reinstall to clean all addons and settings (except saved VPN/VNC)
Named Websites Feature:
- Added 'name' field to config.json schema for all sites
- Added NAMES array throughout codebase for site name management
- Added update_site_names() function with menu option (Sites → option 3)
- Names prompted during site addition (both new installs and adding sites)
- Site listings now display as "Name" - URL when name is provided
- All management functions (add, update, delete, reorder) handle names properly
- Names fully integrated with save/load config operations
Navigation Menu Feature:
- Added enableNavButton config option (defaults to true)
- Added navigation button at top-left (purple key icon 🔑)
- Button shows on user interaction (same logic as pause/keyboard buttons)
- Navigation menu overlay with 2-column layout:
* Column 1: Clickable list of all non-hidden sites (uses names if available)
* Column 2: Touch gesture cheat sheet and keyboard shortcuts reference
- Menu accessible via key icon click
- IPC handlers added: get-config and navigate-to-tab
- Menu excludes hidden sites (duration === -1) as requested
- Optional feature configurable via Optional Buttons menu
README Updates:
- Updated all version references to 0.9.8
- Updated installation commands to use install_kiosk_0.9.8.sh
- Added comprehensive "Why Use Named Sites?" section with use cases:
* Home/Family kiosks examples
* Business kiosks examples
* Digital signage examples
* Multi-location setups examples
- Updated Multi-Site Management section to include named sites and navigation menu
- Updated Touch Controls section to reflect correct gesture (3-finger DOWN toggle)
- Updated Project Status to version 0.9.8
- Updated menu access commands throughout document
Technical Implementation:
- preload.js: Added nav button/menu variables, create/show/hide functions
- preload.js: Added IPC listener for 'nav-button-enabled'
- preload.js: Integration with user interaction handlers
- main.js: Added enableNavButton variable and config loading
- main.js: Added 'get-config' and 'navigate-to-tab' IPC handlers
- main.js: Send nav-button-enabled state on page load
- Bash script: Added site name prompts in add_new_sites() and add_new_sites_simple()
- Bash script: Added update_site_names() function for updating existing site names
- Bash script: Updated configure_optional_buttons() to include navigation button
- Bash script: Updated all save/load config operations to handle NAMES array
Script now at 9607 lines (242 lines added for navigation menu feature)
All syntax validated with bash -n
Bug fixes completed:
- Fixed virtual console menu display bug (checks both getty and X11)
- Fixed complete_uninstall to properly remove LMS/Squeezelite
- Fixed full_reinstall to clean all addons and settings
Named websites feature completed:
- Added 'name' field to config.json schema
- Added site name management throughout (add, update, delete, reorder)
- Added update_site_names() menu option
- Names display in site listings
Navigation button configuration completed:
- Added enableNavButton config option
- Added to optional buttons menu
- Integrated with save/load config
TODO: Navigation menu UI implementation in preload.js still needed
- Removed PTT (Push-to-Talk) functionality (moved to separate project)
- Fixed power menu to display both local and VPN IP addresses
- Updated Electron to v39.2.4 with enhanced rollback instructions
- Updated README dates to 2025 and version references to 0.9.7-5
- Removed stray text from README printer section
Issue: Option 10 was immediately returning to main menu instead of
performing the reinstall.
Root Cause: The menu case statement had 'return' after full_reinstall,
which caused it to exit the core_settings_menu even when the user
cancelled the reinstall (by not typing 'YES').
Fix: Removed the 'return' statement from line 8994.
Now when users cancel, they stay in the core settings menu.
When reinstall completes, the pause() at the end lets them press enter
and naturally return to the menu for additional configuration.
Note: Option 11 (complete_uninstall) correctly keeps 'return' since
uninstalling should exit the entire menu system.
Major Changes:
1. FIXED: Virtual console Ctrl+Alt+F1-F8 key combinations now work properly
- Updated configure_virtual_consoles() to modify X11 serverflags
- Enable/disable now updates both systemd getty AND X11 VT switching
- Added restart lightdm notification for changes to take effect
- Fixed initial installation to set X11 VT switching based on user choice
- Users who previously enabled consoles must re-enable for keys to work
2. IMPROVED: Simplified hidden tab gesture to single toggle
- 3-finger DOWN now toggles hidden tabs (both show AND hide)
- Removed separate 3-finger UP gesture (simpler UX)
- Updated all gesture handlers in both standard and Jitsi modes
- Updated all console.log messages and documentation
README Updates:
- Updated all version references to 0.9.7-4
- Updated Touch Gesture Quick Reference table
- Simplified gesture: 3-finger DOWN = Toggle hidden tabs
- Updated hidden tabs documentation to reflect toggle behavior
- Added important notes about virtual console fix in 0.9.7-4
- Added instructions to re-enable consoles for existing users
- Updated menu system access examples
- Updated project status to "Gesture & Console Improvements"
This release focuses on fixing the console key combo issue and improving
the hidden tab gesture for easier one-handed use.
Completed Changes:
- Updated version header to 0.9.7-4 with release notes
- FIXED: Virtual console Ctrl+Alt+F1-F8 key combinations now work
- Updated configure_virtual_consoles() to modify X11 serverflags
- Enable/disable now updates both systemd getty AND X11 VT switching
- Added restart lightdm notification for changes to take effect - Fixed initial installation to set X11 VT switching based on user choice
- FIXED: Swapped hidden tab gestures for easier use
- 3-finger DOWN now shows hidden tabs (easier than UP)
- 3-finger UP now returns to normal tabs
- Updated all gesture handlers and documentation
- Changed in both standard and Jitsi preload modes
In Progress:
- URL naming feature (requires config schema changes)
- Navigation hot corner menu (substantial new feature)
These changes fix the immediate issues and improve usability.
The navigation menu feature requires extensive additional development.
Version Changes:
- Created install_kiosk_0.9.7-3.sh with updated version number
- Updated script header with release notes for 0.9.7-3
- Updated SCRIPT_VERSION constant to "0.9.7-3"
README Updates:
- Updated all references from 0.9.7-2 to 0.9.7-3
- Updated Quick Install wget command
- Updated Menu System Access examples
- Updated Complete Uninstall examples
- Updated Project Status section
- Updated footer version
Release Notes for 0.9.7-3:
- Fixed missing complete_uninstall function (line 8704 error)
- Added virtual console configuration (Ctrl+Alt+F1-F8)
- Added emergency hotspot to initial installation
- Enhanced security options and documentation
Documentation updates:
- Update Quick Install script name to install_kiosk_0.9.7-2.sh
- Document virtual console configuration feature
- Document emergency hotspot configuration during install
- Document complete uninstall functionality
- Add comprehensive "Why Use Hidden Tabs?" section with:
- Private communication use cases
- Administrative access scenarios
- Content management applications
- Secure entertainment options
- Business use cases
- Real-world example scenarios
- Add new "Installation & Management Features" section with:
- Virtual Console Configuration details
- Emergency WiFi Hotspot documentation
- Complete Uninstall process and safety features
- Update menu system documentation
- Update system behavior security notes
- Update version and date to 0.9.7-2, December 2, 2024
- Fix line 8704: Add missing complete_uninstall() function
- Provides full system cleanup and uninstallation
- Removes kiosk user, services, and all configurations
- Offers reboot option after uninstall
- Add virtual console configuration feature
- New configure_virtual_consoles() function
- Allows enabling/disabling Ctrl+Alt+F1-F8 console access
- Added to Advanced menu (option 7)
- Integrated into initial installation with security prompt
- Add emergency hotspot to initial installation
- Prompts user at end of installation
- Can be configured immediately or deferred
- Provides automatic WiFi hotspot when internet is down
Fixed critical bug where jq command was incomplete in save_config function,
causing configuration save to fail with jq usage error when configuring
rotation sites.
Issue: The jq -n command on lines 3476-3493 had all arguments but was
missing the JSON template/filter, causing jq to output its help text
instead of creating the config file.
Fix: Added complete JSON object template to jq command with all required
fields (unit, autoswitch, enableTouch, etc.) and empty tabs array.
Version bumped from 0.9.6-2 to 0.9.6-3.
Created setup_intercom_simple.sh using talkiepi (barnard fork) instead of
talkkonnect for a more stable and lightweight intercom solution.
Advantages over talkkonnect:
- Simpler build process (no Opus patching required)
- Fewer dependencies (just Go, libopenal, libopus)
- Full CLI support for server, username, password, and channel
- More stable and less fragile
- Better suited for headless/kiosk automation
Also added INTERCOM_COMPARISON.md documenting all evaluated options
(talkkonnect, talkiepi, barnard, mumbler) with recommendations.
The uninstall function was only removing old installation paths, causing
talkkonnect to appear as "still installed" after uninstalling.
Fixed both check_talkkonnect_status() and uninstall_talkkonnect():
check_talkkonnect_status():
- Now checks for both old and new installation locations
- Old: ~/go/bin/talkkonnect and ~/talkkonnect.xml
- New: /usr/local/bin/talkkonnect and ~/.config/talkkonnect/
- Checks all locations to accurately determine if installed
uninstall_talkkonnect():
- Removes binaries from BOTH /usr/local/bin and ~/go/bin
- Removes config from BOTH ~/.config/talkkonnect and ~/talkkonnect.xml
- Properly stops and disables service before removing
- Shows progress with descriptive messages
- Optionally removes source directory ~/talkkonnect
- Only performs actions if files/directories actually exist
Now uninstall properly removes everything and status correctly shows
"Not installed" after uninstalling.
Completely replaced the install_talkkonnect_with_config() function in
setup_intercom.sh with the proven method from talkkonnect_complete_install.sh.
Key improvements:
- Uses working Opus patch for x86_64 architecture
- Installs to /usr/local/bin/talkkonnect (not ~/go/bin)
- Config in ~/.config/talkkonnect/ (proper XDG location)
- Sets <insecure>true</insecure> by default (handles self-signed certs)
- Proper XDG_RUNTIME_DIR in systemd service for PipeWire/audio
- Automatically enables service (starts on boot)
- Better progress feedback and error handling
- Uses Go 1.24.1 (latest stable)
The function now:
1. Installs all dependencies correctly
2. Builds with proper Opus library integration
3. Creates config with user's server/credentials from prompts
4. Sets up systemd service properly
5. Enables and starts service automatically
This makes setup_intercom.sh a complete, reliable one-stop solution
that uses the same proven method as talkkonnect_complete_install.sh
while maintaining the interactive prompt workflow.
Included files:
- setup_intercom.sh - Updated with new function
- setup_intercom.sh.backup - Backup of original
- setup_intercom_talkkonnect_function.txt - Reference for the new function
- update_setup_intercom.sh - Script used to perform the replacement
Created fix_talkkonnect_now.sh to resolve immediate issues:
- Sets <insecure>true</insecure> for self-signed certificates
- Warns about SuperUser account (admin-only, not for clients)
- Fixes file ownership and permissions
- Creates/updates systemd service
- Enables and starts talkkonnect service automatically
This fixes:
- Certificate errors ("wrong certificate")
- SuperUser connection issues (can't connect as SuperUser)
- Service not starting automatically
The script detects existing installations and fixes them in-place.
Minor update to setup_intercom.sh to indicate improved method.
Added fix_talkkonnect_audio.sh diagnostic script:
- Checks PipeWire/PulseAudio session availability
- Lists all audio devices (ALSA, PulseAudio/PipeWire)
- Shows current talkkonnect audio configuration
- Provides specific recommendations for audio issues
- Includes test commands for verifying audio
Updated TALKKONNECT_SETUP.md:
- Added "Common Warnings (Usually Non-Fatal)" section
- Explains "Unable to Unmute" error (cosmetic, audio usually works)
- Explains "Unable to Find Channel Name" warning
- Explains "Failed to connect PipeWire event context" error
- Added reference to audio diagnostic script
- Included quick audio test commands
These help users understand that common errors like "Unable to Unmute"
are non-fatal and audio typically works despite the warnings.
The installation script was failing with "Permission denied" when trying
to create the config directory in another user's home directory.
Fixes:
1. Added home directory existence check before creating config
2. Use sudo -u to create directory as target user when needed
3. Use sudo tee to write config file (handles all permission scenarios)
4. Use sudo for sed command to modify the created config file
5. Always set proper ownership and permissions after creation
This fixes the "mkdir: cannot create directory '/home/kiosk': Permission denied" error.
Now the script will:
- Verify target user's home directory exists (fail gracefully if not)
- Create config directory as the target user
- Write config file with sudo to avoid permission issues
- Set proper ownership (user:user) and permissions (755 dir, 644 file)
Added diagnose_talkkonnect.sh to help troubleshoot installation issues:
- Checks binary installation
- Finds all config files and shows ownership
- Analyzes systemd service configuration
- Detects audio/PipeWire sessions for each user
- Identifies permission mismatches
- Provides specific recommendations
Improved fix_talkkonnect_permissions.sh:
- Now automatically updates systemd service file if needed
- Changes service user to match target user
- Updates config path in service file
- Reloads systemd after changes
- Stops service before making changes
These tools help resolve the "permission denied" error when talkkonnect
is configured to run as one user but config is owned by another.
The installation script had a critical bug where it created the config
directory in the script runner's home directory instead of the target
user's home directory. This caused "permission denied" errors when the
systemd service tried to run as the target user.
Changes:
- Fixed CONFIG_DIR to use $TARGET_HOME instead of $HOME
- Updated config path replacements to use $TARGET_HOME
- Added ownership change after config creation when running as different user
- Created fix_talkkonnect_permissions.sh script to repair existing installations
- Added comprehensive TALKKONNECT_SETUP.md with troubleshooting guide
This fixes the "open /home/user/.config/talkkonnect/talkkonnect.xml: permission denied" error.
The script was exiting immediately after showing "Devices found: 14" because
the timeout command returns exit code 124, which caused the script to bail
due to 'set -euo pipefail'. Added '|| true' to wait and bluetoothctl scan off
commands to allow the script to continue executing and display the device list.
install_kiosk_0.9.6-2.sh:7185-7186
Fixes:
1. Addons menu: Remove duplicate menu items (1,2 appeared twice)
- Fixed numbering: Remote Access is now option 5 instead of 6
2. Bluetooth scanning improvements:
- Make controller pairable/discoverable before scanning
- Capture scan output to show DISCOVERED devices (not just paired)
- Real-time progress indicator during 30-second scan
- Separate display of discovered vs already-paired devices
- Better error handling for pairing failures
- Proper cleanup of scan process with 'scan off'
- Helpful hints when pairing fails (e.g., AuthenticationFailed)
The bluetooth scan previously only showed paired devices after scanning,
which made it appear that no devices were found even when they were
broadcasting. Now it properly captures and displays discovered devices.
Created check_talkkonnect_fix.sh to verify if the terminal initialization
fix has been applied to the talkkonnect systemd service. This script checks
for the presence of:
- Environment="TERM=dumb"
- StandardInput=null
These settings are required to prevent the "Cannot Initialize Terminal" error
when talkkonnect runs as a systemd service without a TTY.
PROBLEM:
- talkkonnect fails with "Cannot Initialize Terminal" error
- Service shows "Talkkonnect Terminated Abnormally" in logs
- Root cause: talkkonnect uses termbox-go which requires a TTY
- Systemd services don't provide TTY by default
SOLUTION:
- Added Environment="TERM=dumb" to systemd service
- Added StandardInput=null to prevent terminal initialization
- Both changes allow talkkonnect to run headless as a service
CHANGES:
- Updated install_kiosk_0.9.6-1.sh with the fix
- Created install_kiosk_0.9.6-2.sh (new version with fix)
- Added fix_talkkonnect_terminal.sh for quick patching of existing installations
Created new version 0.9.6-1 that removes all WebRTC and Jitsi intercom
functionality and adds comprehensive Bluetooth support instead.
Changes:
- REMOVED: WebRTC intercom system (all auto-discovery, mesh networking, PWA client)
- REMOVED: Jitsi web intercom (all configuration, PTT service, PIN dialog)
- ADDED: Bluetooth addon with device management
* Scan and pair bluetooth devices
* Connect/disconnect bluetooth audio devices
* Auto-reconnect to trusted devices
* Battery status display for supported devices
- UPDATED: Addon menu now shows "Bluetooth Audio & Devices" instead of Jitsi
- UPDATED: Logs menu now includes Bluetooth logs
- UPDATED: Installation overview lists bluetooth instead of Jitsi
- UPDATED: Version header documents this as "Bluetooth Edition"
Recommended: Users should use talkkonnect addon for native PTT intercom
instead of web-based solutions for better audio quality and lower latency.
File size reduced from 338KB to 322KB (removed ~400 lines of code)
All bash syntax validated successfully