Files
Windows-11-Clipboard-Histor…/scripts/install.sh
Gustavo Carvalho 0cde431e72 feat: multi arch support (#175)
* feat: multi arch support

* fix: sanitize GTK environment

Co-authored-by: kinou-p <kinou-p@users.noreply.github.com>
2026-02-13 21:31:44 -03:00

594 lines
21 KiB
Bash
Executable File

#!/bin/bash
# install.sh - Smart installer for Win11 Clipboard History
# Usage: curl -fsSL https://raw.githubusercontent.com/gustavosett/Windows-11-Clipboard-History-For-Linux/master/scripts/install.sh | bash
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[*]${NC} $1"; }
success() { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
error() { echo -e "${RED}[✗]${NC} $1"; exit 1; }
# Configuration
REPO_OWNER="gustavosett"
REPO_NAME="Windows-11-Clipboard-History-For-Linux"
CLOUDSMITH_REPO="gustavosett/clipboard-manager"
# Cleanup previous AppImage installation (prevents conflicts with package manager installs)
cleanup_appimage_installation() {
local has_appimage=false
# Check for AppImage installation artifacts
if [ -f "$HOME/.local/bin/win11-clipboard-history.AppImage" ] || \
[ -f "$HOME/.local/bin/win11-clipboard-history" ] || \
[ -f "$HOME/.local/share/applications/win11-clipboard-history.desktop" ]; then
has_appimage=true
fi
if [ "$has_appimage" = true ]; then
log "Detected previous AppImage installation. Cleaning up..."
# Kill any running AppImage instances
pkill -f "win11-clipboard-history.AppImage" 2>/dev/null || true
# Wait for processes to terminate, with a timeout
timeout=5
interval=1
elapsed=0
while pgrep -f "win11-clipboard-history.AppImage" >/dev/null 2>&1; do
if [ "$elapsed" -ge "$timeout" ]; then
warn "Timed out waiting for Win11 Clipboard History AppImage processes to terminate."
break
fi
sleep "$interval"
elapsed=$((elapsed + interval))
done
# Remove AppImage files
rm -f "$HOME/.local/bin/win11-clipboard-history.AppImage" 2>/dev/null || true
rm -f "$HOME/.local/bin/win11-clipboard-history" 2>/dev/null || true
rm -f "$HOME/.local/share/applications/win11-clipboard-history.desktop" 2>/dev/null || true
rm -f "$HOME/.local/share/icons/hicolor"/*/apps/win11-clipboard-history.png 2>/dev/null || true
# Update desktop database if available
if command -v update-desktop-database &>/dev/null; then
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
fi
success "Previous AppImage installation cleaned up"
fi
}
# Detect the distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO_ID="${ID}"
DISTRO_ID_LIKE="${ID_LIKE:-}"
SYSTEM_FAMILY_INFO=$(echo "$ID $ID_LIKE" | tr '[:upper:]' '[:lower:]')
else
error "Cannot detect distribution. /etc/os-release not found."
fi
}
# Detect system architecture and set DEB_ARCH/RPM_ARCH
detect_arch() {
local arch
arch=$(uname -m)
case "$arch" in
x86_64|amd64)
DEB_ARCH="amd64"
RPM_ARCH="x86_64"
;;
aarch64|arm64)
DEB_ARCH="arm64"
RPM_ARCH="aarch64"
;;
armv7l|armhf)
DEB_ARCH="armhf"
RPM_ARCH="armv7hl"
;;
*)
warn "Unknown architecture: $arch. Defaulting to x86_64."
DEB_ARCH="amd64"
RPM_ARCH="x86_64"
;;
esac
log "Architecture: $arch (DEB: $DEB_ARCH, RPM: $RPM_ARCH)"
}
# Check WebKitGTK compatibility
check_webkit_compatibility() {
log "Checking WebKitGTK compatibility..."
if ldconfig -p 2>/dev/null | grep -q "libwebkit2gtk-4.1"; then
success "WebKitGTK 4.1 found (modern)"
return 0
elif ldconfig -p 2>/dev/null | grep -q "libwebkit2gtk-4.0"; then
warn "WebKitGTK 4.0 found (legacy)"
warn "Package manager installation may fail. AppImage fallback available."
return 1
else
warn "WebKitGTK not found. Will be installed with the package."
return 0
fi
}
# Installation via package manager
install_via_package_manager() {
# Clean up any previous AppImage installation to prevent PATH conflicts
cleanup_appimage_installation
# IMPORTANT: Check distro ID/family FIRST before falling back to command detection.
# This prevents misdetection when tools like pacman are installed on non-Arch systems.
# 1. Check for Fedora/RHEL Family
# Note: fedora-asahi-remix is a Fedora-based distro for Apple Silicon Macs
if [[ "$DISTRO_ID" == "fedora-asahi-remix" || "$SYSTEM_FAMILY_INFO" =~ "fedora" || "$SYSTEM_FAMILY_INFO" =~ "rhel" || "$SYSTEM_FAMILY_INFO" =~ "centos" ]]; then
install_rpm
return 0
# 2. Check for Debian/Ubuntu Family
elif [[ "$SYSTEM_FAMILY_INFO" =~ "debian" || "$SYSTEM_FAMILY_INFO" =~ "ubuntu" ]]; then
install_deb
return 0
# 3. Check for OpenSUSE Family
elif [[ "$SYSTEM_FAMILY_INFO" =~ "suse" ]]; then
install_rpm_suse
return 0
# 4. Check for Arch Family (Arch, Manjaro, CachyOS, Endeavour, etc)
# Check this AFTER other distros to avoid false positives from pacman being installed
elif [[ "$SYSTEM_FAMILY_INFO" =~ "arch" ]]; then
install_aur
return 0
fi
# Fallback: Check for package managers if distro detection failed
# This handles edge cases where /etc/os-release is incomplete
if command -v dnf &>/dev/null; then
install_rpm
return 0
elif command -v apt-get &>/dev/null; then
install_deb
return 0
elif command -v zypper &>/dev/null; then
install_rpm_suse
return 0
elif command -v pacman &>/dev/null; then
install_aur
return 0
fi
return 1 # Unknown system family
}
install_deb() {
log "Setting up APT repository (Cloudsmith)..."
# Install prerequisites for HTTPS repos
sudo apt-get update -qq
sudo apt-get install -y apt-transport-https curl || true
# Try Cloudsmith repository first (enables auto-updates)
if curl -1sLf "https://dl.cloudsmith.io/public/${CLOUDSMITH_REPO}/setup.deb.sh" | sudo -E bash 2>/dev/null; then
log "Installing win11-clipboard-history from repository..."
sudo apt-get update -qq
if sudo apt-get install -y win11-clipboard-history; then
success "Installed via APT repository! (auto-updates enabled)"
return 0
fi
fi
# Fallback: download from GitHub releases
warn "Repository not available, falling back to GitHub release..."
log "Installing from GitHub releases (.deb)..."
LATEST_RELEASE_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest"
RELEASE_TAG=$(curl -s "$LATEST_RELEASE_URL" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"([^"]+)".*/\1/' | tr -cd '[:alnum:]._-')
[ -z "$RELEASE_TAG" ] && error "Failed to fetch version."
CLEAN_VERSION="${RELEASE_TAG#v}"
TEMP_DIR=$(mktemp -d)
chmod 755 "$TEMP_DIR"
cd "$TEMP_DIR"
trap 'rm -rf "$TEMP_DIR"' EXIT
FILE="win11-clipboard-history_${CLEAN_VERSION}_${DEB_ARCH}.deb"
BASE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$RELEASE_TAG"
log "Downloading $FILE..."
if ! curl -L -o "$FILE" "$BASE_URL/$FILE" --progress-bar --fail; then
error "Failed to download $FILE"
fi
chmod 644 "$FILE"
log "Installing dependencies..."
sudo apt-get install -y xclip wl-clipboard acl || true
sudo apt-get install -y libayatana-appindicator3-1 || sudo apt-get install -y libappindicator3-1 || true
log "Installing .deb package..."
yes | sudo apt-get install -y "./$FILE"
success "Installed via APT (from GitHub release)"
}
install_rpm() {
log "Setting up RPM repository (Cloudsmith)..."
local env_args=()
if [[ "$DISTRO_ID" == "fedora-asahi-remix" ]]; then
log "Detected Fedora Asahi Remix - using standard Fedora repository..."
local fedora_version=""
if [ -f /etc/os-release ]; then
fedora_version="$(awk -F= '$1=="VERSION_ID"{gsub(/"/,"",$2);print $2}' /etc/os-release)"
fi
env_args=("distro=fedora" "codename=")
if [[ -n "$fedora_version" ]]; then
env_args+=("version=$fedora_version")
fi
fi
# Try Cloudsmith repository first (enables auto-updates)
local setup_cmd="curl -1sLf 'https://dl.cloudsmith.io/public/${CLOUDSMITH_REPO}/setup.rpm.sh' | sudo -E bash"
local repo_setup_success=false
if [[ ${#env_args[@]} -gt 0 ]]; then
env "${env_args[@]}" bash -c "$setup_cmd" 2>/dev/null && repo_setup_success=true
else
bash -c "$setup_cmd" 2>/dev/null && repo_setup_success=true
fi
if [ "$repo_setup_success" = true ]; then
log "Installing win11-clipboard-history from repository..."
if sudo dnf install -y win11-clipboard-history; then
success "Installed via DNF repository! (auto-updates enabled)"
return 0
fi
fi
# Fallback: download from GitHub releases
warn "Repository not available, falling back to GitHub release..."
log "Installing from GitHub releases (.rpm)..."
LATEST_RELEASE_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest"
RELEASE_TAG=$(curl -s "$LATEST_RELEASE_URL" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"([^"]+)".*/\1/' | tr -cd '[:alnum:]._-')
[ -z "$RELEASE_TAG" ] && error "Failed to fetch version."
CLEAN_VERSION="${RELEASE_TAG#v}"
TEMP_DIR=$(mktemp -d)
chmod 755 "$TEMP_DIR"
cd "$TEMP_DIR"
trap 'rm -rf "$TEMP_DIR"' EXIT
FILE="win11-clipboard-history-${CLEAN_VERSION}-1.${RPM_ARCH}.rpm"
BASE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$RELEASE_TAG"
log "Downloading $FILE..."
if ! curl -L -o "$FILE" "$BASE_URL/$FILE" --progress-bar --fail; then
error "Failed to download $FILE"
fi
chmod 644 "$FILE"
log "Installing dependencies..."
sudo dnf install -y xclip wl-clipboard acl libayatana-appindicator-gtk3 || true
log "Installing .rpm package..."
sudo dnf install -y "./$FILE"
success "Installed via DNF (from GitHub release)"
}
install_rpm_suse() {
log "Setting up RPM repository (Cloudsmith)..."
# Try Cloudsmith repository first (enables auto-updates)
if curl -1sLf "https://dl.cloudsmith.io/public/${CLOUDSMITH_REPO}/setup.rpm.sh" | sudo -E bash 2>/dev/null; then
log "Installing win11-clipboard-history from repository..."
if sudo zypper install -y win11-clipboard-history; then
success "Installed via Zypper repository! (auto-updates enabled)"
return 0
fi
fi
# Fallback: download from GitHub releases
warn "Repository not available, falling back to GitHub release..."
log "Installing from GitHub releases (.rpm)..."
LATEST_RELEASE_URL="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest"
RELEASE_TAG=$(curl -s "$LATEST_RELEASE_URL" | grep '"tag_name":' | head -n 1 | sed -E 's/.*"([^"]+)".*/\1/' | tr -cd '[:alnum:]._-')
[ -z "$RELEASE_TAG" ] && error "Failed to fetch version."
CLEAN_VERSION="${RELEASE_TAG#v}"
TEMP_DIR=$(mktemp -d)
chmod 755 "$TEMP_DIR"
cd "$TEMP_DIR"
trap 'rm -rf "$TEMP_DIR"' EXIT
FILE="win11-clipboard-history-${CLEAN_VERSION}-1.${RPM_ARCH}.rpm"
BASE_URL="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$RELEASE_TAG"
log "Downloading $FILE..."
if ! curl -L -o "$FILE" "$BASE_URL/$FILE" --progress-bar --fail; then
error "Failed to download $FILE"
fi
chmod 644 "$FILE"
log "Installing dependencies..."
sudo zypper install -y xclip wl-clipboard acl libayatana-appindicator3-1 || true
log "Installing .rpm package..."
sudo zypper install -y "./$FILE"
success "Installed via Zypper (from GitHub release)"
}
install_aur() {
log "Installing from AUR..."
# Detect AUR helper
if command -v yay &>/dev/null; then
yay -S --noconfirm win11-clipboard-history-bin
elif command -v paru &>/dev/null; then
paru -S --noconfirm win11-clipboard-history-bin
else
warn "No AUR helper found. Installing yay first..."
sudo pacman -S --needed --noconfirm git base-devel
git clone https://aur.archlinux.org/yay-bin.git /tmp/yay-bin
cd /tmp/yay-bin && makepkg -si --noconfirm
yay -S --noconfirm win11-clipboard-history-bin
fi
success "Installed via AUR!"
}
install_appimage() {
log "Installing AppImage (universal fallback)..."
local arch_name
arch_name=$(uname -m)
# Fetch latest version, filtering by architecture
LATEST_URL=$(curl -s https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest \
| grep "browser_download_url.*AppImage" \
| grep -i "${DEB_ARCH}\|${arch_name}" \
| head -1 \
| cut -d '"' -f 4)
# Fallback: try any AppImage if no arch-specific match found
if [ -z "$LATEST_URL" ]; then
LATEST_URL=$(curl -s https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest \
| grep "browser_download_url.*AppImage" \
| head -1 \
| cut -d '"' -f 4)
fi
if [ -z "$LATEST_URL" ]; then
error "Could not find AppImage download URL"
fi
# Create directories
mkdir -p "$HOME/.local/bin"
mkdir -p "$HOME/.local/share/applications"
mkdir -p "$HOME/.local/share/icons/hicolor/128x128/apps"
# Download AppImage
log "Downloading AppImage..."
curl -fsSL -o "$HOME/.local/bin/win11-clipboard-history.AppImage" "$LATEST_URL"
chmod +x "$HOME/.local/bin/win11-clipboard-history.AppImage"
# Download app icon for proper menu integration
log "Downloading app icon..."
curl -fsSL -o "$HOME/.local/share/icons/hicolor/128x128/apps/win11-clipboard-history.png" \
"https://raw.githubusercontent.com/$REPO_OWNER/$REPO_NAME/master/src-tauri/icons/128x128.png" 2>/dev/null || true
# Wrapper script — mirrors src-tauri/bundle/linux/wrapper.sh sanitization
cat > "$HOME/.local/bin/win11-clipboard-history" << 'WRAPPER_EOF'
#!/bin/bash
# AppImage wrapper for win11-clipboard-history
# Sanitizes Snap/Flatpak environment leaks, then launches the AppImage.
# Always clear library/runtime overrides from sandbox parents
unset LD_LIBRARY_PATH
unset LD_PRELOAD
unset GTK_PATH
unset GIO_MODULE_DIR
unset GTK_IM_MODULE_FILE
unset GTK_EXE_PREFIX
unset LOCPATH
unset GSETTINGS_SCHEMA_DIR
# Fix XDG_DATA_DIRS only when contaminated by sandbox paths
sanitize_xdg_data_dirs() {
local xdg="${XDG_DATA_DIRS:-}"
local system_dirs="/usr/local/share:/usr/share:/var/lib/snapd/desktop"
if [[ -z "${SNAP:-}" && -z "${FLATPAK_ID:-}" && "$xdg" != *"/snap/"* && "$xdg" != *"/flatpak/"* ]]; then
return
fi
local cleaned=""
local entry
IFS=':' read -ra entries <<< "$xdg"
for entry in "${entries[@]}"; do
case "$entry" in
*/snap/*|*/flatpak/*) continue ;;
esac
case ":$system_dirs:" in
*":$entry:"*) continue ;;
esac
cleaned="${cleaned:+$cleaned:}$entry"
done
export XDG_DATA_DIRS="${system_dirs}${cleaned:+:$cleaned}"
}
sanitize_xdg_data_dirs
export GDK_SCALE="${GDK_SCALE:-1}"
export GDK_DPI_SCALE="${GDK_DPI_SCALE:-1}"
export NO_AT_BRIDGE=1
exec "$HOME/.local/bin/win11-clipboard-history.AppImage" "$@"
WRAPPER_EOF
chmod +x "$HOME/.local/bin/win11-clipboard-history"
# .desktop file with proper icon
cat > "$HOME/.local/share/applications/win11-clipboard-history.desktop" << EOF
[Desktop Entry]
Type=Application
Name=Clipboard History
Comment=Windows 11-style Clipboard History Manager
Exec=$HOME/.local/bin/win11-clipboard-history
Icon=win11-clipboard-history
Terminal=false
Categories=Utility;
StartupWMClass=win11-clipboard-history
EOF
# Ask about udev rules for AppImage (optional - maintains portability)
setup_udev_appimage_optional
success "AppImage installed to ~/.local/bin/"
warn "Add ~/.local/bin to your PATH if not already there"
}
setup_udev_appimage_optional() {
echo ""
warn "For paste simulation to work, the app needs access to /dev/uinput."
echo ""
echo "You have two options:"
echo " 1. Quick fix (no logout required): Run this command:"
echo " sudo setfacl -m u:$USER:rw /dev/uinput"
echo ""
echo " 2. Permanent fix (requires sudo, then logout/login):"
echo " The installer can set up udev rules for you."
echo ""
# Check if running interactively
if [ -t 0 ]; then
read -p "Set up permanent udev rules now? [y/N] " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
setup_udev_appimage
else
log "Skipping udev setup. You can run this later if paste doesn't work:"
echo " sudo setfacl -m u:$USER:rw /dev/uinput"
fi
else
# Non-interactive: just use ACL for immediate access
log "Non-interactive mode: Using ACL for immediate access..."
if command -v setfacl &>/dev/null && [ -e /dev/uinput ]; then
sudo setfacl -m "u:${USER}:rw" /dev/uinput 2>/dev/null || true
success "Permissions configured via ACL"
else
warn "Run 'sudo setfacl -m u:$USER:rw /dev/uinput' if paste doesn't work"
fi
fi
}
setup_udev_appimage() {
log "Setting up permanent uinput permissions (requires sudo)..."
# Create udev rule
sudo tee /etc/udev/rules.d/99-win11-clipboard-input.rules > /dev/null << 'EOF'
# udev rules for Windows 11 Clipboard History
ACTION=="add", SUBSYSTEM=="misc", KERNEL=="uinput", OPTIONS+="static_node=uinput"
KERNEL=="uinput", SUBSYSTEM=="misc", MODE="0660", GROUP="input", TAG+="uaccess"
EOF
# Configure module to load on boot
echo "uinput" | sudo tee /etc/modules-load.d/win11-clipboard.conf > /dev/null
# Load now
sudo modprobe uinput 2>/dev/null || true
sudo udevadm control --reload-rules 2>/dev/null || true
sudo udevadm trigger --subsystem-match=misc 2>/dev/null || true
# ACL for immediate access
if command -v setfacl &>/dev/null && [ -e /dev/uinput ]; then
sudo setfacl -m "u:${USER}:rw" /dev/uinput 2>/dev/null || true
success "Permissions configured (immediate access via ACL)"
else
warn "You may need to log out and back in for permissions to take effect"
fi
}
launch_app() {
log "Starting application..."
# Kill any existing instances (matches both wrapper and -bin binary)
pkill -f "win11-clipboard-history-bin" 2>/dev/null || true
pkill -f "win11-clipboard-history.AppImage" 2>/dev/null || true
sleep 1
# Launch detached from terminal
nohup win11-clipboard-history >/dev/null 2>&1 < /dev/null & disown
sleep 2
if pgrep -f "win11-clipboard-history" > /dev/null; then
return 0
else
return 1
fi
}
# Main
main() {
echo ""
echo "╔═══════════════════════════════════════════════════════════╗"
echo "║ Win11 Clipboard History - Linux Installer ║"
echo "╚═══════════════════════════════════════════════════════════╝"
echo ""
command -v curl >/dev/null 2>&1 || error "curl is required."
detect_distro
detect_arch
log "Detected: $DISTRO_ID (Family: $SYSTEM_FAMILY_INFO)"
# Check WebKitGTK compatibility
check_webkit_compatibility
webkit_status=$?
# Prefer AppImage if only legacy WebKitGTK 4.0 is available
if [ "$webkit_status" -eq 1 ]; then
warn "Legacy WebKitGTK detected. Preferring AppImage for better compatibility."
install_appimage
# Try package manager first
elif install_via_package_manager; then
success "Package installation complete!"
else
warn "No native package found for your system family. Using AppImage."
install_appimage
fi
# Try to launch
if launch_app; then
echo ""
success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
success " Installation complete! App is running."
success " Press Super+V to open your clipboard history."
success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
else
echo ""
success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
success " Installation complete!"
success " Run 'win11-clipboard-history' or find it in your menu."
success " Press Super+V to open your clipboard history."
success "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
fi
echo ""
}
main "$@"