diff --git a/lib/provision.sh b/lib/provision.sh index 82aeb37..d1fb3a5 100644 --- a/lib/provision.sh +++ b/lib/provision.sh @@ -94,8 +94,17 @@ provision_install_packages() { provision_create_kiosk_user() { echo "[2/10] Creating kiosk user..." + local kiosk_groups=(audio video input plugdev netdev) + # Not every Ubuntu release ships all of these - 26.04 Server has no + # netdev group - and useradd refuses to create the user at all if any + # -G group is missing. Create missing ones as plain system groups + # (no-op when they exist). + local g + for g in "${kiosk_groups[@]}"; do + sudo groupadd -f -r "$g" + done if ! id "$KIOSK_USER" &>/dev/null; then - sudo useradd -m -s /bin/bash -G audio,video,input,plugdev,netdev "$KIOSK_USER" + sudo useradd -m -s /bin/bash -G "$(IFS=,; echo "${kiosk_groups[*]}")" "$KIOSK_USER" echo "$KIOSK_USER:kiosk" | sudo chpasswd log_success "Kiosk user created (default password: kiosk - change it)" else @@ -129,7 +138,12 @@ provision_install_app() { echo "[4/10] Installing kiosk app..." # *.mjs: pdf-render.mjs (pdf.js only ships as a JavaScript module). sudo cp "$KIOSK_APP_SRC"/*.js "$KIOSK_APP_SRC"/*.mjs "$KIOSK_APP_SRC"/*.html "$KIOSK_APP_SRC/package.json" "$KIOSK_APP_SRC/start.sh" "$KIOSK_DIR/" - sudo chown "$KIOSK_USER:$KIOSK_USER" "$KIOSK_DIR"/*.js "$KIOSK_DIR"/*.mjs "$KIOSK_DIR"/*.html "$KIOSK_DIR/package.json" "$KIOSK_DIR/start.sh" + # Not `chown "$KIOSK_DIR"/*.js ...`: that glob is expanded by *this* + # (admin) shell before sudo runs, and the kiosk user's home is private + # (mode 750 on 26.04), so it can't be listed - the pattern stays literal + # and chown fails. find runs as root. Top level only: node_modules is + # already the kiosk user's, and chrome-sandbox inside it must stay root's. + sudo find "$KIOSK_DIR" -maxdepth 1 -type f -exec chown "$KIOSK_USER:$KIOSK_USER" {} + sudo chmod +x "$KIOSK_DIR/start.sh" if offline_mode_active; then diff --git a/menus/addon_asterisk_intercom.sh b/menus/addon_asterisk_intercom.sh index c70a418..ff88e6b 100644 --- a/menus/addon_asterisk_intercom.sh +++ b/menus/addon_asterisk_intercom.sh @@ -62,7 +62,9 @@ baresip_installed_version() { # `apt install baresip` with no configured extension isn't something # this menu should call done. baresip_is_installed() { - command -v baresip &>/dev/null && [[ -f "$BARESIP_CONFIG_DIR/accounts" ]] + # sudo: it's under the kiosk user's home, which the admin running this + # can't look inside (mode 750) - a plain [[ -f ]] is always false there. + command -v baresip &>/dev/null && sudo test -f "$BARESIP_CONFIG_DIR/accounts" } baresip_is_running() { @@ -78,9 +80,9 @@ addon_asterisk_intercom_status() { else echo "Asterisk Intercom: Installed (v${ver:-unknown}) - Not running" fi - if [[ -f "$BARESIP_CONFIG_DIR/accounts" ]]; then + if sudo test -f "$BARESIP_CONFIG_DIR/accounts"; then local account - account=$(head -1 "$BARESIP_CONFIG_DIR/accounts" 2>/dev/null) + account=$(sudo head -1 "$BARESIP_CONFIG_DIR/accounts" 2>/dev/null) local extension="${account#/dev/null) + [[ "$type" == "wlan" ]] || continue + if [[ "$(cat "$r/soft" 2>/dev/null)" == "1" ]]; then + echo "WiFi is switched off in software (rfkill) - switching it on..." + echo 0 | sudo tee "$r/soft" >/dev/null || true + fi + if [[ "$(cat "$r/hard" 2>/dev/null)" == "1" ]]; then + log_warning "WiFi is switched off by a hardware switch or the BIOS/UEFI - turn it on there" + fi + done +} + +# YAML single-quoted scalar: the only special character inside is the +# quote itself, doubled. Safe for any SSID/password (", \, #, : ...) - +# double quotes broke on a password containing " or \. +wifi_yaml_quote() { + local v="$1" + printf "'%s'" "${v//\'/\'\'}" +} + +# IFACE's first IPv4 address (without the /prefix), or nothing. +wifi_iface_ip() { + ip -4 -o addr show dev "$1" 2>/dev/null \ + | awk '{for (i = 1; i < NF; i++) if ($i == "inet") { sub("/.*", "", $(i+1)); print $(i+1); exit }}' +} + +# True if IFACE has an IPv4 address. +wifi_iface_has_ip() { + ip -4 addr show dev "$1" 2>/dev/null | grep -q "inet " +} + +wifi_configure() { echo echo " ═══ WIFI CONFIGURATION ═══" echo @@ -55,11 +131,11 @@ wifi_menu() { fi local wifi_iface - wifi_iface=$(ls /sys/class/net 2>/dev/null | grep -E "^wl" | head -1) - - if [[ -z "$wifi_iface" ]]; then + if ! wifi_iface=$(wifi_detect_iface); then log_warning "No WiFi hardware detected" - echo "If you have a USB WiFi adapter, ensure it's plugged in." + echo "Network interfaces found: $(ls /sys/class/net 2>/dev/null | tr '\n' ' ')" + echo "If you have a USB WiFi adapter, make sure it's plugged in. A built-in" + echo "adapter with no driver shows up in 'lspci -k' / 'lsusb' but not above." return 1 fi @@ -74,9 +150,11 @@ wifi_menu() { ask_yes_no "Configure WiFi?" "n" || return 0 + wifi_unblock echo "Bringing up interface..." - if ! sudo ip link set "$wifi_iface" up 2>/dev/null; then - log_error "Failed to bring up interface" + local link_err + if ! link_err=$(sudo ip link set "$wifi_iface" up 2>&1); then + log_error "Failed to bring up $wifi_iface: ${link_err:-unknown error}" return 1 fi sleep 3 @@ -161,15 +239,9 @@ apply_wifi_config() { local ssid="$2" local password="$3" - # `|| true`: under set -e + pipefail (this whole tool runs under both), - # `ls` matching nothing exits non-zero even with stderr silenced, which - # would abort this function outright instead of falling through to the - # default filename below. Same class of bug as the run_menu fix in - # lib/menu.sh - masked here in practice because cloud-init almost - # always leaves a *.yaml file behind, but not guaranteed. - local netplan_file - netplan_file=$(ls "$NETPLAN_DIR"/*.yaml 2>/dev/null | head -1) || true - [[ -z "$netplan_file" ]] && netplan_file="$NETPLAN_DIR/50-cloud-init.yaml" + # Our own file, merged by netplan with the others (wired config etc). + local netplan_file="$WIFI_NETPLAN_FILE" + sudo mkdir -p "$NETPLAN_DIR" local backup="" if [[ -f "$netplan_file" ]]; then @@ -190,29 +262,31 @@ network: dhcp6: false optional: true access-points: - "$ssid": - password: "$password" + $(wifi_yaml_quote "$ssid"): + password: $(wifi_yaml_quote "$password") EOF - if [[ -n "${SSH_CONNECTION:-}" ]] && [[ -n "$backup" ]]; then + if [[ -n "${SSH_CONNECTION:-}" ]]; then + # Reverts if the WiFi interface still has no address after 60s - + # not "can't ping 8.8.8.8", which on a network without internet + # would undo a perfectly good connection every time. No backup + # means there was no WiFi file before: revert = remove ours. local watchdog watchdog=$(mktemp --suffix=.sh) cat > "$watchdog" <<'WATCHEOF' #!/bin/bash +# $1 = backup ("" if none), $2 = netplan file, $3 = WiFi interface sleep 60 -if [[ -f "$1" && -f "$2" ]]; then - ip=$(hostname -I | awk '{print $1}') - if [[ -z "$ip" ]] || ! ping -c 2 8.8.8.8 >/dev/null 2>&1; then - cp "$1" "$2" - netplan apply 2>/dev/null - echo "WiFi config reverted - connection failed" | wall - fi +if ! ip -4 addr show dev "$3" 2>/dev/null | grep -q "inet "; then + if [[ -n "$1" && -f "$1" ]]; then cp "$1" "$2"; else rm -f "$2"; fi + netplan apply 2>/dev/null + echo "WiFi config reverted - $3 got no address" | wall fi rm -f "$0" WATCHEOF chmod +x "$watchdog" - nohup sudo bash "$watchdog" "$backup" "$netplan_file" >/dev/null 2>&1 & - echo "Watchdog started - will revert in 60s if the connection fails" + nohup sudo bash "$watchdog" "$backup" "$netplan_file" "$wifi_iface" >/dev/null 2>&1 & + echo "Watchdog started - will revert in 60s if $wifi_iface gets no address" fi sudo cp "$temp_plan" "$netplan_file" @@ -224,22 +298,30 @@ WATCHEOF netplan_log=$(mktemp) if sudo netplan apply > "$netplan_log" 2>&1; then cat "$netplan_log" - sleep 10 - local new_ip - new_ip=$(get_ip_address) - if [[ -n "$new_ip" && "$new_ip" != "No IP" ]]; then - log_success "Connected: $ssid ($new_ip)" + echo "Waiting for $wifi_iface to connect (up to 30 seconds)..." + local _ + for _ in {1..30}; do + wifi_iface_has_ip "$wifi_iface" && break + sleep 1 + done + if wifi_iface_has_ip "$wifi_iface"; then + log_success "Connected to $ssid: $(wifi_iface_ip "$wifi_iface")" [[ -n "${SSH_CONNECTION:-}" ]] && echo "Connection successful - watchdog will not revert" else - log_warning "Config applied but no IP yet" - echo "Check: sudo journalctl -u systemd-networkd -f" + log_warning "Config applied but $wifi_iface has no address yet" + echo "Wrong password or network name is the usual cause. Details:" + echo " sudo journalctl -b -u 'netplan-wpa-*' -u systemd-networkd -n 30 --no-pager" fi else log_error "netplan apply failed" echo "Error log:" cat "$netplan_log" - if [[ -n "$backup" ]] && ask_yes_no "Restore backup?" "y"; then - sudo cp "$backup" "$netplan_file" + if ask_yes_no "Undo this WiFi change?" "y"; then + if [[ -n "$backup" ]]; then + sudo cp "$backup" "$netplan_file" + else + sudo rm -f "$netplan_file" + fi # Last resort after everything else failed: report, don't crash # the session if even the restore-and-reapply doesn't work. if sudo netplan apply; then