Files
socktop-swipe/install.sh
T

441 lines
15 KiB
Bash
Raw Normal View History

#!/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. Measured on an Atom x5-Z8350: a 103 MB target directory,
# 108 seconds, peaking at 1.1 GB of RAM. 250 leaves headroom.
COST_MB=$((COST_MB + 250))
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 (~100 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 "=============================================================="