Update install script to reload hyprctl and launch Python service; adjust pyproject.toml version and metadata
549 lines
14 KiB
Bash
Executable File
549 lines
14 KiB
Bash
Executable File
#!/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
|
||
apple-fonts
|
||
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
|
||
}
|
||
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 $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 -qF "$MODUS_MODULE_LINE" "$HYPR_CONFIG"; then
|
||
success "Modus module already 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; then
|
||
success "Stopped running instance"
|
||
sleep 1
|
||
else
|
||
info "No existing instance found"
|
||
fi
|
||
|
||
step "Starting Modus..."
|
||
hyprctl reload
|
||
uv run python "$INSTALL_DIR/start.py" &
|
||
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 ""
|