Files
Modus/install.sh
T
sankalp e93e115522 fix(install): skip already-installed packages via pacman -Qi
Avoids AUR helper failures for packages that exist locally
2026-07-19 18:56:54 +05:45

561 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# ███╗ ███╗ ██████╗ ██████╗ ██╗ ██╗███████╗
# ████╗ ████║██╔═══██╗██╔══██╗██║ ██║██╔════╝
# ██╔████╔██║██║ ██║██║ ██║██║ ██║███████╗
# ██║╚██╔╝██║██║ ██║██║ ██║██║ ██║╚════██║
# ██║ ╚═╝ ██║╚██████╔╝██████╔╝╚██████╔╝███████║
# ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
#
# A hackable shell for Hyprland
# Installation Script for Arch Linux
#
# Repository: https://github.com/S4NKALP/Modus
# License: GPLv3
set -e
set -u
set -o pipefail
# CLI flags
AUTO_YES=false
DRY_RUN=false
for arg in "$@"; do
case "$arg" in
-y|--yes) AUTO_YES=true ;;
-n|--dry-run) DRY_RUN=true ;;
-h|--help)
echo "Usage: install.sh [OPTIONS]"
echo ""
echo "Options:"
echo " -y, --yes Skip all prompts"
echo " -n, --dry-run Show what would be installed without installing"
echo " -h, --help Show this help"
exit 0
;;
esac
done
REPO_URL="https://github.com/S4NKALP/Modus.git"
INSTALL_DIR="$HOME/.config/Modus"
# Package definitions
PACKAGES=(
uv
fabric-cli-git
uwsm
cliphist
slurp
grim
swappy
wl-clipboard
wtype
libnotify
playerctl
matugen-bin
hypridle
hyprsunset
hyprpicker
hyprshot
gtk-session-lock
awww
otf-apple-sf-pro
webp-pixbuf-loader
cinnamon-desktop
libmediaart
acpi
brightnessctl
power-profiles-daemon
ddcutil
at-spi2-core
networkmanager
network-manager-applet
blueman
pipewire
libpulse
gcc
make
pkgconf
meson
ninja
wayland-protocols
gobject-introspection
gtk-layer-shell
librsvg
libqalculate
appmenu-gtk-module
libdbusmenu-gtk3
libdbusmenu-qt5
pciutils
wf-recorder
ffmpeg
)
# Colors
if [ -t 1 ]; then
GREEN=$(tput setaf 2)
YELLOW=$(tput setaf 3)
RED=$(tput setaf 1)
CYAN=$(tput setaf 6)
BLUE=$(tput setaf 4)
MAGENTA=$(tput setaf 5)
BOLD=$(tput bold)
DIM=$(tput dim)
RESET=$(tput sgr0)
else
GREEN="" YELLOW="" RED="" CYAN="" BLUE="" MAGENTA="" BOLD="" DIM="" RESET=""
fi
ARROW="→"
CHECK="✔"
CROSS="✖"
INFO="ℹ"
WARN="⚠"
# Helpers
header() {
echo ""
echo -e " ${BOLD}${CYAN}╔══════════════════════════════════════════════╗${RESET}"
echo -e " ${BOLD}${CYAN}║${RESET} ${BOLD}Modus Installer${RESET} ${DIM}v2.0${RESET} ${BOLD}${CYAN}║${RESET}"
echo -e " ${BOLD}${CYAN}╚══════════════════════════════════════════════╝${RESET}"
echo ""
}
section() {
echo ""
echo -e " ${BOLD}${MAGENTA}── $1 ──${RESET}"
}
step() {
echo -e " ${CYAN}${ARROW}${RESET} $1"
}
success() {
echo -e " ${GREEN}${CHECK}${RESET} ${GREEN}$1${RESET}"
}
warn() {
echo -e " ${YELLOW}${WARN}${RESET} ${YELLOW}$1${RESET}"
}
error() {
echo -e " ${RED}${CROSS}${RESET} ${RED}$1${RESET}"
}
info() {
echo -e " ${BLUE}${INFO}${RESET} ${DIM}$1${RESET}"
}
divider() {
echo -e " ${DIM}──────────────────────────────────────────────${RESET}"
}
confirm() {
local prompt="$1"
local default="${2:-n}"
local yn
if [ "$AUTO_YES" = true ]; then
return 0
fi
if [ "$default" = "y" ]; then
read -rp " ${BOLD}${prompt} [Y/n]:${RESET} " yn
[[ ! "$yn" =~ ^[Nn]$ ]]
else
read -rp " ${BOLD}${prompt} [y/N]:${RESET} " yn
[[ "$yn" =~ ^[Yy]$ ]]
fi
}
spinner() {
local pid=$1
local message=$2
local spin=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r %b%s%b %s" "$CYAN" "${spin[i]}" "$RESET" "$message"
i=$(((i + 1) % 10))
sleep 0.1
done
wait "$pid" 2>/dev/null
local rc=$?
printf "\r"
if [ $rc -eq 0 ]; then
echo -e " ${GREEN}${CHECK}${RESET} ${GREEN}${message}${RESET}"
else
echo -e " ${RED}${CROSS}${RESET} ${RED}${message}${RESET}"
fi
return $rc
}
# Cleanup
cleanup() {
if [ -n "${SUDO_KEEPER_PID:-}" ]; then
kill "$SUDO_KEEPER_PID" 2>/dev/null || true
fi
jobs -p 2>/dev/null | xargs -r kill 2>/dev/null || true
}
trap cleanup EXIT INT TERM
# Banner
clear
header
echo -e " ${BOLD}A hackable shell for Hyprland${RESET}"
echo -e " ${DIM}https://github.com/S4NKALP/Modus${RESET}"
echo ""
# Pre-flight checks
section "Pre-flight checks"
step "Checking operating system..."
if ! grep -qi "arch" /etc/os-release; then
error "This script requires Arch Linux or an Arch-based distribution"
exit 1
fi
success "Arch Linux detected"
step "Checking user permissions..."
if [ "$(id -u)" -eq 0 ]; then
error "Please run this script as a regular user, not as root"
exit 1
fi
success "Running as regular user"
step "Checking git..."
if ! command -v git &>/dev/null; then
error "git is not installed. Run: sudo pacman -S git"
exit 1
fi
success "git found"
# Package list
section "Packages"
if [ "$DRY_RUN" = true ]; then
echo ""
printf " ${DIM}%s${RESET}\n" "${PACKAGES[@]}"
echo ""
info "Dry run — no packages will be installed"
exit 0
fi
info "Total: ${#PACKAGES[@]} packages"
if ! confirm "Proceed with installation?"; then
warn "Cancelled"
exit 0
fi
# Sudo
section "Permissions"
info "Some packages require root privileges"
if ! sudo -v 2>/dev/null; then
error "Sudo authentication failed"
exit 1
fi
success "Sudo authenticated"
while true; do
sudo -n true
sleep 60
kill -0 "$$" || exit
done 2>/dev/null &
SUDO_KEEPER_PID=$!
# AUR helper
section "AUR helper"
AUR=""
if command -v paru &>/dev/null; then
AUR="paru"
success "Using paru"
elif command -v yay &>/dev/null; then
AUR="yay"
success "Using yay"
else
step "Installing yay-bin..."
tmpdir=$(mktemp -d)
(
git clone --quiet --depth=1 https://aur.archlinux.org/yay-bin.git "$tmpdir/yay-bin"
cd "$tmpdir/yay-bin"
makepkg -si --noconfirm >/dev/null 2>&1
) &
spinner $! "Building yay-bin"
wait $! || {
error "Failed to install yay-bin"
rm -rf "$tmpdir"
exit 1
}
rm -rf "$tmpdir"
AUR="yay"
success "yay-bin installed"
fi
# Repository
section "Repository"
if [ -d "$INSTALL_DIR" ]; then
step "Pulling latest changes..."
git -C "$INSTALL_DIR" pull --quiet 2>/dev/null || true
success "Repository updated"
else
step "Cloning repository..."
git clone --quiet "$REPO_URL" "$INSTALL_DIR" 2>/dev/null || true
success "Repository cloned"
fi
info "${INSTALL_DIR}"
# Install packages
section "Installing packages"
step "Syncing databases..."
$AUR -Syy --noconfirm >/dev/null 2>&1 || true
success "Databases synced"
installed=0
failed=0
failed_pkgs=()
total=${#PACKAGES[@]}
for pkg in "${PACKAGES[@]}"; do
if pacman -Qi "$pkg" &>/dev/null; then
installed=$((installed + 1))
elif $AUR -S --needed --noconfirm "$pkg" >/dev/null 2>&1; then
installed=$((installed + 1))
else
failed=$((failed + 1))
failed_pkgs+=("$pkg")
fi
pct=$((installed * 100 / total))
printf "\r ${CYAN}${ARROW}${RESET} [%-50s] %3d%% (%d/%d)" "$(printf '#%.0s' $(seq 1 $((pct / 2))))" "$pct" "$installed" "$total"
done
echo ""
if [ $failed -eq 0 ]; then
success "All ${installed} packages installed"
else
warn "${failed} package(s) failed:"
for pkg in "${failed_pkgs[@]}"; do
echo -e " ${RED}${CROSS}${RESET} ${pkg}"
done
fi
# Build native modules
section "Building native modules"
# Global Menu shim
step "Compiling libmenu_button_shim.so..."
SHIM_SRC="$INSTALL_DIR/src/window/globalmenu/libmenu_button_shim.c"
SHIM_OUT="$INSTALL_DIR/src/window/globalmenu/libmenu_button_shim.so"
if [ -f "$SHIM_SRC" ]; then
if PKG_OUTPUT=$(pkg-config --cflags --libs gtk+-3.0 2>/dev/null); then
read -r -a PKG_FLAGS <<< "$PKG_OUTPUT"
if OUTPUT=$(gcc \
-shared \
-fPIC \
-O2 \
-o "$SHIM_OUT" \
"$SHIM_SRC" \
"${PKG_FLAGS[@]}" \
-ldl \
2>&1); then
success "libmenu_button_shim.so built"
else
warn "Failed to compile shim — global menu may not work"
echo
echo "──────────────── Compiler Output ────────────────"
echo "$OUTPUT"
echo "─────────────────────────────────────────────────"
echo
fi
else
warn "pkg-config could not find gtk+-3.0"
fi
else
warn "Shim source not found — skipped"
fi
# App Capture
step "Compiling libappcapture.so..."
if [ -d "$INSTALL_DIR/src/window/switcher/app-capture" ]; then
if (
cd "$INSTALL_DIR/src/window/switcher/app-capture"
meson setup builddir --wipe >/dev/null 2>&1 || meson setup builddir >/dev/null 2>&1
meson compile -C builddir >/dev/null 2>&1
); then
success "libappcapture.so built"
else
warn "Failed to compile app-capture"
fi
else
warn "App-capture source not found — skipped"
fi
# Hyprland config
section "Hyprland configuration"
HYPR_CONFIG="$HOME/.config/hypr/hyprland.lua"
MODUS_MODULE_LINE="dofile(\"$INSTALL_DIR/config/hypr/modus.lua\")"
if [ -f "$HYPR_CONFIG" ]; then
if grep -q "modus.lua" "$HYPR_CONFIG"; then
sed -i "s|-- *dofile.*modus.lua.*|$MODUS_MODULE_LINE|" "$HYPR_CONFIG"
success "Modus module configured in hyprland.lua"
else
step "Adding Modus module to hyprland.lua..."
{
echo ""
echo "-- Modus configuration"
echo "$MODUS_MODULE_LINE"
} >> "$HYPR_CONFIG"
success "Module added"
fi
else
warn "hyprland.lua not found at ${HYPR_CONFIG}"
info "Add manually: ${MODUS_MODULE_LINE}"
fi
step "Symlinking hypridle.conf..."
HYPR_DIR="$HOME/.config/hypr"
HYPRIDLE_CONF="$INSTALL_DIR/config/hypr/hypridle.conf"
HYPRIDLE_TARGET="$HYPR_DIR/hypridle.conf"
mkdir -p "$HYPR_DIR"
if [ -f "$HYPRIDLE_CONF" ]; then
# Backup existing file if it isn't already our symlink
if [ -e "$HYPRIDLE_TARGET" ] && [ ! -L "$HYPRIDLE_TARGET" ]; then
BACKUP="${HYPRIDLE_TARGET}.bak.$(date +%Y%m%d-%H%M%S)"
step "Backing up existing hypridle.conf..."
mv "$HYPRIDLE_TARGET" "$BACKUP"
success "Backup created: $BACKUP"
elif [ -L "$HYPRIDLE_TARGET" ]; then
info "Existing hypridle.conf is already a symlink"
fi
ln -sfn "$HYPRIDLE_CONF" "$HYPRIDLE_TARGET"
success "hypridle.conf symlinked"
else
warn "hypridle.conf not found — skipped"
fi
# Environment
section "Global environment"
APP_CONF_DIR="$HOME/.config/environment.d"
APP_CONF_FILE="$APP_CONF_DIR/appmenu.conf"
step "Writing environment.d config..."
mkdir -p "$APP_CONF_DIR"
cat > "$APP_CONF_FILE" << 'EOF'
GTK_MODULES=appmenu-gtk-module
UBUNTU_MENUPROXY=1
EOF
success "environment.d/appmenu.conf written"
PAM_FILE="$HOME/.pam_environment"
step "Updating pam_environment..."
for entry in "GTK_MODULES DEFAULT=appmenu-gtk-module" "UBUNTU_MENUPROXY DEFAULT=1"; do
var_name="${entry%% *}"
if ! grep -q "^${var_name} " "$PAM_FILE" 2>/dev/null; then
echo "$entry" >> "$PAM_FILE"
fi
done
success "pam_environment updated"
# Matugen
section "Matugen"
MATUGEN_CONFIG="$HOME/.config/matugen/config.toml"
MODUS_BLOCK='[templates.modus]
input_path = "~/.config/Modus/config/matugen/templates/modus.css"
output_path = "~/.config/Modus/src/styles/colors.css"
post_hook = "fabric-cli exec modus '\''app.set_css()'\'' &"'
HYPR_BLOCK='[templates.hyprland]
input_path = "~/.config/Modus/config/matugen/templates/hyprland-colors.lua"
output_path = "~/.config/Modus/config/matugen/colors.lua"'
if [ -f "$MATUGEN_CONFIG" ]; then
if grep -qF "[templates.modus]" "$MATUGEN_CONFIG"; then
success "Modus template already configured"
else
step "Adding Modus template..."
echo "" >> "$MATUGEN_CONFIG"
echo "$MODUS_BLOCK" >> "$MATUGEN_CONFIG"
success "Modus template added"
fi
if grep -qF "[templates.hyprland]" "$MATUGEN_CONFIG"; then
success "Hyprland template already configured"
else
step "Adding Hyprland template..."
echo "" >> "$MATUGEN_CONFIG"
echo "$HYPR_BLOCK" >> "$MATUGEN_CONFIG"
success "Hyprland template added"
fi
else
warn "matugen config not found at ${MATUGEN_CONFIG}"
info "Install matugen first, then re-run this script"
fi
# Launch
section "Launch"
step "Stopping existing instances..."
if killall modus 2>/dev/null || pkill -f "start.py" 2>/dev/null; then
success "Stopped running instance"
sleep 1
else
info "No existing instance found"
fi
step "Installing Python dependencies..."
if (cd "$INSTALL_DIR" && uv sync); then
success "Python dependencies installed"
else
warn "uv sync failed — Modus may not start"
fi
step "Starting Modus..."
hyprctl reload
nohup bash -c "cd \"$INSTALL_DIR\" && uv run python start.py" >/dev/null 2>&1 &
MODUS_PID=$!
MODUS_PID=$!
sleep 3
if kill -0 "$MODUS_PID" 2>/dev/null; then
success "Modus started (PID: $MODUS_PID)"
else
warn "Modus exited during startup"
echo
fi
# Summary
echo ""
echo -e " ${GREEN}${BOLD}╔══════════════════════════════════════════╗${RESET}"
echo -e " ${GREEN}${BOLD}║${RESET} ${GREEN}${BOLD}║${RESET}"
echo -e " ${GREEN}${BOLD}║${RESET} ${GREEN}${BOLD}Installation complete!${RESET} ${GREEN}${BOLD}║${RESET}"
echo -e " ${GREEN}${BOLD}║${RESET} ${GREEN}${BOLD}║${RESET}"
echo -e " ${GREEN}${BOLD}╚══════════════════════════════════════════╝${RESET}"
echo ""
divider
info "Packages: ${GREEN}${installed}${RESET} installed${RED:+, ${failed} failed}"
info "Location: ${INSTALL_DIR}"
info "Config: ${INSTALL_DIR}/config/"
divider
echo ""