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
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Ubuntu Based Kiosk
|
||||
|
||||
**Current Version:** 2.10.0 (check script header for latest version)
|
||||
**Current Version:** 2.11.0 (check script header for latest version)
|
||||
**Built with Claude Sonnet 4.6 AI assistance**
|
||||
**License:** GPL v3 - Keep derivatives open source
|
||||
**Repository:** https://github.com/outis1one/ubuntu-based-kiosk/
|
||||
@@ -1239,6 +1239,18 @@ terminal menu and the web UI, so they can't drift apart).
|
||||
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.
|
||||
- `install.sh` — entry point for the modular tool, now grouped **Core
|
||||
Settings / Addons / Advanced** like the legacy menu. Run it against an
|
||||
*already-installed* kiosk:
|
||||
@@ -1253,10 +1265,11 @@ most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000
|
||||
lines and still contains its own unremoved, unmodified copies of every
|
||||
menu above, including the legacy three-option (Client/Server/Full)
|
||||
Easy Asterisk Intercom — the modular version only replaces the Client
|
||||
option, by design (plus Upgrade, Reinstall, Uninstall, and the other 8
|
||||
Advanced items — none of that has moved yet). Both copies coexist
|
||||
deliberately: the old ones stay until enough of Core Settings/Addons/
|
||||
Advanced is migrated to retire them in one pass, rather than leaving
|
||||
option, by design (plus Upgrade, Reinstall, Complete Uninstall, Export/
|
||||
Import Settings, and Fix Squeezelite Audio — none of that has moved
|
||||
yet). 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.
|
||||
@@ -1286,9 +1299,14 @@ full migration pass.
|
||||
|
||||
## Project Status & Future Plans
|
||||
|
||||
**Current Version:** 2.10.0
|
||||
**Current Version:** 2.11.0
|
||||
|
||||
**Recent Updates (v2.10.0):**
|
||||
**Recent Updates (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.
|
||||
|
||||
+15
-3
@@ -22,7 +22,11 @@
|
||||
# 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/
|
||||
# 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):
|
||||
# git clone <repo>
|
||||
@@ -64,6 +68,14 @@ source "$SCRIPT_DIR/menus/addon_remote_access.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"
|
||||
|
||||
################################################################################
|
||||
# Preflight
|
||||
@@ -136,8 +148,8 @@ addons_menu() {
|
||||
}
|
||||
|
||||
advanced_menu_builder() {
|
||||
MENU_LABELS=("Diagnostics")
|
||||
MENU_HANDLERS=(diagnostics_menu)
|
||||
MENU_LABELS=("Diagnostics" "Electron Maintenance" "Factory Reset" "Virtual Consoles" "Emergency Hotspot")
|
||||
MENU_HANDLERS=(diagnostics_menu advanced_electron_menu advanced_factory_reset_menu advanced_virtual_consoles_menu advanced_emergency_hotspot_menu)
|
||||
}
|
||||
|
||||
advanced_menu() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
#!/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; }
|
||||
|
||||
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"
|
||||
|
||||
pause
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+33
-2
@@ -1,8 +1,39 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
### Ubuntu Based Kiosk v2.10.0 ###
|
||||
### Ubuntu Based Kiosk v2.11.0 ###
|
||||
################################################################################
|
||||
#
|
||||
# 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).
|
||||
@@ -379,7 +410,7 @@ set -euo pipefail
|
||||
### SECTION 1: CONSTANTS & GLOBALS
|
||||
################################################################################
|
||||
|
||||
SCRIPT_VERSION="2.10.0"
|
||||
SCRIPT_VERSION="2.11.0"
|
||||
|
||||
# Resolve the real path to this script file.
|
||||
# When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,
|
||||
|
||||
Reference in New Issue
Block a user