#!/usr/bin/env bash # install-espanso.sh — Install Espanso (text expander) with Wayland or X11 build. # Auto-detects session type; falls back to Wayland if unknown. # Hardened: handles apt temp file permissions, package collisions, and ghost processes. set -euo pipefail IFS=$'\n\t' readonly SCRIPT_NAME="${0##*/}" DRY_RUN=0 FORCE_VARIANT="" # "wayland" | "x11" usage() { cat <&2; } die() { printf '\033[1;31m[%s] ERROR:\033[0m %s\n' "${SCRIPT_NAME%.sh}" "$*" >&2; exit 1; } run() { if (( DRY_RUN )); then printf ' DRY-RUN: %s\n' "$*"; else eval "$@"; fi; } trap 'rc=$?; (( rc )) && printf "\033[1;31m[%s] failed at line %s (exit %d)\033[0m\n" "${SCRIPT_NAME%.sh}" "$LINENO" "$rc" >&2' ERR while (( $# )); do case "$1" in --dry-run) DRY_RUN=1 ;; --wayland) FORCE_VARIANT=wayland ;; --x11) FORCE_VARIANT=x11 ;; -h|--help) usage; exit 0 ;; *) die "Unknown argument: $1 (try --help)" ;; esac shift done (( EUID == 0 )) || die "Must run as root. Try: sudo $SCRIPT_NAME" [[ -r /etc/os-release ]] || die "/etc/os-release not found." # shellcheck disable=SC1091 . /etc/os-release case "${ID:-}:${ID_LIKE:-}" in *ubuntu*|*debian*) : ;; *) die "Unsupported distro: ${PRETTY_NAME:-unknown}." ;; esac ARCH="$(dpkg --print-architecture)" [[ "$ARCH" == "amd64" ]] || die "Espanso .deb is published for amd64 only (detected: $ARCH)." ACTUAL_USER="${SUDO_USER:-${USER:-}}" [[ -n "$ACTUAL_USER" && "$ACTUAL_USER" != "root" ]] || die "Run via sudo as a regular user; cannot register the service as root." USER_ID="$(id -u "$ACTUAL_USER")" # --- FIXED: Better session detection logic using loginctl --- if [[ -n "$FORCE_VARIANT" ]]; then VARIANT="$FORCE_VARIANT" else # 1. Try systemd loginctl (most reliable) SESSION_ID=$(loginctl list-sessions 2>/dev/null | grep " $ACTUAL_USER " | awk '{print $1}' | head -n 1 || true) if [[ -n "$SESSION_ID" ]]; then SESSION_TYPE=$(loginctl show-session "$SESSION_ID" -p Type --value 2>/dev/null || true) else SESSION_TYPE="" fi # 2. If loginctl fails, check for active Wayland/X11 socket files if [[ -z "$SESSION_TYPE" || "$SESSION_TYPE" == "tty" ]]; then if sudo -u "$ACTUAL_USER" test -S "/run/user/$USER_ID/wayland-0"; then SESSION_TYPE="wayland" elif sudo -u "$ACTUAL_USER" test -S "/tmp/.X11-unix/X0"; then SESSION_TYPE="x11" else SESSION_TYPE="wayland" # Default fallback for modern Ubuntu fi fi case "$SESSION_TYPE" in wayland) VARIANT=wayland ;; x11) VARIANT=x11 ;; *) warn "Unknown session type='$SESSION_TYPE'; defaulting to wayland."; VARIANT=wayland ;; esac fi log "Detected: ${PRETTY_NAME:-unknown}, user: $ACTUAL_USER, session: $VARIANT" export DEBIAN_FRONTEND=noninteractive # --- FIXED: Use a subdirectory to avoid apt "unsandboxed" permission warnings --- WORK_DIR="$(mktemp -d /tmp/espanso-install.XXXXXX)" chmod 755 "$WORK_DIR" TEMP_DEB="$WORK_DIR/espanso.deb" trap 'rm -rf "$WORK_DIR"' EXIT DEB_NAME="espanso-debian-${VARIANT}-amd64.deb" URL="https://github.com/espanso/espanso/releases/latest/download/${DEB_NAME}" log "Installing prerequisites..." run "apt-get update -qq" run "apt-get install -y wget libcap2-bin psmisc" # --- FIXED: Use wget with progress bar instead of silent mode --- log "Downloading ${DEB_NAME}..." run "wget -q --show-progress -O '$TEMP_DEB' '$URL'" log "Removing conflicting Espanso packages (if any)..." run "apt-get remove -y espanso espanso-wayland >/dev/null 2>&1 || true" log "Installing package..." run "apt-get install -y '$TEMP_DEB'" ESPANSO_BIN="$(command -v espanso || true)" [[ -x "$ESPANSO_BIN" ]] || die "espanso binary not found after install." if [[ "$VARIANT" == "wayland" ]]; then log "Setting CAP_DAC_OVERRIDE on $ESPANSO_BIN..." run "setcap 'cap_dac_override+p' '$ESPANSO_BIN'" fi log "Clearing ghost processes to prevent start timeouts..." run "sudo -u '$ACTUAL_USER' killall espanso 2>/dev/null || true" log "Registering & starting espanso service for $ACTUAL_USER..." if (( DRY_RUN )); then printf ' DRY-RUN: sudo -u %s XDG_RUNTIME_DIR=/run/user/%s espanso service register || true\n' "$ACTUAL_USER" "$USER_ID" printf ' DRY-RUN: sudo -u %s XDG_RUNTIME_DIR=/run/user/%s espanso start || true\n' "$ACTUAL_USER" "$USER_ID" else sudo -u "$ACTUAL_USER" XDG_RUNTIME_DIR="/run/user/$USER_ID" espanso service register || true sudo -u "$ACTUAL_USER" XDG_RUNTIME_DIR="/run/user/$USER_ID" espanso restart || \ sudo -u "$ACTUAL_USER" XDG_RUNTIME_DIR="/run/user/$USER_ID" espanso start || true fi log "Done. Espanso ($VARIANT) installed and started for $ACTUAL_USER." if [[ "$VARIANT" == "wayland" ]]; then log "Wayland note: non-US keyboards must set the layout in ~/.config/espanso/config/default.yml" fi