diff --git a/Readme.md b/Readme.md index 6b5d6bb..7a06719 100644 --- a/Readme.md +++ b/Readme.md @@ -69,7 +69,6 @@ boot of the installed system it runs `./install.sh` automatically on the console. ```bash -sudo apt install xorriso curl git openssl git clone https://github.com/outis1one/ubuntu-based-kiosk.git cd ubuntu-based-kiosk iso/build-iso.sh # downloads + verifies the latest 26.04.x server ISO @@ -77,6 +76,10 @@ iso/build-iso.sh # downloads + verifies the latest 26.04.x server ISO sudo dd if=iso/build/ubuntu-26.04.*-kiosk-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync ``` +The script checks for its build tools (`xorriso`, `curl`, `git`, `openssl`) +and offers to install any that are missing with `sudo apt install`. Pass +`--install-deps` to install them without asking. + By default the installer only asks for **network** (WiFi works here), **disk**, and your **admin username/password** — language, keyboard and the SSH server are answered for you. On first boot, `install.sh` runs @@ -100,8 +103,7 @@ Useful options (`iso/build-iso.sh --help` for all): ### Offline installs (`--offline`) ```bash -sudo apt install apt-utils python3 gnupg ubuntu-keyring # once, on top of the tools above -iso/build-iso.sh --offline +iso/build-iso.sh --offline # also offers to install apt-utils, python3, gnupg, ubuntu-keyring # -> iso/build/ubuntu-26.04.x-kiosk-offline-amd64.iso (~3.2 GB) ``` @@ -114,14 +116,10 @@ This adds a ~420 MB bundle to the ISO: On first boot it still installs online when there's internet. With none, it waits 90 seconds and then installs entirely from the bundle. The bundle -stays on the kiosk at `/opt/kiosk-offline`, so CUPS can be added later -with no internet too: - -```bash -sudo kiosk-offline-apt on # apt uses the bundle only -./install.sh # Addons -> CUPS Printing -sudo kiosk-offline-apt off # back to normal updates -``` +stays on the kiosk at `/opt/kiosk-offline`, so **Addons → CUPS Printing** +(in `./install.sh` or the Web UI) works with no internet too: when it can't +reach the Ubuntu archive, it installs from the bundle by itself and then +switches apt back to normal. Notes: - Build it on an **amd64** Ubuntu/Debian machine, or let the GitHub @@ -195,6 +193,10 @@ The kiosk machine still needs a working internet connection (ethernet, or WiFi c - **Home URL** - Auto-return after inactivity on manual or hidden sites - **Pause functionality** - Temporarily pause rotation (configurable per-site) - **Navigation menu** - Quick access to all sites via key icon (top-left hot corner) +- **Local files** - Enter an absolute path (e.g. `/home/kiosk/docs/menu.pdf`) + instead of a web address and it's shown from the kiosk's own disk: + HTML pages, PDFs (built-in viewer), and images. Works offline. The file + must be readable by the `kiosk` user. ### Touch Controls - **2-finger horizontal swipe** - Switch between sites diff --git a/iso/build-iso.sh b/iso/build-iso.sh index 7015abc..822815f 100755 --- a/iso/build-iso.sh +++ b/iso/build-iso.sh @@ -24,7 +24,9 @@ # (menu title, plus the `autoinstall` kernel argument with --no-confirm); # boot records (BIOS + UEFI) are replayed from the original as-is. # -# Requirements (on the build machine, any recent Ubuntu/Debian): +# Requirements (on the build machine, any recent Ubuntu/Debian): the +# script checks for its build tools and offers to install whatever's +# missing with apt (or pass --install-deps to just do it). By hand: # sudo apt install xorriso curl git openssl # plus, for --offline (amd64 build machine only): # sudo apt install apt-utils python3 gnupg ubuntu-keyring @@ -33,6 +35,7 @@ # iso/build-iso.sh [options] # # --iso PATH Use this Ubuntu Server ISO instead of downloading. +# --install-deps Install missing build tools with apt without asking. # --release VER Ubuntu release to download (default: 26.04 - picks # the newest 26.04.x live-server-amd64 point release). # --output PATH Output ISO (default: iso/build/ubuntu--kiosk-amd64.iso) @@ -74,8 +77,8 @@ # With internet it still installs online as usual; # offline it waits 90s, then uses the bundle. The # bundle stays on the kiosk at /opt/kiosk-offline -# (see iso/firstboot/kiosk-offline-apt for using it -# later, e.g. adding CUPS while offline). +# - Addons -> CUPS Printing then installs from it too +# whenever the kiosk is offline. # # The repo copied onto the ISO is a clone of this checkout's current HEAD # (committed changes only - uncommitted edits are NOT included). @@ -103,6 +106,7 @@ AUTO_NETWORK=0 NO_CONFIRM=0 UNATTENDED=0 OFFLINE=0 +INSTALL_DEPS=0 REPO_URL="" # Usernames the Ubuntu installer rejects (system users/groups) - copied @@ -149,6 +153,7 @@ while [[ $# -gt 0 ]]; do --fully-automatic) AUTO_STORAGE=1; AUTO_NETWORK=1; NO_CONFIRM=1; UNATTENDED=1; shift ;; --repo-url) REPO_URL="$2"; shift 2 ;; --offline) OFFLINE=1; shift ;; + --install-deps) INSTALL_DEPS=1; shift ;; -h|--help) usage 0 ;; *) echo "Unknown option: $1" >&2; usage 1 ;; esac @@ -158,9 +163,34 @@ done # Validate ################################################################################ -for cmd in xorriso curl git openssl sha256sum; do - command -v "$cmd" &>/dev/null || die "'$cmd' not found - install it: sudo apt install xorriso curl git openssl" +# Build tools: command -> the apt package that provides it. +declare -A DEP_PKG=([xorriso]=xorriso [curl]=curl [git]=git [openssl]=openssl [sha256sum]=coreutils) +if (( OFFLINE )); then + DEP_PKG+=([apt-ftparchive]=apt-utils [python3]=python3 [gpg]=gnupg [dpkg-deb]=dpkg) +fi +missing_pkgs=() +for cmd in "${!DEP_PKG[@]}"; do + command -v "$cmd" &>/dev/null || missing_pkgs+=("${DEP_PKG[$cmd]}") done +if (( OFFLINE )) && [[ ! -r /usr/share/keyrings/ubuntu-archive-keyring.gpg ]]; then + missing_pkgs+=(ubuntu-keyring) +fi +if (( ${#missing_pkgs[@]} )); then + mapfile -t missing_pkgs < <(printf '%s\n' "${missing_pkgs[@]}" | sort -u) + echo "Missing build tools: ${missing_pkgs[*]}" + command -v apt-get &>/dev/null \ + || die "install them with your package manager and re-run (this script expects Ubuntu/Debian)" + if (( ! INSTALL_DEPS )); then + [[ -t 0 ]] || die "install them first: sudo apt install ${missing_pkgs[*]} (or re-run with --install-deps)" + read -r -p "Install them now with 'sudo apt install'? [Y/n] " reply + [[ "${reply,,}" =~ ^(|y|yes)$ ]] || die "install them first: sudo apt install ${missing_pkgs[*]}" + fi + sudo apt-get update -q + sudo apt-get install -y "${missing_pkgs[@]}" || die "couldn't install: ${missing_pkgs[*]}" +fi +if (( OFFLINE )) && [[ "$(dpkg --print-architecture 2>/dev/null)" != "amd64" ]]; then + die "--offline must be built on an amd64 (64-bit Intel/AMD) machine - it runs the bundled Node.js to build the kiosk's modules" +fi if [[ -n "$ADMIN_USER" ]]; then [[ "$ADMIN_USER" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || die "invalid username: $ADMIN_USER" diff --git a/iso/firstboot/kiosk-offline-apt b/iso/firstboot/kiosk-offline-apt index ef67786..98d2b2e 100755 --- a/iso/firstboot/kiosk-offline-apt +++ b/iso/firstboot/kiosk-offline-apt @@ -8,10 +8,10 @@ # # Installed by the custom ISO (iso/build-iso.sh); the bundle itself is # only there on ISOs built with --offline. kiosk-firstboot turns this on -# by itself when first boot finds no internet, and back off afterwards. -# By hand it's for installing things later with no internet - e.g. the -# CUPS addon: `sudo kiosk-offline-apt on`, ./install.sh -> Addons -> CUPS, -# then `sudo kiosk-offline-apt off`. While it's on, install.sh also +# by itself when first boot finds no internet, and back off afterwards; +# so does the CUPS addon install (lib/config.sh's +# run_with_offline_fallback). By hand it's for installing anything else +# from the bundle with no internet. While it's on, install.sh also # unpacks the bundle's prebuilt node_modules instead of running npm # (lib/config.sh's offline_mode_active checks for the file this writes). # diff --git a/lib/config.sh b/lib/config.sh index be562cc..01f196f 100644 --- a/lib/config.sh +++ b/lib/config.sh @@ -104,6 +104,36 @@ offline_install_node_modules() { sudo -u "$KIOSK_USER" tar -xzf "$tarball" -C "$dest" } +# Quick reachability check for the package mirrors install.sh needs. +have_internet() { + curl -fsS --max-time 8 -o /dev/null http://archive.ubuntu.com/ubuntu/ 2>/dev/null \ + || curl -fsS --max-time 8 -o /dev/null https://deb.nodesource.com/ 2>/dev/null +} + +# run_with_offline_fallback CMD [ARGS...] +# Runs CMD normally - but on a kiosk installed from an --offline ISO with +# no internet right now, first points apt at the bundle on this disk +# (kiosk-offline-apt on) and switches it back afterwards, whatever CMD +# returns. Lets apt-based installs (e.g. the CUPS addon) work from the +# normal menus and the Web UI with no extra steps. Everywhere else +# (manual installs, or online) it just runs CMD. +run_with_offline_fallback() { + local restore=0 rc=0 + if ! offline_mode_active && [[ -f "$KIOSK_OFFLINE_DIR/apt/Packages" ]] && ! have_internet; then + log_info "No internet - installing from the offline bundle on this disk ($(sed -n 's/^Built: *//p' "$KIOSK_OFFLINE_DIR/bundle-info.txt" 2>/dev/null))" + if sudo kiosk-offline-apt on >/dev/null; then + restore=1 + else + log_warning "Couldn't switch apt to the offline bundle - trying normally" + fi + fi + "$@" || rc=$? + if (( restore )); then + sudo kiosk-offline-apt off >/dev/null || true + fi + return "$rc" +} + kiosk_user_exists() { id "$KIOSK_USER" &>/dev/null } diff --git a/menus/addon_cups.sh b/menus/addon_cups.sh index 78727c7..5a2f828 100644 --- a/menus/addon_cups.sh +++ b/menus/addon_cups.sh @@ -22,7 +22,11 @@ CUPS_APT_PACKAGES=( ) cups_is_installed() { - dpkg -l 2>/dev/null | grep -q "^ii\s\+cups\s" + # Not `dpkg -l | grep -q`: grep -q exits at the first match, dpkg + # takes SIGPIPE writing the rest of its long list, and under + # install.sh's `set -o pipefail` that made this report "not + # installed" for an installed CUPS. + [[ "$(dpkg-query -W -f='${db:Status-Abbrev}' cups 2>/dev/null)" == "ii "* ]] } cups_is_active() { @@ -60,11 +64,7 @@ addon_cups_menu() { # Actions ################################################################################ -action_install_cups() { - echo - ask_yes_no "Install CUPS printing?" "n" || { echo "Cancelled"; return; } - - echo "Installing CUPS from scratch..." +cups_apt_install() { if ! sudo apt update; then log_error "apt update failed - check network/package sources and try again" return 1 @@ -73,6 +73,14 @@ action_install_cups() { log_error "CUPS package installation failed" return 1 fi +} + +action_install_cups() { + echo + ask_yes_no "Install CUPS printing?" "n" || { echo "Cancelled"; return; } + + echo "Installing CUPS from scratch..." + run_with_offline_fallback cups_apt_install || return 1 sudo systemctl enable cups 2>/dev/null || true sudo systemctl start cups 2>/dev/null || true diff --git a/menus/sites.sh b/menus/sites.sh index 1affe4b..60454eb 100644 --- a/menus/sites.sh +++ b/menus/sites.sh @@ -18,11 +18,23 @@ SITE_EDIT_IDX="" ################################################################################ # Normalize whatever the user typed into a URL, same rules the old -# installer used: bare host -> https://, bare IP -> http://. +# installer used: bare host -> https://, bare IP -> http://. Plus local +# files on the kiosk itself (pages, PDFs, images): an absolute path -> +# file://, percent-encoded the same way webui/server.js's parseUrl does. sites_parse_url() { local raw="$1" - if [[ "$raw" =~ ^https?:// ]]; then + if [[ "$raw" =~ ^(https?|file):// ]]; then echo "$raw" + elif [[ "$raw" == /* ]]; then + local LC_ALL=C out="" c i + for (( i = 0; i < ${#raw}; i++ )); do + c="${raw:i:1}" + case "$c" in + [A-Za-z0-9._~/!\'\(\)*-]) out+="$c" ;; + *) out+="$(printf '%%%02X' "'$c")" ;; + esac + done + echo "file://$out" elif [[ "$raw" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then echo "http://${raw}" else diff --git a/webui/public/index.html b/webui/public/index.html index 2b3d183..48d6670 100644 --- a/webui/public/index.html +++ b/webui/public/index.html @@ -184,7 +184,7 @@