ce6acec299
Installer rewritten around a preflight: distro, package manager, display manager, window manager, terminal, tmux, cargo, git, screen locker, touch device, device permissions and free disk are all checked BEFORE anything is installed, and the total cost is printed once for a single confirmation. Prompts read /dev/tty so they still work when the script is piped from curl, and fall back to defaults with a notice when there is no terminal at all. Several "[ test ] && action" statements were set -e landmines: under set -e an AND-OR list that ends up false aborts the script, so a box with no lightdm, no i3 or nothing to install would have exited silently partway through detection -- which is exactly the fresh-Debian case the installer exists for. Rewritten as if-statements and verified against a stripped PATH with no tmux, cargo, git or package manager present. Also fixed cargo detection reporting blank instead of NOT INSTALLED: the status of `cargo --version | cut` is cut's, and cut succeeds on empty input, so the fallback never fired. Device access now defaults to a udev rule matching touchscreens only, rather than the input group, which grants access to every input device including the keyboard and needs a full logout. README rewritten for someone who has not seen the project: what the photo shows, the hardware, install, then a config built up step by step, each step with the YAML and the resulting map. Every example is verified verbatim against the binary, and every relative link resolves. The mechanism and the reasoning move to notes/: DESIGN.md, HARDWARE-NOTES.md, V1-BASH.md, TODO.md. cad/README.md was a verbatim copy of the one inside geeekpi_rack_adapter_release_v1/, so every path in it -- including the screenshot -- was broken from where it sits. Corrected to its own level, and it now states once that the 9-inch screen, the 10-inch mini-rack mount and the 19-inch rack are three different measurements. The v1 shell implementation is removed; it stays recoverable at tag v1.2 and notes/V1-BASH.md carries the setting-by-setting migration table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
440 lines
15 KiB
Bash
Executable File
440 lines
15 KiB
Bash
Executable File
#!/bin/sh
|
|
# socktop-swipe installer.
|
|
#
|
|
# curl -fsSL https://gt.wittyoneoff.com/jason/socktop-swipe/raw/branch/main/install.sh | sh
|
|
#
|
|
# or, from a checkout:
|
|
#
|
|
# ./install.sh
|
|
#
|
|
# Everything is checked BEFORE anything is installed, and the full cost is
|
|
# printed once. Nothing touches the system before you answer the summary.
|
|
#
|
|
# --yes accept every default without asking
|
|
# --uninstall hand over to uninstall.sh
|
|
# --prefix DIR install to DIR/bin instead of /usr/local/bin
|
|
set -eu
|
|
|
|
REPO=https://gt.wittyoneoff.com/jason/socktop-swipe
|
|
PREFIX=/usr/local
|
|
ASSUME_YES=no
|
|
SRC=""
|
|
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--yes | -y) ASSUME_YES=yes ;;
|
|
--prefix=*) PREFIX=${a#--prefix=} ;;
|
|
--uninstall) exec sh -c "$(dirname "$0")/uninstall.sh" ;;
|
|
-h | --help) sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "unknown option: $a" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
BIN="$PREFIX/bin"
|
|
CONF_DIR="$HOME/.config/socktop-swipe"
|
|
CONF="$CONF_DIR/config.yaml"
|
|
UDEV=/etc/udev/rules.d/70-socktop-swipe.rules
|
|
XBLANK=/etc/X11/xorg.conf.d/10-no-blanking-socktop-swipe.conf
|
|
LIGHTDM=/etc/lightdm/lightdm.conf.d/50-autologin-socktop-swipe.conf
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Asking
|
|
# --------------------------------------------------------------------------
|
|
# Piping into sh leaves stdin holding the SCRIPT, not a keyboard, so prompts
|
|
# must read the terminal directly. With no terminal at all we take defaults and
|
|
# say so rather than silently guessing.
|
|
if [ -r /dev/tty ] && [ -t 1 ]; then HAVE_TTY=yes; else HAVE_TTY=no; fi
|
|
|
|
say() { printf '%s\n' "$*"; }
|
|
step() { printf '\n==> %s\n' "$*"; }
|
|
warn() { printf ' !! %s\n' "$*" >&2; }
|
|
|
|
# ask "question" default(y|n) -> 0 for yes
|
|
ask() {
|
|
_q=$1
|
|
_d=$2
|
|
if [ "$ASSUME_YES" = yes ] || [ "$HAVE_TTY" = no ]; then
|
|
[ "$_d" = y ] && return 0 || return 1
|
|
fi
|
|
[ "$_d" = y ] && _hint="[Y/n]" || _hint="[y/N]"
|
|
while :; do
|
|
printf ' %s %s ' "$_q" "$_hint" >/dev/tty
|
|
read -r _a </dev/tty || _a=""
|
|
[ -z "$_a" ] && _a=$_d
|
|
case "$_a" in
|
|
[Yy] | [Yy][Ee][Ss]) return 0 ;;
|
|
[Nn] | [Nn][Oo]) return 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# askval "question" "default" -> echoes the answer
|
|
askval() {
|
|
if [ "$ASSUME_YES" = yes ] || [ "$HAVE_TTY" = no ]; then
|
|
printf '%s' "$2"
|
|
return
|
|
fi
|
|
printf ' %s [%s] ' "$1" "$2" >/dev/tty
|
|
read -r _a </dev/tty || _a=""
|
|
[ -z "$_a" ] && _a=$2
|
|
printf '%s' "$_a"
|
|
}
|
|
|
|
have() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Preflight
|
|
# --------------------------------------------------------------------------
|
|
say "socktop-swipe installer"
|
|
say "======================="
|
|
|
|
OS_NAME=$(. /etc/os-release 2>/dev/null && printf '%s' "${PRETTY_NAME:-unknown}" || true)
|
|
[ -n "$OS_NAME" ] || OS_NAME=unknown
|
|
ARCH=$(uname -m)
|
|
|
|
PKG=""
|
|
PKG_INSTALL=""
|
|
for p in apt-get dnf pacman zypper apk; do
|
|
if have $p; then
|
|
PKG=$p
|
|
break
|
|
fi
|
|
done
|
|
case "$PKG" in
|
|
apt-get) PKG_INSTALL="sudo apt-get install -y" ;;
|
|
dnf) PKG_INSTALL="sudo dnf install -y" ;;
|
|
pacman) PKG_INSTALL="sudo pacman -S --needed --noconfirm" ;;
|
|
zypper) PKG_INSTALL="sudo zypper install -y" ;;
|
|
apk) PKG_INSTALL="sudo apk add" ;;
|
|
esac
|
|
|
|
# Display manager and window manager, for the autostart and autologin steps.
|
|
# NB: written as `if`, not `[ x ] && y`. Under `set -e` an AND-OR list that
|
|
# ends up false aborts the script, so a box with no lightdm would exit here.
|
|
DM=none
|
|
if [ -d /etc/lightdm ]; then DM=lightdm
|
|
elif [ -d /etc/gdm3 ] || [ -d /etc/gdm ]; then DM=gdm
|
|
elif [ -f /etc/sddm.conf ] || [ -d /etc/sddm.conf.d ]; then DM=sddm
|
|
fi
|
|
|
|
WM=none
|
|
if have i3; then WM=i3; fi
|
|
|
|
TERMINAL=""
|
|
for t in alacritty foot kitty xterm; do
|
|
if have $t; then
|
|
TERMINAL=$t
|
|
break
|
|
fi
|
|
done
|
|
|
|
LOCKER=""
|
|
for l in xss-lock light-locker xscreensaver gnome-screensaver; do
|
|
if have $l; then
|
|
LOCKER="$LOCKER $l"
|
|
fi
|
|
done
|
|
|
|
# Space for the toolchain and the build. Both land under $HOME unless
|
|
# CARGO_HOME says otherwise, which is exactly the 8GB-of-eMMC case.
|
|
avail_mb() { df -Pm "$1" 2>/dev/null | awk 'NR==2 {print $4}'; }
|
|
HOME_FREE=$(avail_mb "$HOME")
|
|
PREFIX_FREE=$(avail_mb "$(dirname "$PREFIX")")
|
|
|
|
step "Found"
|
|
say " system $OS_NAME ($ARCH)"
|
|
say " packages ${PKG:-none detected}"
|
|
say " display mgr $DM"
|
|
say " window mgr $WM"
|
|
say " terminal ${TERMINAL:-none found}"
|
|
say " tmux $(tmux -V 2>/dev/null || echo 'NOT INSTALLED')"
|
|
# Not `cargo --version | cut ... || echo`: the pipeline's status is cut's, and
|
|
# cut succeeds on empty input, so the fallback would never fire.
|
|
if have cargo; then
|
|
say " cargo $(cargo --version 2>/dev/null | cut -d' ' -f1-2)"
|
|
else
|
|
say " cargo NOT INSTALLED"
|
|
fi
|
|
say " git $(git --version 2>/dev/null || echo 'NOT INSTALLED')"
|
|
say " free space ${HOME_FREE:-?} MB in \$HOME, ${PREFIX_FREE:-?} MB on $PREFIX"
|
|
if [ -n "$LOCKER" ]; then say " screen locker $LOCKER"; fi
|
|
|
|
# What we are about to need.
|
|
NEED_PKGS=""
|
|
NEED_RUSTUP=no
|
|
COST_MB=10
|
|
|
|
if ! have tmux; then NEED_PKGS="$NEED_PKGS tmux"; fi
|
|
if ! have git; then NEED_PKGS="$NEED_PKGS git"; fi
|
|
if ! have cargo; then
|
|
NEED_RUSTUP=yes
|
|
COST_MB=$((COST_MB + 1200))
|
|
fi
|
|
# The build itself: a debug-free release build of this crate and its deps.
|
|
COST_MB=$((COST_MB + 600))
|
|
|
|
if [ -z "$TERMINAL" ]; then
|
|
warn "no terminal emulator found. socktop-swipe can attach in an existing"
|
|
warn "terminal, but unattended autostart needs one. alacritty is the tested choice."
|
|
fi
|
|
if [ -z "$PKG" ]; then
|
|
warn "no supported package manager. Nothing will be installed for you;"
|
|
warn "make sure tmux, git and a Rust toolchain are present."
|
|
fi
|
|
if [ -n "$HOME_FREE" ] && [ "$HOME_FREE" -lt "$COST_MB" ]; then
|
|
warn "only ${HOME_FREE} MB free in \$HOME but about ${COST_MB} MB is needed."
|
|
warn "A source build will not fit. Free space first, or set CARGO_HOME and"
|
|
warn "CARGO_TARGET_DIR to somewhere with room."
|
|
fi
|
|
|
|
step "This will"
|
|
if [ -n "$NEED_PKGS" ]; then say " install packages:$NEED_PKGS"; fi
|
|
if [ "$NEED_RUSTUP" = yes ]; then say " install the Rust toolchain via rustup (~1.2 GB)"; fi
|
|
say " build socktop-swipe from source (~600 MB of build artifacts)"
|
|
say " install the binary to $BIN"
|
|
say " write a starter config to $CONF"
|
|
say " ...then ask about the touch device, autostart, autologin and blanking."
|
|
say ""
|
|
say " About ${COST_MB} MB of disk. Nothing has been changed yet."
|
|
say ""
|
|
|
|
if [ "$HAVE_TTY" = no ]; then
|
|
say "No terminal available, so every question takes its default."
|
|
say "Run the script directly for the interactive version:"
|
|
say " curl -fsSLO $REPO/raw/branch/main/install.sh && sh install.sh"
|
|
say ""
|
|
fi
|
|
|
|
ask "Continue?" y || { say "Nothing done."; exit 0; }
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Dependencies
|
|
# --------------------------------------------------------------------------
|
|
if [ -n "$NEED_PKGS" ]; then
|
|
step "Installing:$NEED_PKGS"
|
|
if [ -z "$PKG_INSTALL" ]; then
|
|
warn "no package manager -- install$NEED_PKGS yourself and re-run."
|
|
exit 1
|
|
fi
|
|
if [ "$PKG" = apt-get ]; then sudo apt-get update; fi
|
|
# shellcheck disable=SC2086
|
|
$PKG_INSTALL $NEED_PKGS
|
|
fi
|
|
|
|
if [ "$NEED_RUSTUP" = yes ]; then
|
|
step "Installing the Rust toolchain"
|
|
if ask "Install rustup now?" y; then
|
|
curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --no-modify-path
|
|
# shellcheck disable=SC1091
|
|
. "$HOME/.cargo/env"
|
|
else
|
|
say "Install Rust yourself, then re-run this script."
|
|
exit 1
|
|
fi
|
|
fi
|
|
if ! have cargo; then warn "cargo still not on PATH -- open a new shell and re-run."; exit 1; fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Source
|
|
# --------------------------------------------------------------------------
|
|
here=$(CDPATH='' cd -- "$(dirname -- "$0")" 2>/dev/null && pwd || echo "")
|
|
if [ -n "$here" ] && [ -f "$here/Cargo.toml" ]; then
|
|
SRC=$here
|
|
step "Building from this checkout: $SRC"
|
|
else
|
|
SRC="$HOME/.local/src/socktop-swipe"
|
|
step "Fetching the source to $SRC"
|
|
mkdir -p "$(dirname "$SRC")"
|
|
if [ -d "$SRC/.git" ]; then
|
|
git -C "$SRC" pull --ff-only
|
|
else
|
|
git clone "$REPO" "$SRC"
|
|
fi
|
|
fi
|
|
|
|
step "Building (this is the slow part on a low-power box)"
|
|
( cd "$SRC" && cargo build --release )
|
|
sudo install -d "$BIN"
|
|
sudo install -m 755 "$SRC/target/release/socktop-swipe" "$BIN/socktop-swipe"
|
|
say " installed $BIN/socktop-swipe"
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Touch device
|
|
# --------------------------------------------------------------------------
|
|
step "Touch panel"
|
|
DEVICE=""
|
|
# Prefer the by-id path: eventN numbers get reshuffled on reboot.
|
|
for d in /dev/input/by-id/*event*; do
|
|
[ -e "$d" ] || continue
|
|
if udevadm info --query=property --name="$d" 2>/dev/null | grep -q '^ID_INPUT_TOUCHSCREEN=1'; then
|
|
say " found touchscreen: $d"
|
|
if [ -z "$DEVICE" ]; then DEVICE=$d; fi
|
|
fi
|
|
done
|
|
if [ -z "$DEVICE" ]; then
|
|
warn "no touchscreen found under /dev/input/by-id/."
|
|
warn "If the panel is not plugged in yet, that is fine -- set touch.device"
|
|
warn "in the config later. 'socktop-swipe doctor --list' will show it."
|
|
DEVICE=/dev/input/by-id/CHANGE-ME
|
|
else
|
|
DEVICE=$(askval "Use which device?" "$DEVICE")
|
|
fi
|
|
|
|
step "Device access"
|
|
say " Reading touch events needs permission on the device."
|
|
say " A udev rule grants it for touchscreens only and takes effect at once."
|
|
say " The 'input' group grants access to every input device, keyboard included,"
|
|
say " and needs a full logout."
|
|
RELOGIN=no
|
|
if ask "Install the udev rule?" y; then
|
|
sudo install -m 644 "$SRC/packaging/70-socktop-swipe.rules" "$UDEV"
|
|
sudo udevadm control --reload
|
|
sudo udevadm trigger --subsystem-match=input
|
|
say " wrote $UDEV"
|
|
if ! id -nG | tr ' ' '\n' | grep -qx input; then
|
|
warn "the rule uses GROUP=input and you are not in it."
|
|
if ask "Add $(id -un) to the 'input' group as well?" y; then
|
|
sudo usermod -aG input "$(id -un)"
|
|
RELOGIN=yes
|
|
fi
|
|
fi
|
|
elif ask "Add $(id -un) to the 'input' group instead?" n; then
|
|
sudo usermod -aG input "$(id -un)"
|
|
RELOGIN=yes
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Config
|
|
# --------------------------------------------------------------------------
|
|
step "Configuration"
|
|
mkdir -p "$CONF_DIR"
|
|
if [ -e "$CONF" ]; then
|
|
say " keeping the existing $CONF"
|
|
else
|
|
sed "s|^ device: .*| device: $DEVICE|" "$SRC/config.example.yaml" >"$CONF"
|
|
say " wrote $CONF"
|
|
say " EDIT IT: the example screens are the author's rack, not yours."
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Optional monitors
|
|
# --------------------------------------------------------------------------
|
|
step "Monitor programs (all optional -- socktop-swipe runs whatever you configure)"
|
|
if ! have socktop && ask "Install socktop (the point of the project)?" y; then
|
|
cargo install socktop
|
|
fi
|
|
if ! have uptime-kuma-status && ask "Install uptime-kuma-status?" n; then
|
|
cargo install uptime-kuma-status
|
|
fi
|
|
if ! have unifly && ask "Build unifly (UniFi TUI) from source?" n; then
|
|
U="$HOME/.local/src/unifly"
|
|
mkdir -p "$(dirname "$U")"
|
|
if [ -d "$U/.git" ]; then git -C "$U" pull --ff-only; else
|
|
git clone https://github.com/jasonwitty/unifly "$U"
|
|
fi
|
|
( cd "$U" && cargo build --profile release-small -p unifly )
|
|
say " built $U/target/release-small/unifly"
|
|
say " put that path under 'binaries: unifly:' in $CONF"
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Unattended operation
|
|
# --------------------------------------------------------------------------
|
|
step "Wall-display behaviour"
|
|
|
|
if ask "Stop the screen blanking, sleeping and locking?" y; then
|
|
sudo mkdir -p /etc/X11/xorg.conf.d
|
|
sudo tee "$XBLANK" >/dev/null <<-'EOF'
|
|
# Installed by socktop-swipe. This is a wall display: it must never blank.
|
|
Section "ServerFlags"
|
|
Option "BlankTime" "0"
|
|
Option "StandbyTime" "0"
|
|
Option "SuspendTime" "0"
|
|
Option "OffTime" "0"
|
|
EndSection
|
|
EOF
|
|
say " wrote $XBLANK"
|
|
# The Xorg snippet covers every session including the greeter and survives
|
|
# reboots; xset covers a session that re-enables blanking after X starts.
|
|
# Both are needed, and xset MUST run as the session user -- under sudo it
|
|
# targets root's X connection and silently does nothing.
|
|
if [ -n "${DISPLAY:-}" ] && have xset; then
|
|
xset s off s noblank -dpms 2>/dev/null || true
|
|
say " applied to the running session"
|
|
fi
|
|
for l in $LOCKER; do
|
|
warn "$l is installed and may still lock the screen. Disable it in your"
|
|
warn "session's autostart."
|
|
done
|
|
fi
|
|
|
|
if [ "$WM" = i3 ] && [ -e "$HOME/.config/i3/config" ]; then
|
|
if ask "Add the i3 autostart lines?" y; then
|
|
i3conf="$HOME/.config/i3/config"
|
|
if grep -q socktop-swipe "$i3conf"; then
|
|
say " already present; leaving it alone"
|
|
else
|
|
bak="$i3conf.bak-$(date +%Y%m%d-%H%M%S)"
|
|
cp "$i3conf" "$bak"
|
|
{
|
|
printf '\n# --- socktop-swipe ---\n'
|
|
printf '# One process: builds the session, opens it in a terminal and reads the panel.\n'
|
|
printf 'exec --no-startup-id %s/socktop-swipe run\n' "$BIN"
|
|
printf '# Never blank (the Xorg snippet covers reboots; this covers this session).\n'
|
|
printf 'exec --no-startup-id xset s off s noblank -dpms\n'
|
|
if have unclutter; then
|
|
printf '# With the panel grabbed, nothing ever moves the pointer, so hide it.\n'
|
|
printf 'exec --no-startup-id unclutter --timeout 1 --ignore-scrolling\n'
|
|
fi
|
|
} >>"$i3conf"
|
|
if i3 -C -c "$i3conf" >/dev/null 2>&1; then
|
|
say " added the autostart (backup: $bak)"
|
|
else
|
|
cp "$bak" "$i3conf"
|
|
warn "i3 rejected the config; restored $bak"
|
|
fi
|
|
fi
|
|
fi
|
|
elif [ "$DM" != none ] || [ "$WM" != none ]; then
|
|
say " no i3 config found. For a systemd user session instead:"
|
|
say " cp $SRC/packaging/socktop-swipe.service ~/.config/systemd/user/"
|
|
say " systemctl --user enable --now socktop-swipe"
|
|
fi
|
|
|
|
if [ "$DM" = lightdm ]; then
|
|
say ""
|
|
say " Autologin makes the display come back by itself after a power cut."
|
|
say " ANYONE WITH PHYSICAL ACCESS TO THE PANEL GETS THIS USER'S SESSION."
|
|
say " Only do this where the account is nothing but the dashboard."
|
|
if ask "Log $(id -un) straight into $WM at boot?" n; then
|
|
sudo mkdir -p /etc/lightdm/lightdm.conf.d
|
|
sudo tee "$LIGHTDM" >/dev/null <<-EOF
|
|
# Installed by socktop-swipe. Delete this file to restore the login prompt.
|
|
[Seat:*]
|
|
autologin-user=$(id -un)
|
|
autologin-user-timeout=0
|
|
autologin-session=$WM
|
|
EOF
|
|
say " wrote $LIGHTDM (takes effect at next boot)"
|
|
fi
|
|
elif [ "$DM" != none ]; then
|
|
say " Autologin is only automated for lightdm; yours is $DM. Configure it there"
|
|
say " if the display must come back unattended after a power cut."
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
say ""
|
|
say "=============================================================="
|
|
say "Installed."
|
|
if [ "$RELOGIN" = yes ]; then say "LOG OUT AND BACK IN before use (group membership changed)."; fi
|
|
cat <<EOF
|
|
|
|
Next:
|
|
1. Edit $CONF -- the screens are the author's, not yours.
|
|
2. socktop-swipe validate check it, and see the grid map
|
|
3. socktop-swipe doctor swipe, and see what the panel reports
|
|
4. socktop-swipe run build the session and start swiping
|
|
|
|
EOF
|
|
say "=============================================================="
|