#!/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."