Files
arch/scripts/99-apps.sh
2026-03-31 20:13:15 +08:00

496 lines
17 KiB
Bash

#!/bin/bash
# ==============================================================================
# 99-apps.sh - Common Applications (FZF Menu + Split Repo/AUR + Retry Logic)
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/00-utils.sh"
# --- [CONFIGURATION] ---
# LazyVim 硬性依赖列表 (Moved from niri-setup)
LAZYVIM_DEPS=("neovim" "ripgrep" "fd" "ttf-jetbrains-mono-nerd" "git")
check_root
# Ensure FZF is installed
if ! command -v fzf &> /dev/null; then
log "Installing dependency: fzf..."
pacman -S --noconfirm fzf >/dev/null 2>&1
fi
trap 'echo -e "\n ${H_YELLOW}>>> Operation cancelled by user (Ctrl+C). Skipping...${NC}"' INT
# ------------------------------------------------------------------------------
# 0. Identify Target User & Helper
# ------------------------------------------------------------------------------
section "Phase 5" "Common Applications"
log "Identifying target user..."
DETECTED_USER=$(awk -F: '$3 == 1000 {print $1}' /etc/passwd)
if [ -n "$DETECTED_USER" ]; then
TARGET_USER="$DETECTED_USER"
else
read -p " Please enter the target username: " TARGET_USER
fi
HOME_DIR="/home/$TARGET_USER"
info_kv "Target" "$TARGET_USER"
# Helper function for user commands
as_user() {
runuser -u "$TARGET_USER" -- "$@"
}
# ------------------------------------------------------------------------------
# 1. List Selection & User Prompt
# ------------------------------------------------------------------------------
if [ "$DESKTOP_ENV" == "kde" ]; then
LIST_FILENAME="kde-common-applist.txt"
else
LIST_FILENAME="common-applist.txt"
fi
LIST_FILE="$PARENT_DIR/$LIST_FILENAME"
REPO_APPS=()
AUR_APPS=()
FLATPAK_APPS=()
FAILED_PACKAGES=()
INSTALL_LAZYVIM=false
if [ ! -f "$LIST_FILE" ]; then
warn "File $LIST_FILENAME not found. Skipping."
trap - INT
exit 0
fi
if ! grep -q -vE "^\s*#|^\s*$" "$LIST_FILE"; then
warn "App list is empty. Skipping."
trap - INT
exit 0
fi
echo ""
echo -e " Selected List: ${BOLD}$LIST_FILENAME${NC}"
echo -e " ${H_YELLOW}>>> Do you want to CUSTOMIZE the application installation?${NC}"
echo ""
read -t 60 -p " Please select[Y/n]: " choice
READ_STATUS=$?
SELECTED_RAW=""
# Case 1: Timeout (Auto Install ALL - Default to N)
if [ $READ_STATUS -ne 0 ]; then
echo ""
warn "Timeout reached (60s). Auto-installing ALL applications from list..."
SELECTED_RAW=$(grep -vE "^\s*#|^\s*$" "$LIST_FILE" | sed -E 's/[[:space:]]+#/\t#/')
# Case 2: User Input
else
# Enter defaults to Y
choice=${choice:-Y}
if [[ "$choice" =~ ^[nN]$ ]]; then
log "User chose to auto-install ALL applications without customization."
SELECTED_RAW=$(grep -vE "^\s*#|^\s*$" "$LIST_FILE" | sed -E 's/[[:space:]]+#/\t#/')
else
clear
echo -e "\n Loading application list..."
SELECTED_RAW=$(grep -vE "^\s*#|^\s*$" "$LIST_FILE" | \
sed -E 's/[[:space:]]+#/\t#/' | \
fzf --multi \
--layout=reverse \
--border \
--margin=1,2 \
--prompt="Search App > " \
--pointer=">>" \
--marker="* " \
--delimiter=$'\t' \
--with-nth=1 \
--bind 'load:select-all' \
--bind 'ctrl-a:select-all,ctrl-d:deselect-all,j:down,k:up' \
--info=inline \
--header="[TAB] TOGGLE | [ENTER] INSTALL | [CTRL-D] DE-ALL | [CTRL-A] SE-ALL" \
--preview "echo {} | cut -f2 -d$'\t' | sed 's/^# //'" \
--preview-window=down:45%:wrap:border-up \
--color=dark \
--color=fg+:white,bg+:black \
--color=hl:blue,hl+:blue:bold \
--color=header:yellow:bold \
--color=info:magenta \
--color=prompt:cyan,pointer:cyan:bold,marker:green:bold \
--color=spinner:yellow)
clear
if [ -z "$SELECTED_RAW" ]; then
log "Skipping application installation (User cancelled selection in FZF)."
trap - INT
exit 0
fi
fi
fi
# ------------------------------------------------------------------------------
# 2. Categorize Selection & Strip Prefixes (Includes LazyVim Check)
# ------------------------------------------------------------------------------
log "Processing selection..."
while IFS= read -r line; do
raw_pkg=$(echo "$line" | cut -f1 -d$'\t' | xargs)
[[ -z "$raw_pkg" ]] && continue
# Check for LazyVim explicitly (Case insensitive check)
if [[ "${raw_pkg,,}" == "lazyvim" ]]; then
INSTALL_LAZYVIM=true
REPO_APPS+=("${LAZYVIM_DEPS[@]}")
info_kv "Config" "LazyVim detected" "Setup deferred to Post-Install"
continue
fi
if [[ "$raw_pkg" == flatpak:* ]]; then
clean_name="${raw_pkg#flatpak:}"
FLATPAK_APPS+=("$clean_name")
elif [[ "$raw_pkg" == AUR:* ]]; then
clean_name="${raw_pkg#AUR:}"
AUR_APPS+=("$clean_name")
else
REPO_APPS+=("$raw_pkg")
fi
done <<< "$SELECTED_RAW"
info_kv "Scheduled" "Repo: ${#REPO_APPS[@]}" "AUR: ${#AUR_APPS[@]}" "Flatpak: ${#FLATPAK_APPS[@]}"
# ------------------------------------------------------------------------------
#[SETUP] GLOBAL SUDO CONFIGURATION
# ------------------------------------------------------------------------------
if [ ${#REPO_APPS[@]} -gt 0 ] || [ ${#AUR_APPS[@]} -gt 0 ]; then
log "Configuring temporary NOPASSWD for installation..."
SUDO_TEMP_FILE="/etc/sudoers.d/99_shorin_installer_apps"
echo "$TARGET_USER ALL=(ALL) NOPASSWD: ALL" > "$SUDO_TEMP_FILE"
chmod 440 "$SUDO_TEMP_FILE"
fi
# ------------------------------------------------------------------------------
# 3. Install Applications
# ------------------------------------------------------------------------------
# --- A. Install Repo Apps (BATCH MODE) ---
if [ ${#REPO_APPS[@]} -gt 0 ]; then
section "Step 1/3" "Official Repository Packages (Batch)"
REPO_QUEUE=()
for pkg in "${REPO_APPS[@]}"; do
if pacman -Qi "$pkg" &>/dev/null; then
log "Skipping '$pkg' (Already installed)."
else
REPO_QUEUE+=("$pkg")
fi
done
if [ ${#REPO_QUEUE[@]} -gt 0 ]; then
BATCH_LIST="${REPO_QUEUE[*]}"
info_kv "Installing" "${#REPO_QUEUE[@]} packages via Pacman/Yay"
if ! exe as_user yay -Syu --noconfirm --needed --answerdiff=None --answerclean=None $BATCH_LIST; then
error "Batch installation failed. Some repo packages might be missing."
for pkg in "${REPO_QUEUE[@]}"; do
FAILED_PACKAGES+=("repo:$pkg")
done
else
success "Repo batch installation completed."
fi
else
log "All Repo packages are already installed."
fi
fi
# --- B. Install AUR Apps (INDIVIDUAL MODE + RETRY) ---
if [ ${#AUR_APPS[@]} -gt 0 ]; then
section "Step 2/3" "AUR Packages "
for app in "${AUR_APPS[@]}"; do
if pacman -Qi "$app" &>/dev/null; then
log "Skipping '$app' (Already installed)."
continue
fi
log "Installing AUR: $app ..."
install_success=false
max_retries=1
for (( i=0; i<=max_retries; i++ )); do
if [ $i -gt 0 ]; then
warn "Retry $i/$max_retries for '$app' ..."
fi
if as_user yay -Syu --noconfirm --needed --answerdiff=None --answerclean=None "$app"; then
install_success=true
success "Installed $app"
break
else
warn "Attempt $((i+1)) failed for $app"
fi
done
if [ "$install_success" = false ]; then
error "Failed to install $app after $((max_retries+1)) attempts."
FAILED_PACKAGES+=("aur:$app")
fi
done
fi
# --- C. Install Flatpak Apps (INDIVIDUAL MODE) ---
if [ ${#FLATPAK_APPS[@]} -gt 0 ]; then
section "Step 3/3" "Flatpak Packages (Individual)"
for app in "${FLATPAK_APPS[@]}"; do
if flatpak info "$app" &>/dev/null; then
log "Skipping '$app' (Already installed)."
continue
fi
log "Installing Flatpak: $app ..."
if ! exe flatpak install -y flathub "$app"; then
error "Failed to install: $app"
FAILED_PACKAGES+=("flatpak:$app")
else
success "Installed $app"
fi
done
fi
# ------------------------------------------------------------------------------
# 4. Environment & Additional Configs (Virt/Wine/Steam/LazyVim)
# ------------------------------------------------------------------------------
section "Post-Install" "System & App Tweaks"
# --- [NEW] Virtualization Configuration (Virt-Manager) ---
if pacman -Qi virt-manager &>/dev/null && ! systemd-detect-virt -q; then
info_kv "Config" "Virt-Manager detected"
# 1. 安装完整依赖
# iptables-nft 和 dnsmasq 是默认 NAT 网络必须的
log "Installing QEMU/KVM dependencies..."
pacman -S --noconfirm --needed qemu-full virt-manager swtpm dnsmasq virt-viewer
# 2. 添加用户组 (需要重新登录生效)
log "Adding $TARGET_USER to libvirt group..."
usermod -a -G libvirt "$TARGET_USER"
# 同时添加 kvm 和 input 组以防万一
usermod -a -G kvm,input "$TARGET_USER"
# 3. 开启服务
log "Enabling libvirtd service..."
systemctl enable --now libvirtd
# 4. [修复] 强制设置 virt-manager 默认连接为 QEMU/KVM
# 解决第一次打开显示 LXC 或无法连接的问题
log "Setting default URI to qemu:///system..."
# 编译 glib schemas (防止 gsettings 报错)
glib-compile-schemas /usr/share/glib-2.0/schemas/
# 强制写入 Dconf 配置
# uris: 连接列表
# autoconnect: 自动连接的列表
as_user gsettings set org.virt-manager.virt-manager.connections uris "['qemu:///system']"
as_user gsettings set org.virt-manager.virt-manager.connections autoconnect "['qemu:///system']"
# 5. 配置网络 (Default NAT)
log "Starting default network..."
sleep 3
virsh net-start default >/dev/null 2>&1 || warn "Default network might be already active."
virsh net-autostart default >/dev/null 2>&1 || true
success "Virtualization (KVM) configured."
fi
# --- [NEW] Wine Configuration & Fonts ---
if command -v wine &>/dev/null; then
info_kv "Config" "Wine detected"
# 1. 安装 Gecko 和 Mono
log "Ensuring Wine Gecko/Mono are installed..."
pacman -S --noconfirm --needed wine wine-gecko wine-mono
# 2. 初始化 Wine (使用 wineboot -u 在后台运行,不弹窗)
WINE_PREFIX="$HOME_DIR/.wine"
if [ ! -d "$WINE_PREFIX" ]; then
log "Initializing wine prefix (This may take a minute)..."
# WINEDLLOVERRIDES prohibits popups
as_user env WINEDLLOVERRIDES="mscoree,mshtml=" wineboot -u
# Wait for completion
as_user wineserver -w
else
log "Wine prefix already exists."
fi
# 3. 复制字体
FONT_SRC="$PARENT_DIR/resources/windows-sim-fonts"
FONT_DEST="$WINE_PREFIX/drive_c/windows/Fonts"
if [ -d "$FONT_SRC" ]; then
log "Copying Windows fonts from resources..."
# 1. 确保目标目录存在 (以用户身份创建)
if [ ! -d "$FONT_DEST" ]; then
as_user mkdir -p "$FONT_DEST"
fi
# 2. 执行复制 (关键修改:直接以目标用户身份复制,而不是 Root 复制后再 Chown)
# 使用 cp -rT 确保目录内容合并,而不是把源目录本身拷进去
# 注意:这里假设 as_user 能够接受命令参数。如果 as_user 只是简单的 su/sudo 封装:
if sudo -u "$TARGET_USER" cp -rf "$FONT_SRC"/. "$FONT_DEST/"; then
success "Fonts copied successfully."
else
error "Failed to copy fonts."
fi
# 3. 强制刷新 Wine 字体缓存 (非常重要!)
# 字体文件放进去了,但 Wine 不一定会立刻重修构建 fntdata.dat
# 杀死 wineserver 会强制 Wine 下次启动时重新扫描系统和本地配置
log "Refreshing Wine font cache..."
if command -v wineserver &> /dev/null; then
# 必须以目标用户身份执行 wineserver -k
as_user env WINEPREFIX="$WINE_PREFIX" wineserver -k
fi
success "Wine fonts installed and cache refresh triggered."
else
warn "Resources font directory not found at: $FONT_SRC"
fi
fi
if command -v lutris &> /dev/null; then
log "Lutris detected. Installing 32-bit gaming dependencies..."
pacman -S --noconfirm --needed alsa-plugins giflib glfw gst-plugins-base-libs lib32-alsa-plugins lib32-giflib lib32-gst-plugins-base-libs lib32-gtk3 lib32-libjpeg-turbo lib32-libva lib32-mpg123 lib32-openal libjpeg-turbo libva libxslt mpg123 openal ttf-liberation
fi
# --- Steam Locale Fix ---
STEAM_desktop_modified=false
NATIVE_DESKTOP="/usr/share/applications/steam.desktop"
if [ -f "$NATIVE_DESKTOP" ]; then
log "Checking Native Steam..."
if ! grep -q "env LC_CTYPE=zh_CN.UTF-8" "$NATIVE_DESKTOP"; then
exe sed -i 's|^Exec=/usr/bin/steam|Exec=env LC_CTYPE=zh_CN.UTF-8 /usr/bin/steam|' "$NATIVE_DESKTOP"
exe sed -i 's|^Exec=steam|Exec=env LC_CTYPE=zh_CN.UTF-8 steam|' "$NATIVE_DESKTOP"
success "Patched Native Steam .desktop."
STEAM_desktop_modified=true
else
log "Native Steam already patched."
fi
fi
if flatpak list | grep -q "com.valvesoftware.Steam"; then
log "Checking Flatpak Steam..."
exe flatpak override --env=LANG=zh_CN.UTF-8 com.valvesoftware.Steam
success "Applied Flatpak Steam override."
STEAM_desktop_modified=true
fi
# --- [MOVED] LazyVim Configuration ---
if [ "$INSTALL_LAZYVIM" = true ]; then
section "Config" "Applying LazyVim Overrides"
NVIM_CFG="$HOME_DIR/.config/nvim"
if [ -d "$NVIM_CFG" ]; then
BACKUP_PATH="$HOME_DIR/.config/nvim.old.apps.$(date +%s)"
warn "Collision detected. Moving existing nvim config to $BACKUP_PATH"
mv "$NVIM_CFG" "$BACKUP_PATH"
fi
log "Cloning LazyVim starter..."
if as_user git clone https://github.com/LazyVim/starter "$NVIM_CFG"; then
rm -rf "$NVIM_CFG/.git"
success "LazyVim installed (Override)."
else
error "Failed to clone LazyVim."
fi
fi
# --- hide desktop ---
# --- hide desktop (User Level Override) ---
section "Config" "Hiding useless .desktop files"
log "Hiding useless .desktop files"
run_hide_desktop_file
# --- Post-Dotfiles Configuration: Firefox ---
section "Config" "Firefox UI Customization"
MOZILLA_DIR="$HOME_DIR/.config/mozilla"
if [ -d "$MOZILLA_DIR" ]; then
log "Backing up existing mozilla directory..."
mv "$MOZILLA_DIR" "$MOZILLA_DIR.bak.$(date +%s)"
fi
if mkdir -p "$MOZILLA_DIR"; then
log "directory created."
fi
if cp -rf "$PARENT_DIR/resources/firefox" "$MOZILLA_DIR/"; then
chown -R "$TARGET_USER" "$MOZILLA_DIR"
log "firefox dotfiles doployed."
fi
section "Config" "clash tun"
if command -v clash-verge; then
/usr/bin/clash-verge-service &
sleep 3
clash-verge-service-uninstall || true
sleep 3
clash-verge-service-install || true
fi
# --- mangohud ---
section "Config" "MangoHud Configuration"
if command -v mangohud &>/dev/null; then
if [ -d "$HOME_DIR/.config/MangoHud" ]; then
log "MangoHud config already exists."
else
log "Deploying MangoHud config..."
force_copy "$PARENT_DIR/resources/MangoHud" "$HOME_DIR/.config/MangoHud"
fi
fi
# ------------------------------------------------------------------------------
# [FIX] CLEANUP GLOBAL SUDO CONFIGURATION
# ------------------------------------------------------------------------------
if [ -f "$SUDO_TEMP_FILE" ]; then
log "Revoking temporary NOPASSWD..."
rm -f "$SUDO_TEMP_FILE"
fi
# ------------------------------------------------------------------------------
# 5. Generate Failure Report
# ------------------------------------------------------------------------------
if [ ${#FAILED_PACKAGES[@]} -gt 0 ]; then
DOCS_DIR="$HOME_DIR/Documents"
REPORT_FILE="$DOCS_DIR/安装失败的软件.txt"
if [ ! -d "$DOCS_DIR" ]; then as_user mkdir -p "$DOCS_DIR"; fi
echo -e "\n========================================================" >> "$REPORT_FILE"
echo -e " Installation Failure Report - $(date)" >> "$REPORT_FILE"
echo -e "========================================================" >> "$REPORT_FILE"
printf "%s\n" "${FAILED_PACKAGES[@]}" >> "$REPORT_FILE"
chown "$TARGET_USER:$TARGET_USER" "$REPORT_FILE"
echo ""
warn "Some applications failed to install."
warn "A report has been saved to:"
echo -e " ${BOLD}$REPORT_FILE${NC}"
else
success "All scheduled applications processed successfully."
fi
# Reset Trap
trap - INT
log "Module 99-apps completed."