Files
arch/scripts/04-niri-setup.sh

617 lines
22 KiB
Bash
Raw Permalink Normal View History

2026-03-31 20:13:15 +08:00
#!/bin/bash
# ==============================================================================
# 04-niri-setup.sh - Niri Desktop (Restored FZF & Robust AUR)
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/00-utils.sh"
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST"
DEBUG=${DEBUG:-0}
CN_MIRROR=${CN_MIRROR:-0}
UNDO_SCRIPT="$SCRIPT_DIR/de-undochange.sh"
check_root
# --- [HELPER FUNCTIONS] ---
# 2. Critical Failure Handler (The "Big Red Box")
# 2. Critical Failure Handler (The "Big Red Box")
critical_failure_handler() {
local failed_reason="$1"
trap - ERR
echo ""
echo -e "\033[0;31m################################################################\033[0m"
echo -e "\033[0;31m# #\033[0m"
echo -e "\033[0;31m# CRITICAL INSTALLATION FAILURE DETECTED #\033[0m"
echo -e "\033[0;31m# #\033[0m"
echo -e "\033[0;31m# Reason: $failed_reason\033[0m"
echo -e "\033[0;31m# #\033[0m"
echo -e "\033[0;31m# OPTIONS: #\033[0m"
echo -e "\033[0;31m# 1. Restore snapshot (Undo changes & Exit) #\033[0m"
echo -e "\033[0;31m# 2. Retry / Re-run script #\033[0m"
echo -e "\033[0;31m# 3. Abort (Exit immediately) #\033[0m"
echo -e "\033[0;31m# #\033[0m"
echo -e "\033[0;31m################################################################\033[0m"
echo ""
while true; do
read -p "Select an option [1-3]: " -r choice
case "$choice" in
1)
# Option 1: Restore Snapshot
if [ -f "$UNDO_SCRIPT" ]; then
warn "Executing recovery script..."
bash "$UNDO_SCRIPT"
exit 1
else
error "Recovery script missing! You are on your own."
exit 1
fi
;;
2)
# Option 2: Re-run Script
warn "Restarting installation script..."
echo "-----------------------------------------------------"
sleep 1
exec "$0" "$@"
;;
3)
# Option 3: Exit
warn "User chose to abort."
warn "Please fix the issue manually before re-running."
error "Installation aborted."
exit 1
;;
*)
echo "Invalid input. Please enter 1, 2, or 3."
;;
esac
done
}
# 3. Robust Package Installation with Retry Loop
ensure_package_installed() {
local pkg="$1"
local context="$2" # e.g., "Repo" or "AUR"
local max_attempts=3
local attempt=1
local install_success=false
# 1. Check if already installed
if pacman -Q "$pkg" &>/dev/null; then
return 0
fi
# 2. Retry Loop
while [ $attempt -le $max_attempts ]; do
if [ $attempt -gt 1 ]; then
warn "Retrying '$pkg' ($context)... (Attempt $attempt/$max_attempts)"
sleep 3 # Cooldown
else
log "Installing '$pkg' ($context)..."
fi
# Try installation
if as_user yay -S --noconfirm --needed --answerdiff=None --answerclean=None "$pkg"; then
install_success=true
break
else
warn "Attempt $attempt/$max_attempts failed for '$pkg'."
fi
((attempt++))
done
# 3. Final Verification
if [ "$install_success" = true ] && pacman -Q "$pkg" &>/dev/null; then
success "Installed '$pkg'."
else
critical_failure_handler "Failed to install '$pkg' after $max_attempts attempts."
fi
}
section "Phase 4" "Niri Desktop Environment"
# ==============================================================================
# STEP 0: Safety Checkpoint
# ==============================================================================
# Enable Trap
trap 'critical_failure_handler "Script Error at Line $LINENO"' ERR
# ==============================================================================
# STEP 1: Identify User & DM Check
# ==============================================================================
detect_target_user
info_kv "Target" "$TARGET_USER"
# DM Check
check_dm_conflict
# ==============================================================================
# STEP 2: Core Components
# ==============================================================================
section "Step 1/9" "Core Components"
PKGS="niri xdg-desktop-portal-gnome fuzzel kitty firefox libnotify mako polkit-gnome"
# 記錄到清單
echo "$PKGS" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed $PKGS
log "Configuring Firefox Policies..."
POL_DIR="/etc/firefox/policies"
exe mkdir -p "$POL_DIR"
cat << 'EOF' > "$POL_DIR/policies.json"
{
"policies": {
"Extensions": {
"Install": [
"https://addons.mozilla.org/firefox/downloads/latest/pywalfox/latest.xpi",
"https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"
]
}
}
}
EOF
exe chmod 755 "$POL_DIR" && exe chmod 644 "$POL_DIR/policies.json"
# ==============================================================================
# STEP 3: File Manager
# ==============================================================================
section "Step 2/9" "File Manager"
FM_PKGS1="ffmpegthumbnailer gvfs-smb nautilus-open-any-terminal file-roller gnome-keyring gst-plugins-base gst-plugins-good gst-libav nautilus"
FM_PKGS2="xdg-desktop-portal-gtk thunar tumbler ffmpegthumbnailer poppler-glib gvfs-smb file-roller thunar-archive-plugin gnome-keyring thunar-volman gvfs-mtp gvfs-gphoto2 webp-pixbuf-loader libgsf"
# 記錄到清單
echo "$FM_PKGS1" >> "$VERIFY_LIST"
echo "$FM_PKGS2" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed $FM_PKGS1
exe pacman -S --noconfirm --needed $FM_PKGS2
# 默认终端处理
echo "xdg-terminal-exec" >> "$VERIFY_LIST"
exe as_user paru -S --noconfirm --needed xdg-terminal-exec
if ! grep -q "kitty" "$HOME_DIR/.config/xdg-terminals.list"; then
echo 'kitty.desktop' >> "$HOME_DIR/.config/xdg-terminals.list"
fi
# if [ ! -f /usr/local/bin/gnome-terminal ] || [ -L /usr/local/bin/gnome-terminal ]; then
# exe ln -sf /usr/bin/kitty /usr/local/bin/gnome-terminal
# fi
sudo -u "$TARGET_USER" dbus-run-session gsettings set com.github.stunkymonkey.nautilus-open-any-terminal terminal kitty
# Nautilus Nvidia/Input Fix
configure_nautilus_user
section "Step 3/9" "Temp sudo file"
SUDO_TEMP_FILE="/etc/sudoers.d/99_shorin_installer_temp"
echo "$TARGET_USER ALL=(ALL) NOPASSWD: ALL" >"$SUDO_TEMP_FILE"
chmod 440 "$SUDO_TEMP_FILE"
log "Temp sudo file created..."
# ==============================================================================
# STEP 5: Dependencies (RESTORED FZF)
# ==============================================================================
section "Step 4/9" "Dependencies"
LIST_FILE="$PARENT_DIR/niri-applist.txt"
# Ensure tools
command -v fzf &>/dev/null || pacman -S --noconfirm fzf >/dev/null 2>&1
if [ -f "$LIST_FILE" ]; then
mapfile -t DEFAULT_LIST < <(grep -vE "^\s*#|^\s*$" "$LIST_FILE" | sed 's/#.*//; s/AUR://g' | xargs -n1)
if [ ${#DEFAULT_LIST[@]} -eq 0 ]; then
warn "App list is empty. Skipping."
PACKAGE_ARRAY=()
else
echo -e "\n ${H_YELLOW}>>> Default installation in 60s. Press ANY KEY to customize...${NC}"
if read -t 60 -n 1 -s -r; then
# --- [RESTORED] Original FZF Selection Logic ---
clear
log "Loading package list..."
SELECTED_LINES=$(grep -vE "^\s*#|^\s*$" "$LIST_FILE" |
sed -E 's/[[:space:]]+#/\t#/' |
fzf --multi \
--layout=reverse \
--border \
--margin=1,2 \
--prompt="Search Pkg > " \
--pointer=">>" \
--marker="* " \
--delimiter=$'\t' \
--with-nth=1 \
--bind 'load:select-all' \
--bind 'ctrl-a:select-all,ctrl-d:deselect-all' \
--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:50%:wrap \
--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_LINES" ]; then
warn "User cancelled selection. Installing NOTHING."
PACKAGE_ARRAY=()
else
PACKAGE_ARRAY=()
while IFS= read -r line; do
raw_pkg=$(echo "$line" | cut -f1 -d$'\t' | xargs)
clean_pkg="${raw_pkg#AUR:}"
[ -n "$clean_pkg" ] && PACKAGE_ARRAY+=("$clean_pkg")
done <<<"$SELECTED_LINES"
fi
# -----------------------------------------------
else
log "Auto-confirming ALL packages."
PACKAGE_ARRAY=("${DEFAULT_LIST[@]}")
fi
fi
# --- Installation Loop ---
if [ ${#PACKAGE_ARRAY[@]} -gt 0 ]; then
BATCH_LIST=()
AUR_LIST=()
info_kv "Target" "${#PACKAGE_ARRAY[@]} packages scheduled."
# 記錄到清單 (將陣列展開並寫入)
echo "${PACKAGE_ARRAY[@]}" >> "$VERIFY_LIST"
for pkg in "${PACKAGE_ARRAY[@]}"; do
[ "$pkg" == "imagemagic" ] && pkg="imagemagick"
[[ "$pkg" == "AUR:"* ]] && AUR_LIST+=("${pkg#AUR:}") || BATCH_LIST+=("$pkg")
done
# 1. Batch Install Repo Packages
if [ ${#BATCH_LIST[@]} -gt 0 ]; then
log "Phase 1: Batch Installing Repo Packages..."
as_user yay -Syu --noconfirm --needed --answerdiff=None --answerclean=None "${BATCH_LIST[@]}" || true
# Verify Each
for pkg in "${BATCH_LIST[@]}"; do
ensure_package_installed "$pkg" "Repo"
done
fi
# 2. Sequential AUR Install
if [ ${#AUR_LIST[@]} -gt 0 ]; then
log "Phase 2: Installing AUR Packages (Sequential)..."
for pkg in "${AUR_LIST[@]}"; do
ensure_package_installed "$pkg" "AUR"
done
fi
# Waybar fallback
if ! command -v waybar &>/dev/null; then
warn "Waybar missing. Installing stock..."
exe pacman -S --noconfirm --needed waybar
fi
else
warn "No packages selected."
fi
else
warn "niri-applist.txt not found."
fi
# ==============================================================================
# STEP 6: Dotfiles (Smart Recursive Symlink)
# ==============================================================================
section "Step 5/9" "Deploying Dotfiles"
REPO_GITHUB="https://github.com/SHORiN-KiWATA/Shorin-ArchLinux-Guide.git"
# 1. 仓库位置:放在 .local/share 下,不污染 home 根目录
DOTFILES_REPO="$HOME_DIR/.local/share/shorin-niri"
# --- Smart Linking Function ---
# 核心逻辑:只链接“叶子”节点,对于“容器”目录(.config, .local, share则递归进入
link_recursive() {
local src_dir="$1"
local dest_dir="$2"
local exclude_list="$3"
as_user mkdir -p "$dest_dir"
find "$src_dir" -mindepth 1 -maxdepth 1 -not -path '*/.git*' | while read -r src_path; do
local item_name
item_name=$(basename "$src_path")
# 0. 排除检查
if echo "$exclude_list" | grep -qw "$item_name"; then
log "Skipping excluded: $item_name"
continue
fi
# 1. 判断是否是需要“穿透”的系统目录
local need_recurse=false
if [ "$item_name" == ".config" ]; then
need_recurse=true
elif [ "$item_name" == ".local" ]; then
need_recurse=true
# 只有当父目录名字是以 .local 结尾时,才穿透 share (bin 被移除了)
elif [[ "$src_dir" == *".local" ]] && [ "$item_name" == "share" ]; then
need_recurse=true
fi
if [ "$need_recurse" = true ]; then
# 递归进入:传入当前路径作为新的源和目标
log " Entering container: $item_name"
link_recursive "$src_path" "$dest_dir/$item_name" "$exclude_list"
else
# 2. 具体的配置文件夹/文件(如 fcitx5, niri, .zshrc, .local/bin -> 执行链接
local target_path="$dest_dir/$item_name"
# 先清理旧的目标(无论是文件、文件夹还是死链)
if [ -e "$target_path" ] || [ -L "$target_path" ]; then
log " Overwriting: $item_name"
as_user rm -rf "$target_path"
fi
# 创建软链接
info_kv "Linking" "$item_name -> $dest_dir"
as_user ln -sf "$src_path" "$target_path"
fi
done
}
# --- Execution ---
# 1. 准备仓库
prepare_repository() {
local TARGET_DIRS=("dotfiles" "wallpapers")
local BRANCH_NAME="main"
# --- 1. 检查是否存在旧仓库 ---
if [ -d "$DOTFILES_REPO" ]; then
if ! as_user git -C "$DOTFILES_REPO" rev-parse --is-inside-work-tree &>/dev/null; then
warn "Found incomplete or broken repository folder. Cleaning up..."
rm -rf "$DOTFILES_REPO"
else
log "Repository already exists. Checking for updates..."
if ! as_user git -C "$DOTFILES_REPO" pull origin "$BRANCH_NAME"; then
warn "Update failed (network issue?), cleaning up..."
rm -rf "$DOTFILES_REPO"
fi
fi
fi
# --- 2. 初始化新仓库 ---
if [ ! -d "$DOTFILES_REPO" ]; then
log "Initializing Sparse & Shallow Checkout to $DOTFILES_REPO..."
cd "$HOME_DIR"
# ================= [修改开始:智能安全创建目录] =================
# 逻辑:
# 1. 优先尝试用 as_user (sudo -u) 创建,这是最干净的。
# 2. 如果失败,通常是因为父目录 (.local 或 .local/share) 被 Root 占用了。
# 3. 此时只修正父目录的 Owner (不递归),然后再试。
if ! as_user mkdir -p "$DOTFILES_REPO" 2>/dev/null; then
local parent_dir=$(dirname "$DOTFILES_REPO")
log "User creation failed. Fixing parent permissions: $parent_dir"
# 确保父目录存在,并修正父目录权限 (非递归,瞬间完成)
if [ ! -d "$parent_dir" ]; then
mkdir -p "$parent_dir"
fi
chown "$TARGET_USER:" "$parent_dir"
# 再次尝试以用户身份创建
if ! as_user mkdir -p "$DOTFILES_REPO"; then
# 最后的兜底:实在不行就用 Root 创建,然后只修这个新目录的权限
warn "Fallback to root creation..."
mkdir -p "$DOTFILES_REPO"
chown -R "$TARGET_USER:" "$DOTFILES_REPO"
fi
fi
# ================= [修改结束] =================
as_user git -C "$DOTFILES_REPO" init
# 强制将本地分支名设为 main
as_user git -C "$DOTFILES_REPO" branch -m "$BRANCH_NAME"
as_user git -C "$DOTFILES_REPO" config core.sparseCheckout true
local sparse_file="$DOTFILES_REPO/.git/info/sparse-checkout"
for item in "${TARGET_DIRS[@]}"; do
log " Configuring sparse-checkout: $item"
echo "$item" | as_user tee -a "$sparse_file" >/dev/null
done
log " Adding remote origin: $REPO_GITHUB"
as_user git -C "$DOTFILES_REPO" remote add origin "$REPO_GITHUB"
log "Downloading latest snapshot (Github)..."
if ! as_user git -C "$DOTFILES_REPO" pull origin "$BRANCH_NAME" --depth 1 ; then
error "Failed to download dotfiles."
warn "Cleaning up empty directory to prevent errors on retry..."
rm -rf "$DOTFILES_REPO"
critical_failure_handler "Failed to download dotfiles (Sparse+Shallow failed)."
else
# 这里可以保留作为双重保险,或者因为上面已经处理好了权限,这行其实是可选的
chown -R "$TARGET_USER:" "$DOTFILES_REPO"
as_user git -C "$DOTFILES_REPO" branch --set-upstream-to=origin/main main
# 这是一个常见痛点:因为 git 也是 sudo -u 运行的,这步是为了防止 git 报错 "dubious ownership"
as_user git config --global --add safe.directory "$DOTFILES_REPO"
success "Repository prepared."
fi
else
log "Dotfiles repo already exists. Skipping clone."
fi
}
prepare_repository
# 2. 执行链接
if [ -d "$DOTFILES_REPO/dotfiles" ]; then
EXCLUDE_LIST=""
if [ "$TARGET_USER" != "shorin" ]; then
EXCLUDE_FILE="$PARENT_DIR/exclude-dotfiles.txt"
if [ -f "$EXCLUDE_FILE" ]; then
log "Loading exclusions..."
EXCLUDE_LIST=$(grep -vE "^\s*#|^\s*$" "$EXCLUDE_FILE" | tr '\n' ' ')
fi
fi
log "Backing up existing configs..."
as_user tar -czf "$HOME_DIR/config_backup_$(date +%s).tar.gz" -C "$HOME_DIR" .config
# 调用递归函数:从 dotfiles 根目录开始,目标是 HOME
link_recursive "$DOTFILES_REPO/dotfiles" "$HOME_DIR" "$EXCLUDE_LIST"
as_user chmod -R +x $HOME_DIR/.local/bin
# 创建shorin工具的链接
as_user shorin link
# --- Post-Process (防止污染 git 的修正) ---
OUTPUT_EXAMPLE_KDL="$HOME_DIR/.config/niri/output-example.kdl"
OUTPUT_KDL="$HOME_DIR/.config/niri/output.kdl"
if [ "$TARGET_USER" != "shorin" ]; then
as_user touch $OUTPUT_KDL
# 修复 Bookmarks (转为实体文件并修改)
BOOKMARKS_FILE="$HOME_DIR/.config/gtk-3.0/bookmarks"
REPO_BOOKMARKS="$DOTFILES_REPO/dotfiles/.config/gtk-3.0/bookmarks"
if [ -f "$REPO_BOOKMARKS" ]; then
as_user sed -i "s/shorin/$TARGET_USER/g" "$REPO_BOOKMARKS"
log "Updated GTK bookmarks."
fi
else
as_user cp "$DOTFILES_REPO/dotfiles/.config/niri/output-example.kdl" "$OUTPUT_KDL"
fi
# GTK Theme Symlinks (Fix internal links)
GTK4="$HOME_DIR/.config/gtk-4.0"
THEME="$HOME_DIR/.local/share/themes/adw-gtk3-dark/gtk-4.0"
if [ -d "$GTK4" ]; then
as_user rm -f "$GTK4/gtk.css" "$GTK4/gtk-dark.css"
as_user ln -sf "$THEME/gtk-dark.css" "$GTK4/gtk-dark.css"
as_user ln -sf "$THEME/gtk.css" "$GTK4/gtk.css"
fi
# Flatpak overrides
if command -v flatpak &>/dev/null; then
as_user flatpak override --user --filesystem=xdg-data/themes
as_user flatpak override --user --filesystem="$HOME_DIR/.themes"
as_user flatpak override --user --filesystem=xdg-config/gtk-4.0
as_user flatpak override --user --filesystem=xdg-config/gtk-3.0
as_user flatpak override --user --env=GTK_THEME=adw-gtk3-dark
as_user flatpak override --user --filesystem=xdg-config/fontconfig
fi
success "Dotfiles Linked."
else
warn "Dotfiles missing in repo directory."
fi
# === niri blur ====
curl -L shorin.xyz/niri-blur-toggle | as_user bash
# ==============================================================================
# STEP 7: Wallpapers
# ==============================================================================
section "Step 6/9" "Wallpapers"
# 更新引用路径
if [ -d "$DOTFILES_REPO/wallpapers" ]; then
as_user ln -sf "$DOTFILES_REPO/wallpapers" "$HOME_DIR/Pictures/Wallpapers"
as_user mkdir -p "$HOME_DIR/Templates"
as_user touch "$HOME_DIR/Templates/new"
echo "#!/bin/bash" | as_user tee "$HOME_DIR/Templates/new.sh" >/dev/null
as_user chmod +x "$HOME_DIR/Templates/new.sh"
success "Installed."
fi
# === remove gtk bottom =======
as_user gsettings set org.gnome.desktop.wm.preferences button-layout ":close"
# ==============================================================================
# STEP 8: Hardware Tools
# ==============================================================================
section "Step 7/9" "Hardware"
if pacman -Q ddcutil &>/dev/null; then
gpasswd -a "$TARGET_USER" i2c
lsmod | grep -q i2c_dev || echo "i2c-dev" >/etc/modules-load.d/i2c-dev.conf
fi
if pacman -Q swayosd &>/dev/null; then
systemctl enable --now swayosd-libinput-backend.service >/dev/null 2>&1
fi
success "Tools configured."
section "Config" "Hiding useless .desktop files"
log "Hiding useless .desktop files"
run_hide_desktop_file
rm -f "$SUDO_TEMP_FILE"
# === 教程文件 ===
log "Copying tutorial file on desktop..."
as_user cp "$PARENT_DIR/resources/必看-shoirn-Niri使用方法.txt" "$HOME_DIR/必看-Shoirn-Niri使用方法.txt"
# ==============================================================================
# STEP 9: Cleanup & Auto-Login
# ==============================================================================
# section "Final" "Cleanup & Boot"
# SVC_DIR="$HOME_DIR/.config/systemd/user"
# SVC_FILE="$SVC_DIR/niri-autostart.service"
# LINK="$SVC_DIR/default.target.wants/niri-autostart.service"
# if [ "$SKIP_DM" = true ]; then
# log "Auto-login skipped."
# as_user rm -f "$LINK" "$SVC_FILE"
# else
# log "Configuring TTY Auto-login..."
# mkdir -p "/etc/systemd/system/getty@tty1.service.d"
# echo -e "[Service]\nExecStart=\nExecStart=-/sbin/agetty --noreset --noclear --autologin $TARGET_USER - \${TERM}" >"/etc/systemd/system/getty@tty1.service.d/autologin.conf"
# as_user mkdir -p "$(dirname "$LINK")"
# cat <<EOT >"$SVC_FILE"
# [Unit]
# Description=Niri Session Autostart
# After=graphical-session-pre.target
# [Service]
# ExecStart=/usr/bin/niri-session
# Restart=on-failure
# [Install]
# WantedBy=default.target
# EOT
# as_user ln -sf "../niri-autostart.service" "$LINK"
# chown -R "$TARGET_USER" "$SVC_DIR"
# success "Enabled."
# fi
# ==============================================================================
# STEP 9: Display Manager (greetd + tuigreet) & Cleanup
# ==============================================================================
section "Final" "Cleanup & Boot Configuration"
# 1. 清理旧的 TTY 自动登录残留(无论是否启用 greetd旧版残留都应清除
log "Cleaning up legacy TTY autologin configs..."
rm -f /etc/systemd/system/getty@tty1.service.d/autologin.conf 2>/dev/null
if [ "$SKIP_DM" = true ]; then
log "Display Manager setup skipped (Conflict found or user opted out)."
warn "You will need to start your session manually from the TTY."
else
setup_ly
fi
trap - ERR
log "Module 04 completed."