This commit is contained in:
2026-03-31 20:13:15 +08:00
parent 48044e957d
commit 08c513b995
1155 changed files with 79920 additions and 0 deletions

150
scripts/00-btrfs-init.sh Normal file
View File

@ -0,0 +1,150 @@
#!/bin/bash
# ==============================================================================
# 00-btrfs-init.sh - Pre-install Snapshot Safety Net (Root & Home)
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/00-utils.sh"
check_root
section "Phase 0" "System Snapshot Initialization"
# ------------------------------------------------------------------------------
# 0. Early Exit Check
# ------------------------------------------------------------------------------
log "Checking Root filesystem..."
ROOT_FSTYPE=$(findmnt -n -o FSTYPE /)
if [ "$ROOT_FSTYPE" != "btrfs" ]; then
warn "Root filesystem is not Btrfs ($ROOT_FSTYPE detected)."
log "Skipping Btrfs snapshot initialization entirely."
exit 0
fi
log "Root is Btrfs. Proceeding with pristine Snapshot Safety Net setup..."
# ------------------------------------------------------------------------------
# 1. Configure Root (/) & Home (/home)
# ------------------------------------------------------------------------------
# 【极致纯净】这里只装 snapper不装任何多余工具
log "Installing Snapper..."
exe pacman -Syu --noconfirm --needed snapper
log "Configuring Snapper for Root..."
if ! snapper list-configs | grep -q "^root "; then
if [ -d "/.snapshots" ]; then
exe_silent umount /.snapshots
exe_silent rm -rf /.snapshots
fi
if exe snapper -c root create-config /; then
success "Config 'root' created."
exe snapper -c root set-config ALLOW_GROUPS="wheel" TIMELINE_CREATE="yes" TIMELINE_CLEANUP="yes" NUMBER_LIMIT="10" NUMBER_MIN_AGE="0" NUMBER_LIMIT_IMPORTANT="5" TIMELINE_LIMIT_HOURLY="3" TIMELINE_LIMIT_DAILY="0" TIMELINE_LIMIT_WEEKLY="0" TIMELINE_LIMIT_MONTHLY="0" TIMELINE_LIMIT_YEARLY="0"
exe systemctl enable snapper-cleanup.timer
exe systemctl enable snapper-timeline.timer
fi
fi
if findmnt -n -o FSTYPE /home | grep -q "btrfs"; then
log "Configuring Snapper for Home..."
if ! snapper list-configs | grep -q "^home "; then
if [ -d "/home/.snapshots" ]; then
exe_silent umount /home/.snapshots
exe_silent rm -rf /home/.snapshots
fi
if exe snapper -c home create-config /home; then
success "Config 'home' created."
exe snapper -c home set-config ALLOW_GROUPS="wheel" TIMELINE_CREATE="yes" TIMELINE_CLEANUP="yes" NUMBER_MIN_AGE="0" NUMBER_LIMIT="10" NUMBER_LIMIT_IMPORTANT="5" TIMELINE_LIMIT_HOURLY="3" TIMELINE_LIMIT_DAILY="0" TIMELINE_LIMIT_WEEKLY="0" TIMELINE_LIMIT_MONTHLY="0" TIMELINE_LIMIT_YEARLY="0"
fi
fi
fi
# ------------------------------------------------------------------------------
# 2. Advanced Btrfs-GRUB Decoupling (Pure Base)
# ------------------------------------------------------------------------------
section "Safety Net" "GRUB-Btrfs Decoupling"
if [ -f "/etc/default/grub" ] && command -v grub-mkconfig >/dev/null 2>&1; then
FOUND_ESP_GRUB=""
VFAT_MOUNTS=$(findmnt -n -l -o TARGET -t vfat | grep -v "^/boot$")
if [ -n "$VFAT_MOUNTS" ]; then
while read -r mountpoint; do
if [ -d "$mountpoint/grub" ]; then
FOUND_ESP_GRUB="$mountpoint/grub"
break
fi
done <<< "$VFAT_MOUNTS"
fi
if [ -n "$FOUND_ESP_GRUB" ]; then
log "Applying GRUB Decoupling Stub..."
if [ -L "/boot/grub" ]; then exe rm -f /boot/grub; fi
if [ ! -d "/boot/grub" ]; then exe mkdir -p /boot/grub; fi
BTRFS_UUID=$(findmnt -n -o UUID /)
SUBVOL_NAME=$(findmnt -n -o OPTIONS / | tr ',' '\n' | grep '^subvol=' | cut -d= -f2)
if [ "$SUBVOL_NAME" == "/" ] || [ -z "$SUBVOL_NAME" ]; then
BTRFS_BOOT_PATH="/boot/grub"
else
[[ "$SUBVOL_NAME" != /* ]] && SUBVOL_NAME="/${SUBVOL_NAME}"
BTRFS_BOOT_PATH="${SUBVOL_NAME}/boot/grub"
fi
cat <<EOF | sudo tee "${FOUND_ESP_GRUB}/grub.cfg" > /dev/null
search --no-floppy --fs-uuid --set=root $BTRFS_UUID
configfile ${BTRFS_BOOT_PATH}/grub.cfg
EOF
sed -i 's/^GRUB_DEFAULT=.*/GRUB_DEFAULT=saved/' /etc/default/grub
if grep -q "^#*GRUB_SAVEDEFAULT=" /etc/default/grub; then
sed -i 's/^#*GRUB_SAVEDEFAULT=.*/GRUB_SAVEDEFAULT=true/' /etc/default/grub
else
echo "GRUB_SAVEDEFAULT=true" >> /etc/default/grub
fi
fi
# 【关键】这里生成的是最干净的、没有快照菜单的 grub.cfg
log "Regenerating Pristine GRUB Config..."
exe grub-mkconfig -o /boot/grub/grub.cfg
fi
# ------------------------------------------------------------------------------
# 3. Create Initial Pristine Snapshot
# ------------------------------------------------------------------------------
section "Safety Net" "Creating Pristine Initial Snapshots"
if snapper list-configs | grep -q "root "; then
if ! snapper -c root list --columns description | grep -q "Before Shorin Setup"; then
if exe snapper -c root create --description "Before Shorin Setup"; then
success "Pristine Root snapshot created."
else
error "Failed to create Root snapshot."; exit 1
fi
fi
fi
if snapper list-configs | grep -q "home "; then
if ! snapper -c home list --columns description | grep -q "Before Shorin Setup"; then
if exe snapper -c home create --description "Before Shorin Setup"; then
success "Pristine Home snapshot created."
fi
fi
fi
# ------------------------------------------------------------------------------
# 4. Deploy Rollback Scripts
# ------------------------------------------------------------------------------
BIN_DIR="/usr/local/bin"
UNDO_SRC="$PARENT_DIR/undochange.sh"
DE_UNDO_SRC="$SCRIPT_DIR/de-undochange.sh"
exe mkdir -p "$BIN_DIR"
if [ -f "$UNDO_SRC" ]; then exe cp -f "$UNDO_SRC" "$BIN_DIR/shorin-undochange" && exe chmod +x "$BIN_DIR/shorin-undochange"; fi
if [ -f "$DE_UNDO_SRC" ]; then exe cp -f "$DE_UNDO_SRC" "$BIN_DIR/shorin-de-undochange" && exe chmod +x "$BIN_DIR/shorin-de-undochange"; fi
log "Module 00 completed. Pure base system secured."

612
scripts/00-utils.sh Normal file
View File

@ -0,0 +1,612 @@
#!/bin/bash
# ==============================================================================
# 00-utils.sh - The "TUI" Visual Engine (v4.0)
# ==============================================================================
# --- 1. 颜色与样式定义 (ANSI) ---
# 注意:这里定义的是字面量字符串,需要 echo -e 来解析
export NC='\033[0m'
export BOLD='\033[1m'
export DIM='\033[2m'
export ITALIC='\033[3m'
export UNDER='\033[4m'
export H_MAGENTA='\033[1;35m'
# 常用高亮色
export H_RED='\033[1;31m'
export H_GREEN='\033[1;32m'
export H_YELLOW='\033[1;33m'
export H_BLUE='\033[1;34m'
export H_PURPLE='\033[1;35m'
export H_CYAN='\033[1;36m'
export H_WHITE='\033[1;37m'
export H_GRAY='\033[1;90m'
# 背景色 (用于标题栏)
export BG_BLUE='\033[44m'
export BG_PURPLE='\033[45m'
# 符号定义
export TICK="${H_GREEN}${NC}"
export CROSS="${H_RED}${NC}"
export INFO="${H_BLUE}${NC}"
export WARN="${H_YELLOW}${NC}"
export ARROW="${H_CYAN}${NC}"
check_root() {
if [ "$EUID" -ne 0 ]; then
echo -e "${H_RED} $CROSS CRITICAL ERROR: Script must be run as root.${NC}"
exit 1
fi
}
check_root
# ==============================================================================
# detect_target_user - 识别目标用户 (支持 1-based 序号与回车默认选择)
# ==============================================================================
detect_target_user() {
# 1. 缓存检查
if [[ -f "/tmp/shorin_install_user" ]]; then
TARGET_USER=$(cat "/tmp/shorin_install_user")
HOME_DIR="/home/$TARGET_USER"
export TARGET_USER HOME_DIR
return 0
fi
log "Detecting system users..."
# 2. 提取系统中所有普通用户 (UID 1000-60000)
mapfile -t HUMAN_USERS < <(awk -F: '$3 >= 1000 && $3 < 60000 {print $1}' /etc/passwd)
# 3. 核心决策逻辑
if [[ ${#HUMAN_USERS[@]} -gt 1 ]]; then
echo -e " ${H_YELLOW}>>> Multiple users detected. Who is the target?${NC}"
local default_user=""
local default_idx=""
# 遍历用户,生成 1 开始的序号,并捕获当前 Sudo 用户作为默认值
for i in "${!HUMAN_USERS[@]}"; do
local mark=""
local display_idx=$((i + 1))
if [[ "${HUMAN_USERS[$i]}" == "${SUDO_USER:-}" ]]; then
mark="${H_CYAN}*${NC}"
default_user="${HUMAN_USERS[$i]}"
default_idx="$display_idx"
fi
echo -e " [${display_idx}] ${mark}${HUMAN_USERS[$i]}"
done
while true; do
# 动态生成提示词
if [[ -n "$default_user" ]]; then
echo -ne " ${H_CYAN}Select user ID [1-${#HUMAN_USERS[@]}] (Default ${default_idx}): ${NC}"
else
echo -ne " ${H_CYAN}Select user ID [1-${#HUMAN_USERS[@]}]: ${NC}"
fi
read -r idx
# 处理直接回车:如果有默认用户,直接采纳
if [[ -z "$idx" && -n "$default_user" ]]; then
TARGET_USER="$default_user"
log "Defaulting to current user: ${H_CYAN}${TARGET_USER}${NC}"
break
fi
# 验证输入是否为合法数字 (1 到 数组长度)
if [[ "$idx" =~ ^[0-9]+$ ]] && [ "$idx" -ge 1 ] && [ "$idx" -le "${#HUMAN_USERS[@]}" ]; then
# 数组索引需要减 1 还原
TARGET_USER="${HUMAN_USERS[$((idx - 1))]}"
break
else
warn "Invalid selection. Please enter a valid number or press Enter for default."
fi
done
elif [[ ${#HUMAN_USERS[@]} -eq 1 ]]; then
TARGET_USER="${HUMAN_USERS[0]}"
log "Single user detected: ${H_CYAN}${TARGET_USER}${NC}"
else
if [[ -n "${SUDO_USER:-}" && "$SUDO_USER" != "root" ]]; then
TARGET_USER="$SUDO_USER"
else
echo -ne " ${H_YELLOW}No standard user found. Enter intended username:${NC} "
read -r TARGET_USER
fi
fi
# 4. 最终验证与持久化
if [[ -z "$TARGET_USER" ]]; then
error "Target user cannot be empty."
exit 1
fi
echo "$TARGET_USER" > "/tmp/shorin_install_user"
HOME_DIR="/home/$TARGET_USER"
export TARGET_USER HOME_DIR
}
# 日志文件
export TEMP_LOG_FILE="/tmp/log-shorin-arch-setup.txt"
[ ! -f "$TEMP_LOG_FILE" ] && touch "$TEMP_LOG_FILE" && chmod 666 "$TEMP_LOG_FILE"
# --- 2. 基础工具 ---
write_log() {
# Strip ANSI colors for log file
local clean_msg=$(echo -e "$2" | sed 's/\x1b\[[0-9;]*m//g')
echo "[$(date '+%H:%M:%S')] [$1] $clean_msg" >> "$TEMP_LOG_FILE"
}
# --- 3. 视觉组件 (TUI Style) ---
# 绘制分割线
hr() {
printf "${H_GRAY}%*s${NC}\n" "${COLUMNS:-80}" '' | tr ' ' '─'
}
# 绘制大标题 (Section)
section() {
local title="$1"
local subtitle="$2"
echo ""
echo -e "${H_PURPLE}╭──────────────────────────────────────────────────────────────────────────────╮${NC}"
echo -e "${H_PURPLE}${NC} ${BOLD}${H_WHITE}$title${NC}"
echo -e "${H_PURPLE}${NC} ${H_CYAN}$subtitle${NC}"
echo -e "${H_PURPLE}╰──────────────────────────────────────────────────────────────────────────────╯${NC}"
write_log "SECTION" "$title - $subtitle"
}
# 绘制键值对信息
info_kv() {
local key="$1"
local val="$2"
local extra="$3"
printf " ${H_BLUE}${NC} %-15s : ${BOLD}%s${NC} ${DIM}%s${NC}\n" "$key" "$val" "$extra"
write_log "INFO" "$key=$val"
}
# 普通日志
log() {
echo -e " $ARROW $1"
write_log "LOG" "$1"
}
# 成功日志
success() {
echo -e " $TICK ${H_GREEN}$1${NC}"
write_log "SUCCESS" "$1"
}
# 警告日志 (突出显示)
warn() {
echo -e " $WARN ${H_YELLOW}${BOLD}WARNING:${NC} ${H_YELLOW}$1${NC}"
write_log "WARN" "$1"
}
# 错误日志 (非常突出)
error() {
echo -e ""
echo -e "${H_RED} ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓${NC}"
echo -e "${H_RED} ┃ ERROR: $1${NC}"
echo -e "${H_RED} ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛${NC}"
echo -e ""
write_log "ERROR" "$1"
}
# --- 4. 核心:命令执行器 (Command Exec) ---
exe() {
local full_command="$*"
# Visual: 显示正在运行的命令
echo -e " ${H_GRAY}┌──[ ${H_MAGENTA}EXEC${H_GRAY} ]────────────────────────────────────────────────────${NC}"
echo -e " ${H_GRAY}${NC} ${H_CYAN}$ ${NC}${BOLD}$full_command${NC}"
write_log "EXEC" "$full_command"
# Run the command
"$@"
local status=$?
if [ $status -eq 0 ]; then
echo -e " ${H_GRAY}└──────────────────────────────────────────────────────── ${H_GREEN}OK${H_GRAY} ─┘${NC}"
else
echo -e " ${H_GRAY}└────────────────────────────────────────────────────── ${H_RED}FAIL${H_GRAY} ─┘${NC}"
write_log "FAIL" "Exit Code: $status"
return $status
fi
}
# 静默执行
exe_silent() {
"$@" > /dev/null 2>&1
}
# --- 5. 可复用逻辑块 ---
# 动态选择 Flathub 镜像源 (修复版:使用 echo -e 处理颜色变量)
select_flathub_mirror() {
# 1. 索引数组保证顺序
local names=(
"SJTU (Shanghai Jiao Tong)"
"USTC (Univ of Sci & Tech of China)"
"FlatHub Offical"
)
local urls=(
"https://mirror.sjtu.edu.cn/flathub"
"https://mirrors.ustc.edu.cn/flathub"
"https://dl.flathub.org/repo/"
)
# 2. 动态计算菜单宽度 (基于无颜色的纯文本)
local max_len=0
local title_text="Select Flathub Mirror (60s Timeout)"
max_len=${#title_text}
for name in "${names[@]}"; do
# 预估显示长度:"[x] Name - Recommended"
local item_len=$((${#name} + 4 + 14))
if (( item_len > max_len )); then
max_len=$item_len
fi
done
# 菜单总宽度
local menu_width=$((max_len + 4))
# --- 3. 渲染菜单 (使用 echo -e 确保颜色变量被解析) ---
echo ""
# 生成横线
local line_str=""
printf -v line_str "%*s" "$menu_width" ""
line_str=${line_str// /─}
# 打印顶部边框
echo -e "${H_PURPLE}${line_str}${NC}"
# 打印标题 (计算居中填充)
local title_padding_len=$(( (menu_width - ${#title_text}) / 2 ))
local right_padding_len=$((menu_width - ${#title_text} - title_padding_len))
# 生成填充空格
local t_pad_l=""; printf -v t_pad_l "%*s" "$title_padding_len" ""
local t_pad_r=""; printf -v t_pad_r "%*s" "$right_padding_len" ""
echo -e "${H_PURPLE}${NC}${t_pad_l}${BOLD}${title_text}${NC}${t_pad_r}${H_PURPLE}${NC}"
# 打印中间分隔线
echo -e "${H_PURPLE}${line_str}${NC}"
# 打印选项
for i in "${!names[@]}"; do
local name="${names[$i]}"
local display_idx=$((i+1))
# 1. 构造用于显示的带颜色字符串
local color_str=""
# 2. 构造用于计算长度的无颜色字符串
local raw_str=""
if [ "$i" -eq 0 ]; then
raw_str=" [$display_idx] $name - Recommended"
color_str=" ${H_CYAN}[$display_idx]${NC} ${name} - ${H_GREEN}Recommended${NC}"
else
raw_str=" [$display_idx] $name"
color_str=" ${H_CYAN}[$display_idx]${NC} ${name}"
fi
# 计算右侧填充空格
local padding=$((menu_width - ${#raw_str}))
local pad_str="";
if [ "$padding" -gt 0 ]; then
printf -v pad_str "%*s" "$padding" ""
fi
# 打印:边框 + 内容 + 填充 + 边框
echo -e "${H_PURPLE}${NC}${color_str}${pad_str}${H_PURPLE}${NC}"
done
# 打印底部边框
echo -e "${H_PURPLE}${line_str}${NC}"
echo ""
# --- 4. 用户交互 ---
local choice
# 提示符
read -t 60 -p "$(echo -e " ${H_YELLOW}Enter choice [1-${#names[@]}]: ${NC}")" choice
if [ $? -ne 0 ]; then echo ""; fi
choice=${choice:-1}
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -lt 1 ] || [ "$choice" -gt "${#names[@]}" ]; then
log "Invalid choice or timeout. Defaulting to SJTU..."
choice=1
fi
local index=$((choice-1))
local selected_name="${names[$index]}"
local selected_url="${urls[$index]}"
log "Setting Flathub mirror to: ${H_GREEN}$selected_name${NC}"
# 执行修改 (仅修改 flathub不涉及 github)
if exe flatpak remote-modify flathub --url="$selected_url"; then
success "Mirror updated."
else
error "Failed to update mirror."
fi
}
as_user() {
runuser -u "$TARGET_USER" -- "$@"
}
hide_desktop_file() {
local source_file="$1"
local filename=$(basename "$source_file")
local user_dir="$HOME_DIR/.local/share/applications"
local target_file="$user_dir/$filename"
mkdir -p "$user_dir"
if [[ -f "$source_file" ]]; then
cp -fv "$source_file" "$target_file"
if grep -q "^NoDisplay=" "$target_file"; then
sed -i 's/^NoDisplay=.*/NoDisplay=true/' "$target_file"
else
echo "NoDisplay=true" >> "$target_file"
fi
chown "$TARGET_USER:" "$target_file"
fi
}
# 批量执行
run_hide_desktop_file() {
local apps_to_hide=(
"avahi-discover.desktop"
"qv4l2.desktop"
"qvidcap.desktop"
"bssh.desktop"
"org.fcitx.Fcitx5.desktop"
"org.fcitx.fcitx5-migrator.desktop"
"xgps.desktop"
"xgpsspeed.desktop"
"gvim.desktop"
"kbd-layout-viewer5.desktop"
"bvnc.desktop"
"yazi.desktop"
"btop.desktop"
"vim.desktop"
"nvim.desktop"
"nvtop.desktop"
"mpv.desktop"
"org.gnome.Settings.desktop"
"thunar-settings.desktop"
"thunar-bulk-rename.desktop"
"thunar-volman-settings.desktop"
"clipse-gui.desktop"
"waypaper.desktop"
"xfce4-about.desktop"
"cmake-gui.desktop"
"assistant.desktop"
"qdbusviewer.desktop"
"linguist.desktop"
"designer.desktop"
"org.kde.drkonqi.coredump.gui.desktop"
"org.kde.kwrite.desktop"
"org.freedesktop.MalcontentControl.desktop"
"org.gnome.Nautilus.desktop"
)
echo "正在隐藏不需要的桌面图标..."
# 用一个 for 循环搞定所有调用
for app in "${apps_to_hide[@]}"; do
hide_desktop_file "/usr/share/applications/$app"
done
chown -R "$TARGET_USER:" "$HOME_DIR/.local/share/applications"
echo "图标隐藏完成!"
}
configure_nautilus_user() {
local sys_file="/usr/share/applications/org.gnome.Nautilus.desktop"
local user_dir="$HOME_DIR/.local/share/applications"
local user_file="$user_dir/org.gnome.Nautilus.desktop"
# 1. 检查系统文件是否存在
if [ -f "$sys_file" ]; then
local need_modify=0
local env_vars="env"
# --- 逻辑 1: Niri 检测 (输入法修复) ---
if command -v niri >/dev/null 2>&1; then
# 只要有 niri就强制使用 fcitx 模块
env_vars="$env_vars GTK_IM_MODULE=fcitx"
need_modify=1
log "检测到 Niri 环境,准备注入 GTK_IM_MODULE=fcitx"
fi
# --- 逻辑 2: 双显卡 NVIDIA 检测 (GSK 渲染修复) ---
local gpu_count=$(lspci | grep -E -i "vga|3d" | wc -l)
local has_nvidia=$(lspci | grep -E -i "nvidia" | wc -l)
if [ "$gpu_count" -gt 1 ] && [ "$has_nvidia" -gt 0 ]; then
# 叠加 GSK 渲染变量
env_vars="$env_vars GSK_RENDERER=gl"
need_modify=1
log "检测到双显卡 NVIDIA准备注入 GSK_RENDERER=gl"
# 额外操作: 创建 gsk.conf
local env_conf_dir="$HOME_DIR/.config/environment.d"
if [ ! -f "$env_conf_dir/gsk.conf" ]; then
mkdir -p "$env_conf_dir"
echo "GSK_RENDERER=gl" > "$env_conf_dir/gsk.conf"
# 修复权限
if [ -n "$TARGET_USER" ]; then
chown -R "$TARGET_USER" "$env_conf_dir"
fi
log "已添加用户级环境变量配置: $env_conf_dir/gsk.conf"
fi
fi
# --- 3. 执行修改 (如果命中了任意一个逻辑) ---
if [ "$need_modify" -eq 1 ]; then
# 准备目录并复制
mkdir -p "$user_dir"
cp "$sys_file" "$user_file"
# 修复所有者
if [ -n "$TARGET_USER" ]; then
chown "$TARGET_USER" "$user_file"
fi
# 修改 Desktop 文件
# env_vars 此时可能是:
# - "env GTK_IM_MODULE=fcitx" (仅Niri)
# - "env GSK_RENDERER=gl" (仅双显卡)
# - "env GTK_IM_MODULE=fcitx GSK_RENDERER=gl" (两者都有)
sed -i "s|^Exec=|Exec=$env_vars |" "$user_file"
log "已生成 Nautilus 用户配置: $user_file (参数: $env_vars)"
fi
fi
}
force_copy() {
local src="$1"
local target_dir="$2"
if [[ -z "$src" || -z "$target_dir" ]]; then
warn "force_copy: Missing arguments"
return 1
fi
if [[ -d "${src%/}" ]]; then
(cd "$src" && find . -type d) | while read -r d; do
as_user rm -f "$target_dir/$d" 2>/dev/null
done
fi
exe as_user cp -rf "$src" "$target_dir"
}
# ==============================================================================
# check_dm_conflict - 检测现有的显示管理器冲突,并让用户选择是否启用新 DM
# ==============================================================================
# 使用方法: check_dm_conflict
# 结果: 设置全局变量 $SKIP_DM (true/false)
check_dm_conflict() {
local KNOWN_DMS=(
"cdm" "console-tdm" "emptty" "lemurs" "lidm" "loginx" "ly" "nodm" "tbsm"
"entrance-git" "gdm" "lightdm" "lxdm" "plasma-login-manager" "sddm"
"slim" "xorg-xdm" "greetd"
)
SKIP_DM=false
local DM_FOUND=""
for dm in "${KNOWN_DMS[@]}"; do
if pacman -Q "$dm" &>/dev/null; then
DM_FOUND="$dm"
break
fi
done
if [ -n "$DM_FOUND" ]; then
info_kv "Conflict" "${H_RED}$DM_FOUND${NC}"
SKIP_DM=true
else
# read -t 20 等待 20 秒,超时默认 Y
read -t 20 -p "$(echo -e " ${H_CYAN}Enable Display Manager ? [Y/n] (Default Y): ${NC}")" choice || true
if [[ "${choice:-Y}" =~ ^[Yy]$ ]]; then
SKIP_DM=false
else
SKIP_DM=true
fi
fi
}
# ==============================================================================
# setup_greetd_tuigreet - 安装并配置 greetd + tuigreet
# ==============================================================================
# 使用方法: setup_greetd_tuigreet
setup_greetd_tuigreet() {
log "Installing greetd and tuigreet..."
exe pacman -S --noconfirm --needed greetd greetd-tuigreet
# 禁用可能存在的默认 getty@tty1把 TTY1 彻底让给 greetd
systemctl disable getty@tty1.service 2>/dev/null
# 配置 greetd (覆盖写入 config.toml)
log "Configuring /etc/greetd/config.toml..."
local GREETD_CONF="/etc/greetd/config.toml"
cat <<EOF > "$GREETD_CONF"
[terminal]
# 绑定到 TTY1
vt = 1
[default_session]
# 使用 tuigreet 作为前端
# 自动扫描 /usr/share/wayland-sessions/,支持时间显示、密码星号、记住上次选择
command = "tuigreet --time --user-menu --remember --remember-user-session --asterisks"
user = "greeter"
EOF
# 修复 tuigreet 的 --remember 缓存目录权限
log "Ensuring cache directory permissions for tuigreet..."
mkdir -p /var/cache/tuigreet
chown -R greeter:greeter /var/cache/tuigreet
chmod 755 /var/cache/tuigreet
# 启用服务
log "Enabling greetd service..."
systemctl enable greetd.service
success "greetd with tuigreet frontend has been successfully configured!"
}
# ==============================================================================
# setup_ly - 安装并配置 ly 显示管理器
# ==============================================================================
# 功能列表:
# 1. 安装 ly 软件包
# 2. 禁用其他可能冲突的 TTY 登录服务 (getty/greetd)
# 3. 编辑 /etc/ly/config.ini开启 Matrix (代码雨) 背景动画
# 4. 启用 ly.service 开机自启
# 使用方法: setup_ly
setup_ly() {
log "Installing ly display manager..."
exe pacman -S --noconfirm --needed ly
# # 配置 ly (非破坏性修改 config.ini)
# log "Configuring /etc/ly/config.ini for Matrix animation..."
# local LY_CONF="/etc/ly/config.ini"
# if [[ -f "$LY_CONF" ]]; then
# # 使用 sed 精准替换:
# # 1. 将注释掉的或现有的 animation = none 替换为 animation = matrix
# sed -i 's/^[#[:space:]]*animation[[:space:]]*=.*/animation = matrix/' "$LY_CONF"
# else
# log "Warning: $LY_CONF not found! Please check ly installation."
# fi
# 启用服务
log "Enabling ly service..."
systemctl enable ly@tty1
success "ly display manager with Matrix animation has been successfully configured!"
}

173
scripts/01-base.sh Normal file
View File

@ -0,0 +1,173 @@
#!/bin/bash
# ==============================================================================
# 01-base.sh - Base System Configuration
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/00-utils.sh"
check_root
log "Starting Phase 1: Base System Configuration..."
# ------------------------------------------------------------------------------
# 1. Set Global Default Editor
# ------------------------------------------------------------------------------
section "Step 1/6" "Global Default Editor"
TARGET_EDITOR="vim"
if command -v nvim &> /dev/null; then
TARGET_EDITOR="nvim"
log "Neovim detected."
elif command -v nano &> /dev/null; then
TARGET_EDITOR="nano"
log "Nano detected."
else
log "Neovim or Nano not found. Installing Vim..."
if ! command -v vim &> /dev/null; then
exe pacman -Syu --noconfirm gvim
fi
fi
log "Setting EDITOR=$TARGET_EDITOR in /etc/environment..."
if grep -q "^EDITOR=" /etc/environment; then
exe sed -i "s/^EDITOR=.*/EDITOR=${TARGET_EDITOR}/" /etc/environment
else
# exe handles simple commands, for redirection we wrap in bash -c or just run it
# For simplicity in logging, we just run it and log success
echo "EDITOR=${TARGET_EDITOR}" >> /etc/environment
fi
success "Global EDITOR set to: ${TARGET_EDITOR}"
# ------------------------------------------------------------------------------
# 2. Enable 32-bit (multilib) Repository
# ------------------------------------------------------------------------------
section "Step 2/6" "Multilib Repository"
if grep -q "^\[multilib\]" /etc/pacman.conf; then
success "[multilib] is already enabled."
else
log "Uncommenting [multilib]..."
# Uncomment [multilib] and the following Include line
exe sed -i "/\[multilib\]/,/Include/"'s/^#//' /etc/pacman.conf
log "Refreshing database..."
exe pacman -Syu
success "[multilib] enabled."
fi
# ------------------------------------------------------------------------------
# 3. Install Base Fonts
# ------------------------------------------------------------------------------
section "Step 3/6" "Base Fonts"
log "Installing adobe-source-han-serif-cn-fonts adobe-source-han-sans-cn-fonts , ttf-liberation, emoji..."
exe pacman -S --noconfirm --needed ttf-liberation noto-fonts noto-fonts-cjk noto-fonts-emoji ttf-jetbrains-mono-nerd otf-font-awesome
log "Base fonts installed."
log "Installing terminus-font..."
# 安装 terminus-font 包
exe pacman -S --noconfirm --needed terminus-font
log "Setting font for current session..."
exe setfont ter-v28n
log "Configuring permanent vconsole font..."
if [ -f /etc/vconsole.conf ] && grep -q "^FONT=" /etc/vconsole.conf; then
exe sed -i 's/^FONT=.*/FONT=ter-v28n/' /etc/vconsole.conf
else
echo "FONT=ter-v28n" >> /etc/vconsole.conf
fi
log "Restarting systemd-vconsole-setup..."
exe systemctl restart systemd-vconsole-setup
success "TTY font configured (ter-v28n)."
# ------------------------------------------------------------------------------
# 4. Configure archlinuxcn Repository
# ------------------------------------------------------------------------------
section "Step 4/6" "ArchLinuxCN Repository"
if grep -q "\[archlinuxcn\]" /etc/pacman.conf; then
success "archlinuxcn repository already exists."
else
log "Adding archlinuxcn mirrors to pacman.conf..."
# Timezone check: KISS approach, works reliably inside arch-chroot and host system
LOCAL_TZ=""
if [ -L /etc/localtime ]; then
LOCAL_TZ=$(readlink -f /etc/localtime)
fi
echo "" >> /etc/pacman.conf
echo "[archlinuxcn]" >> /etc/pacman.conf
if [[ "$LOCAL_TZ" == *"Asia/Shanghai"* ]]; then
log "Timezone is Asia/Shanghai. Applying mainland mirrors..."
cat <<EOT >> /etc/pacman.conf
Server = https://mirrors.ustc.edu.cn/archlinuxcn/\$arch
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/\$arch
Server = https://mirrors.hit.edu.cn/archlinuxcn/\$arch
Server = https://repo.huaweicloud.com/archlinuxcn/\$arch
EOT
else
log "Non-Shanghai timezone detected. Prepending global repo.archlinuxcn.org mirror..."
cat <<EOT >> /etc/pacman.conf
Server = https://repo.archlinuxcn.org/\$arch
Server = https://mirrors.ustc.edu.cn/archlinuxcn/\$arch
Server = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/\$arch
Server = https://mirrors.hit.edu.cn/archlinuxcn/\$arch
Server = https://repo.huaweicloud.com/archlinuxcn/\$arch
EOT
fi
success "Mirrors added based on timezone."
fi
log "Installing archlinuxcn-keyring..."
# Keyring installation often needs -Sy specifically, but -Syu is safe too
exe pacman -Syu --noconfirm archlinuxcn-keyring
success "ArchLinuxCN configured."
# ------------------------------------------------------------------------------
# 5. Install AUR Helpers
# ------------------------------------------------------------------------------
section "Step 5/6" "AUR Helpers"
log "Installing yay and paru..."
exe pacman -S --noconfirm --needed base-devel yay paru
success "Helpers installed."
# ------------------------------------------------------------------------------
# 6. Configure NetworkManager Backend (iwd)
# ------------------------------------------------------------------------------
section "Step 6/6" "Network Backend (iwd)"
# Check if NetworkManager is installed before attempting configuration
if pacman -Qi networkmanager &> /dev/null; then
log "NetworkManager detected. Proceeding with iwd backend configuration..."
log "Configuring NetworkManager to use iwd backend..."
exe pacman -S --noconfirm --needed iwd impala
exe systemctl enable iwd
# Ensure directory exists
if [ ! -d /etc/NetworkManager/conf.d ]; then
mkdir -p /etc/NetworkManager/conf.d
fi
if [ -f /etc/NetworkManager/conf.d/wifi_backend.conf ];then
rm /etc/NetworkManager/conf.d/wifi_backend.conf
fi
if [ ! -f /etc/NetworkManager/conf.d/iwd.conf ];then
echo -e "[device]\nwifi.backend=iwd" >> /etc/NetworkManager/conf.d/iwd.conf
rm -rfv /etc/NetworkManager/system-connections/*
fi
log "Notice: NetworkManager restart deferred. Changes will apply after reboot."
success "Network backend configured (iwd)."
else
log "NetworkManager not found. Skipping iwd configuration."
fi
# ------------------------------------------------------------------------------
log "Module 01 completed."

186
scripts/02-musthave.sh Normal file
View File

@ -0,0 +1,186 @@
#!/bin/bash
# ==============================================================================
# 02-musthave.sh - Essential Software, Drivers & Locale
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/00-utils.sh"
check_root
log ">>> Starting Phase 2: Essential (Must-have) Software & Drivers"
# ------------------------------------------------------------------------------
# 1. Btrfs Assistants & GRUB Snapshot Integration
# ------------------------------------------------------------------------------
section "Step 1/8" "Btrfs Snapshot Integration"
ROOT_FSTYPE=$(findmnt -n -o FSTYPE /)
if [ "$ROOT_FSTYPE" == "btrfs" ]; then
log "Btrfs detected. Installing advanced snapshot management tools..."
exe pacman -S --noconfirm --needed btrfs-assistant xorg-xhost grub-btrfs inotify-tools less
success "Btrfs helper tools installed."
if [ -f "/etc/default/grub" ] && command -v grub-mkconfig >/dev/null 2>&1; then
log "Integrating snapshots into GRUB menu..."
# 重新计算 Btrfs 内部的 boot 路径
SUBVOL_NAME=$(findmnt -n -o OPTIONS / | tr ',' '\n' | grep '^subvol=' | cut -d= -f2)
if [ "$SUBVOL_NAME" == "/" ] || [ -z "$SUBVOL_NAME" ]; then
BTRFS_BOOT_PATH="/boot/grub"
else
[[ "$SUBVOL_NAME" != /* ]] && SUBVOL_NAME="/${SUBVOL_NAME}"
BTRFS_BOOT_PATH="${SUBVOL_NAME}/boot/grub"
fi
# 修改 grub-btrfs 的跨区搜索路径
if [ -f "/etc/default/grub-btrfs/config" ]; then
log "Patching grub-btrfs config for Btrfs search path..."
sed -i "s|^#*GRUB_BTRFS_GBTRFS_SEARCH_DIRNAME=.*|GRUB_BTRFS_GBTRFS_SEARCH_DIRNAME=\"${BTRFS_BOOT_PATH}\"|" /etc/default/grub-btrfs/config
fi
# 开启监听服务并重新生成菜单(这次菜单里就会多出 Snapshots 选项了!)
exe systemctl enable --now grub-btrfsd
log "Regenerating GRUB Config with Snapshot entries..."
exe grub-mkconfig -o /boot/grub/grub.cfg
success "GRUB snapshot menu integration completed."
fi
else
log "Root is not Btrfs. Skipping Btrfs tool installation."
fi
# ------------------------------------------------------------------------------
# 2. Audio & Video
# ------------------------------------------------------------------------------
section "Step 2/8" "Audio & Video"
log "Installing firmware..."
exe pacman -S --noconfirm --needed sof-firmware alsa-ucm-conf alsa-firmware
log "Installing Pipewire stack..."
exe pacman -S --noconfirm --needed pipewire lib32-pipewire wireplumber pipewire-pulse pipewire-alsa pipewire-jack
exe systemctl --global enable pipewire pipewire-pulse wireplumber
success "Audio setup complete."
# ------------------------------------------------------------------------------
# 3. Locale
# ------------------------------------------------------------------------------
section "Step 3/8" "Locale Configuration"
# 标记是否需要重新生成
NEED_GENERATE=false
# --- 1. 检测 en_US.UTF-8 ---
if locale -a | grep -iq "en_US.utf8"; then
success "English locale (en_US.UTF-8) is active."
else
log "Enabling en_US.UTF-8..."
# 使用 sed 取消注释
sed -i 's/^#\s*en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
NEED_GENERATE=true
fi
# --- 2. 检测 zh_CN.UTF-8 ---
if locale -a | grep -iq "zh_CN.utf8"; then
success "Chinese locale (zh_CN.UTF-8) is active."
else
log "Enabling zh_CN.UTF-8..."
# 使用 sed 取消注释
sed -i 's/^#\s*zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/' /etc/locale.gen
NEED_GENERATE=true
fi
# --- 3. 如果有修改,统一执行生成 ---
if [ "$NEED_GENERATE" = true ]; then
log "Generating locales (this may take a moment)..."
if exe locale-gen; then
success "Locales generated successfully."
else
error "Locale generation failed."
fi
else
success "All locales are already up to date."
fi
# ------------------------------------------------------------------------------
# 4. Input Method
# ------------------------------------------------------------------------------
section "Step 4/8" "Input Method (Fcitx5)"
# chinese-addons备用ice为主
exe pacman -S --noconfirm --needed fcitx5-im fcitx5-rime rime-ice-git
success "Fcitx5 installed."
# ------------------------------------------------------------------------------
# 5. Bluetooth (Smart Detection)
# ------------------------------------------------------------------------------
section "Step 5/8" "Bluetooth"
# Ensure detection tools are present
log "Detecting Bluetooth hardware..."
exe pacman -S --noconfirm --needed usbutils pciutils
BT_FOUND=false
# 1. Check USB
if lsusb | grep -qi "bluetooth"; then BT_FOUND=true; fi
# 2. Check PCI
if lspci | grep -qi "bluetooth"; then BT_FOUND=true; fi
# 3. Check RFKill
if rfkill list bluetooth >/dev/null 2>&1; then BT_FOUND=true; fi
if [ "$BT_FOUND" = true ]; then
info_kv "Hardware" "Detected"
log "Installing Bluez "
exe pacman -S --noconfirm --needed bluez bluetui
exe systemctl enable --now bluetooth
success "Bluetooth service enabled."
else
info_kv "Hardware" "Not Found"
warn "No Bluetooth device detected. Skipping installation."
fi
# ------------------------------------------------------------------------------
# 6. Power
# ------------------------------------------------------------------------------
section "Step 6/8" "Power Management"
exe pacman -S --noconfirm --needed power-profiles-daemon
exe systemctl enable --now power-profiles-daemon
success "Power profiles daemon enabled."
# ------------------------------------------------------------------------------
# 7. Fastfetch
# ------------------------------------------------------------------------------
section "Step 7/8" "Fastfetch"
exe pacman -S --noconfirm --needed fastfetch gdu btop cmatrix lolcat sl
success "Fastfetch installed."
log "Module 02 completed."
# ------------------------------------------------------------------------------
# 9. flatpak
# ------------------------------------------------------------------------------
exe pacman -S --noconfirm --needed flatpak
exe flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
CURRENT_TZ=$(readlink -f /etc/localtime)
IS_CN_ENV=false
if [[ "$CURRENT_TZ" == *"Shanghai"* ]] || [ "$CN_MIRROR" == "1" ] || [ "$DEBUG" == "1" ]; then
IS_CN_ENV=true
info_kv "Region" "China Optimization Active"
fi
if [ "$IS_CN_ENV" = true ]; then
select_flathub_mirror
else
log "Using Global Sources."
fi

View File

@ -0,0 +1,91 @@
#!/bin/bash
# ==============================================================================
# Script: 02a-dualboot-fix.sh
# Purpose: Auto-configure for Windows dual-boot (OS-Prober only).
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/00-utils.sh"
check_root
# --- GRUB Installation Check ---
if ! command -v grub-mkconfig &>/dev/null || [ ! -f "/etc/default/grub" ]; then
warn "GRUB is not detected. Skipping dual-boot configuration."
exit 0
fi
# --- Helper Functions ---
# Sets a GRUB key-value pair.
set_grub_value() {
local key="$1"
local value="$2"
local conf_file="/etc/default/grub"
local escaped_value
escaped_value=$(printf '%s\n' "$value" | sed 's,[\/&],\\&,g')
if grep -q -E "^#\s*$key=" "$conf_file"; then
exe sed -i -E "s,^#\s*$key=.*,$key=\"$escaped_value\"," "$conf_file"
elif grep -q -E "^$key=" "$conf_file"; then
exe sed -i -E "s,^$key=.*,$key=\"$escaped_value\"," "$conf_file"
else
log "Appending new key: $key"
echo "$key=\"$escaped_value\"" >> "$conf_file"
fi
}
# --- Main Script ---
section "Phase 2A" "Dual-Boot Configuration (Windows)"
# ------------------------------------------------------------------------------
# 1. Detect Windows
# ------------------------------------------------------------------------------
section "Step 1/2" "System Analysis"
log "Installing dual-boot detection tools (os-prober, exfat-utils)..."
exe pacman -S --noconfirm --needed os-prober exfat-utils
log "Scanning for Windows installation..."
WINDOWS_DETECTED=$(os-prober | grep -qi "windows" && echo "true" || echo "false")
if [ "$WINDOWS_DETECTED" != "true" ]; then
log "No Windows installation detected by os-prober."
log "Skipping dual-boot specific configurations."
log "Module 02a completed (Skipped)."
exit 0
fi
success "Windows installation detected."
# --- Check if already configured ---
OS_PROBER_CONFIGURED=$(grep -q -E '^\s*GRUB_DISABLE_OS_PROBER\s*=\s*(false|"false")' /etc/default/grub && echo "true" || echo "false")
if [ "$OS_PROBER_CONFIGURED" == "true" ]; then
log "Dual-boot settings seem to be already configured."
echo ""
echo -e " ${H_YELLOW}>>> It looks like your dual-boot is already set up.${NC}"
echo ""
fi
# ------------------------------------------------------------------------------
# 2. Configure GRUB for Dual-Boot
# ------------------------------------------------------------------------------
section "Step 2/2" "Enabling OS Prober"
log "Enabling OS prober to detect Windows..."
set_grub_value "GRUB_DISABLE_OS_PROBER" "false"
success "Dual-boot settings updated."
log "Regenerating GRUB configuration..."
if exe grub-mkconfig -o /boot/grub/grub.cfg; then
success "GRUB configuration regenerated successfully."
else
error "Failed to regenerate GRUB configuration."
fi
log "Module 02a completed."

153
scripts/03-user.sh Normal file
View File

@ -0,0 +1,153 @@
#!/bin/bash
# ==============================================================================
# 03-user.sh - User Account & Environment Setup (Compatible with detect_target_user)
# ==============================================================================
# 1. 加载工具集
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/00-utils.sh"
# 2. 检查 Root 权限
check_root
# ==============================================================================
# Phase 1: 用户识别与账户同步
# ==============================================================================
section "Phase 3" "User Account Setup"
# 清理缓存
if [ -f "/tmp/shorin_install_user" ]; then
rm "/tmp/shorin_install_user"
fi
# 调用全局函数,确定目标用户
detect_target_user
# 检查系统是否已经真的创建了这个账户
if id "$TARGET_USER" &>/dev/null; then
success "User '${TARGET_USER}' already exists in the system."
SKIP_CREATION=true
else
log "User '${TARGET_USER}' does not exist. Preparing for creation..."
SKIP_CREATION=false
fi
# ==============================================================================
# Phase 2: 账户创建、权限与密码配置
# ==============================================================================
section "Step 2/4" "Account & Privileges"
if [ "$SKIP_CREATION" = true ]; then
log "Ensuring $TARGET_USER belongs to 'wheel' group..."
if groups "$TARGET_USER" | grep -q "\bwheel\b"; then
success "User is already in 'wheel' group."
else
log "Adding user to 'wheel' group..."
exe usermod -aG wheel "$TARGET_USER"
fi
else
log "Creating new user '${TARGET_USER}'..."
# 使用 -m 创建家目录,-g wheel 加入特权组
exe useradd -m -G wheel -s /bin/bash "$TARGET_USER"
log "Setting password for ${TARGET_USER}..."
echo -e " ${H_GRAY}--------------------------------------------------${NC}"
# passwd 必须交互运行
passwd "$TARGET_USER"
PASSWORD_STATUS=$?
echo -e " ${H_GRAY}--------------------------------------------------${NC}"
if [ $PASSWORD_STATUS -eq 0 ]; then
success "Password set successfully."
else
error "Failed to set password. Script aborted."
exit 1
fi
fi
# 1. 配置 Sudoers
log "Configuring sudoers access..."
# A. 确保 wheel 组具备基础 sudo 权限 (需要密码)
if grep -q "^# %wheel ALL=(ALL:ALL) ALL" /etc/sudoers; then
exe sed -i 's/^# %wheel ALL=(ALL:ALL) ALL/%wheel ALL=(ALL:ALL) ALL/' /etc/sudoers
success "Uncommented %wheel in /etc/sudoers."
elif grep -q "^%wheel ALL=(ALL:ALL) ALL" /etc/sudoers; then
success "Sudo access already enabled."
else
log "Appending %wheel rule to /etc/sudoers..."
echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers
success "Sudo access configured."
fi
# B. 配置免密规则 (pacman, systemctl, sudoedit)
SUDO_CONF_FILE="/etc/sudoers.d/10-shorin-nopasswd"
log "Installing specialized NOPASSWD rules..."
cat << EOF > "$SUDO_CONF_FILE"
# Shorin Setup: Essential tools NOPASSWD for wheel group
%wheel ALL=(ALL:ALL) NOPASSWD: /usr/bin/pacman, /usr/bin/systemctl, /usr/bin/sudoedit
EOF
exe chmod 440 "$SUDO_CONF_FILE"
success "Rules installed to $SUDO_CONF_FILE"
# 2. 配置 Faillock (防止输错密码锁定)
log "Configuring password lockout policy (faillock)..."
FAILLOCK_CONF="/etc/security/faillock.conf"
if [ -f "$FAILLOCK_CONF" ]; then
exe sed -i 's/^#\?\s*deny\s*=.*/deny = 0/' "$FAILLOCK_CONF"
success "Account lockout disabled (deny=0)."
fi
# ==============================================================================
# Phase 3: 生成 XDG 用户目录
# ==============================================================================
section "Step 3/4" "User Directories"
exe pacman -S --noconfirm --needed xdg-user-dirs
log "Generating XDG user directories..."
# 获取目标用户最新的家目录路径
REAL_HOME=$(getent passwd "$TARGET_USER" | cut -d: -f6)
# 强制以该用户身份运行更新
if exe runuser -u "$TARGET_USER" -- env LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 HOME="$REAL_HOME" xdg-user-dirs-update --force; then
success "Directories created in $REAL_HOME."
else
warn "Failed to generate standard directories."
fi
# ==============================================================================
# Phase 4: 环境配置 (PATH 与 .local/bin)
# ==============================================================================
section "Step 4/4" "Environment Setup"
LOCAL_BIN_PATH="$REAL_HOME/.local/bin"
log "Setting up user executable path: $LOCAL_BIN_PATH"
if exe runuser -u "$TARGET_USER" -- mkdir -p "$LOCAL_BIN_PATH"; then
success "Directory ready."
else
error "Failed to create ~/.local/bin"
fi
# 配置全局 PATH
PROFILE_SCRIPT="/etc/profile.d/user_local_bin.sh"
cat << 'EOF' > "$PROFILE_SCRIPT"
# Automatically add ~/.local/bin to PATH if it exists
if [ -d "$HOME/.local/bin" ]; then
export PATH="$HOME/.local/bin:$PATH"
fi
EOF
exe chmod 644 "$PROFILE_SCRIPT"
success "PATH optimization script installed."
# ==============================================================================
# 完成
# ==============================================================================
hr
success "User setup module for '${TARGET_USER}' completed."
echo ""

211
scripts/03b-gpu-driver.sh Normal file
View File

@ -0,0 +1,211 @@
#!/bin/bash
# ==============================================================================
# 03b-gpu-driver.sh GPU Driver Installer 参考了cachyos的chwd脚本
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 引用工具库
if [ -f "$SCRIPT_DIR/00-utils.sh" ]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found."
exit 1
fi
check_root
section "Phase 2b" "GPU Driver Setup"
# ==============================================================================
# 1. 变量声明与基础信息获取
# ==============================================================================
log "Detecting GPU Hardware..."
# 核心变量:存放 lspci 信息
GPU_INFO=$(lspci -mm | grep -E -i "VGA|3D|Display")
log "GPU Info Detected:\n$GPU_INFO"
# 状态变量初始化
HAS_AMD=false
HAS_INTEL=false
HAS_NVIDIA=false
GPU_NUMBER=0
# 待安装包数组
PKGS=("libva-utils")
# ==============================================================================
# 2. 状态变更 & 基础包追加 (Base Packages)
# ==============================================================================
# --- AMD 检测 --- -q 静默,-i忽略大小写
if echo "$GPU_INFO" | grep -q -i "AMD\|ATI"; then
HAS_AMD=true
info_kv "Vendor" "AMD Detected"
# 追加 AMD 基础包
PKGS+=("mesa" "lib32-mesa" "xf86-video-amdgpu" "vulkan-radeon" "lib32-vulkan-radeon" "linux-firmware-amdgpu" "gst-plugin-va" "opencl-mesa" "lib32-opencl-mesa" "opencl-icd-loader" "lib32-opencl-icd-loader" )
fi
# --- Intel 检测 ---
if echo "$GPU_INFO" | grep -q -i "Intel"; then
HAS_INTEL=true
info_kv "Vendor" "Intel Detected"
# 追加 Intel 基础包 (保证能亮机,能跑基础桌面)
PKGS+=("mesa" "vulkan-intel" "lib32-mesa" "lib32-vulkan-intel" "gst-plugin-va" "linux-firmware-intel" "opencl-mesa" "lib32-opencl-mesa" "opencl-icd-loader" "lib32-opencl-icd-loader" )
fi
# --- NVIDIA 检测 ---
if echo "$GPU_INFO" | grep -q -i "NVIDIA"; then
HAS_NVIDIA=true
info_kv "Vendor" "NVIDIA Detected"
# 追加 NVIDIA 基础工具包
fi
# --- 多显卡检测 ---
GPU_COUNT=$(echo "$GPU_INFO" | grep -c .)
if [ "$GPU_COUNT" -ge 2 ]; then
info_kv "GPU Layout" "Dual/Multi-GPU Detected (Count: $GPU_COUNT)"
# 安装 vulkan-mesa-layers 以支持 vk-device-select
PKGS+=("vulkan-mesa-layers" "lib32-vulkan-mesa-layers")
if [[ $HAS_NVIDIA == true ]]; then
PKGS+=("nvidia-prime" "switcheroo-control")
# fix gtk4 issue with nvidia dual gpu
if grep -q "GSK_RENDERER" "/etc/environment"; then
echo 'GSK_RENDERER=gl' >> /etc/environment
fi
fi
fi
# ==============================================================================
# 3. Conditional 包判断
# ==============================================================================
# ------------------------------------------------------------------------------
# 3.1 Intel 硬件编解码判断
# ------------------------------------------------------------------------------
if [ "$HAS_INTEL" = true ]; then
if echo "$GPU_INFO" | grep -q -E -i "Arc|Xe|UHD|Iris|Raptor|Alder|Tiger|Rocket|Ice|Comet|Coffee|Kaby|Skylake|Broadwell|Gemini|Jasper|Elkhart|HD Graphics 6|HD Graphics 5[0-9][0-9]\b"; then
log " -> Intel: Modern architecture matched (iHD path)..."
PKGS+=("intel-media-driver")
else
warn " -> Intel: Legacy or Unknown model. Skipping intel-media-driver."
fi
fi
# ------------------------------------------------------------------------------
# 3.2 NVIDIA 驱动版本与内核 Headers 判断
# ------------------------------------------------------------------------------
if [ "$HAS_NVIDIA" = true ]; then
NV_MODEL=$(echo "$GPU_INFO" | grep -i "NVIDIA" | head -n 1)
# 初始化一个标志位,只有匹配到支持的显卡才设为 true
DRIVER_SELECTED=false
# ==========================================================================
# nvidia-open
# ==========================================================================
if echo "$NV_MODEL" | grep -q -E -i "RTX|GTX 16"; then
log " -> NVIDIA: Modern GPU detected (Turing+). Using Open Kernel Modules."
# 核心驱动包
PKGS+=("nvidia-open-dkms" "nvidia-utils" "lib32-nvidia-utils" "opencl-nvidia" "lib32-opencl-nvidia" "libva-nvidia-driver" "vulkan-icd-loader" "lib32-vulkan-icd-loader" "opencl-icd-loader" "lib32-opencl-icd-loader")
DRIVER_SELECTED=true
# ==========================================================================
# nvidia-580xx-dkms
# ==========================================================================
elif echo "$NV_MODEL" | grep -q -E -i "GTX 10|GTX 950|GTX 960|GTX 970|GTX 980|GTX 745|GTX 750|GTX 750 Ti|GTX 840M|GTX 845M|GTX 850M|GTX 860M|GTX 950M|GTX 960M|GeForce 830M|GeForce 840M|GeForce 930M|GeForce 940M|GeForce GTX Titan X|Tegra X1|NVIDIA Titan X|NVIDIA Titan Xp|NVIDIA Titan V|NVIDIA Quadro GV100"; then
log " -> NVIDIA: Pascal/Maxwell GPU detected. Using Proprietary DKMS."
PKGS+=("nvidia-580xx-dkms" "nvidia-580xx-utils" "opencl-nvidia-580xx" "lib32-opencl-nvidia-580xx" "lib32-nvidia-580xx-utils" "libva-nvidia-driver" "vulkan-icd-loader" "lib32-vulkan-icd-loader" "opencl-icd-loader" "lib32-opencl-icd-loader" )
DRIVER_SELECTED=true
# ==========================================================================
# nvidia-470xx-dkms
# ==========================================================================
elif echo "$NV_MODEL" | grep -q -E -i "GTX 6[0-9][0-9]|GTX 760|GTX 765|GTX 770|GTX 775|GTX 780|GTX 860M|GT 6[0-9][0-9]|GT 710M|GT 720|GT 730M|GT 735M|GT 740|GT 745M|GT 750M|GT 755M|GT 920M|Quadro 410|Quadro K500|Quadro K510|Quadro K600|Quadro K610|Quadro K1000|Quadro K1100|Quadro K2000|Quadro K2100|Quadro K3000|Quadro K3100|Quadro K4000|Quadro K4100|Quadro K5000|Quadro K5100|Quadro K6000|Tesla K10|Tesla K20|Tesla K40|Tesla K80|NVS 510|NVS 1000|Tegra K1|Titan|Titan Z"; then
log " -> NVIDIA: Kepler GPU detected. Using nvidia-470xx-dkms."
PKGS+=("nvidia-470xx-dkms" "nvidia-470xx-utils" "opencl-nvidia-470xx" "vulkan-icd-loader" "lib32-nvidia-470xx-utils" "lib32-opencl-nvidia-470xx" "lib32-vulkan-icd-loader" "libva-nvidia-driver" "opencl-icd-loader" "lib32-opencl-icd-loader")
DRIVER_SELECTED=true
# ==========================================================================
# others
# ==========================================================================
else
warn " -> NVIDIA: Legacy GPU detected ($NV_MODEL)."
warn " -> Please manually install GPU driver."
fi
# ==========================================================================
# headers
# ==========================================================================
if [ "$DRIVER_SELECTED" = true ]; then
log " -> NVIDIA: Scanning installed kernels for headers..."
# 1. 获取所有以 linux 开头的候选包
CANDIDATES=$(pacman -Qq | grep "^linux" | grep -vE "headers|firmware|api|docs|tools|utils|qq")
for kernel in $CANDIDATES; do
# 2. 验证:只有在 /boot 下存在对应 vmlinuz 文件的才算是真内核
if [ -f "/boot/vmlinuz-${kernel}" ]; then
HEADER_PKG="${kernel}-headers"
log " + Kernel found: $kernel -> Adding $HEADER_PKG"
PKGS+=("$HEADER_PKG")
fi
done
fi
fi
if systemd-detect-virt -q; then
log "virtualmachine detected"
PKGS+=("spice-vdagent")
fi
# ==============================================================================
# 4. 执行
# ==============================================================================
DETECTED_USER=$(awk -F: '$3 == 1000 {print $1}' /etc/passwd)
TARGET_USER="${DETECTED_USER:-$(read -p "Target user: " u && echo $u)}"
#--------------sudo temp 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..."
# 定义清理函数:无论脚本是成功结束还是意外中断(Ctrl+C),都确保删除免密文件
cleanup_sudo() {
if [ -f "$SUDO_TEMP_FILE" ]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
# 注册陷阱:在脚本退出(EXIT)或被中断(INT/TERM)时触发清理
trap cleanup_sudo EXIT INT TERM
if [ ${#PKGS[@]} -gt 0 ]; then
# 数组去重
UNIQUE_PKGS=($(printf "%s\n" "${PKGS[@]}" | sort -u))
section "Installation" "Installing Packages"
log "Target Packages: ${UNIQUE_PKGS[*]}"
# 执行安装
exe runuser -u "$TARGET_USER" -- yay -S --noconfirm --needed --answerdiff=None --answerclean=None "${UNIQUE_PKGS[@]}"
log "Enabling services (if supported)..."
systemctl enable --now nvidia-powerd &>/dev/null || true
systemctl enable switcheroo-control.service &>/dev/null || true
systemctl enable nvidia-suspend.service &>/dev/null || true
systemctl enable nvidia-hibernate.service &>/dev/null || true
systemctl enable nvidia-resume.service &>/dev/null || true
success "GPU Drivers processed successfully."
else
warn "No GPU drivers matched or needed."
fi
log "Module 02b completed."

View File

@ -0,0 +1,87 @@
#!/bin/bash
# ==============================================================================
# 03c-snapshot-before-desktop.sh
# Creates a system snapshot before installing major Desktop Environments.
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 1. 引用工具库
if [ -f "$SCRIPT_DIR/00-utils.sh" ]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found."
exit 1
fi
# 2. 权限检查
check_root
section "Phase 3c" "System Snapshot"
# ==============================================================================
create_checkpoint() {
local MARKER="Before Desktop Environments"
# 0. 检查 snapper 是否安装
if ! command -v snapper &>/dev/null; then
warn "Snapper tool not found. Skipping snapshot creation."
return
fi
# 1. Root 分区快照
# 检查 root 配置是否存在
if snapper -c root get-config &>/dev/null; then
# 检查是否已存在同名快照 (避免重复创建)
if snapper -c root list --columns description | grep -Fqx "$MARKER"; then
log "Snapshot '$MARKER' already exists on [root]."
else
log "Creating safety checkpoint on [root]..."
# 使用 --type single 表示这是一个独立的存档点
snapper -c root create --description "$MARKER"
success "Root snapshot created."
fi
else
warn "Snapper 'root' config not configured. Skipping root snapshot."
fi
# 2. Home 分区快照 (如果存在 home 配置)
if snapper -c home get-config &>/dev/null; then
if snapper -c home list --columns description | grep -Fqx "$MARKER"; then
log "Snapshot '$MARKER' already exists on [home]."
else
log "Creating safety checkpoint on [home]..."
snapper -c home create --description "$MARKER"
success "Home snapshot created."
fi
fi
}
# ==============================================================================
# 执行
# ==============================================================================
# --- Identify User & DM Check ---
log "Identifying target user..."
detect_target_user
if [[ -z "$TARGET_USER" || ! -d "$HOME_DIR" ]]; then
error "Target user invalid or home directory does not exist."
exit 1
fi
log "Preparing to create restore point..."
create_checkpoint
HYRPLAND_AUTOSTART="$HOME_DIR/.config/systemd/user/hyprland-autostart.service"
NIRI_AUTOSTART="$HOME_DIR/.config/systemd/user/niri-autostart.service"
if [ -f "$HYPRLAND_AUTOSTART" ]; then
log "Removing existing Hyprland autostart service..."
rm -f "$HYPRLAND_AUTOSTART"
fi
if [ -f "$NIRI_AUTOSTART" ]; then
log "Removing existing Niri autostart service..."
rm -f "$NIRI_AUTOSTART"
fi
log "Module 03c completed."

617
scripts/04-niri-setup.sh Normal file
View File

@ -0,0 +1,617 @@
#!/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."

View File

@ -0,0 +1,435 @@
#!/bin/bash
# ==============================================================================
# 06-kdeplasma-setup.sh - KDE Plasma Setup (FZF Menu + Robust Installation)
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/00-utils.sh"
DEBUG=${DEBUG:-0}
CN_MIRROR=${CN_MIRROR:-0}
check_root
# 初始化 Verify 列表
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST"
# 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
section "Phase 6" "KDE Plasma Environment"
# ------------------------------------------------------------------------------
# 0. Identify Target User & DM Check
# ------------------------------------------------------------------------------
detect_target_user
info_kv "Target" "$TARGET_USER"
# 调用 Utils 函数进行冲突检测 (会自动设置 $SKIP_DM 变量)
check_dm_conflict
# ------------------------------------------------------------------------------
# 1. Install KDE Plasma Base
# ------------------------------------------------------------------------------
section "Step 1/5" "Plasma Core"
log "Installing KDE Plasma Meta & Apps..."
KDE_PKGS="plasma-meta konsole dolphin kate firefox qt6-multimedia-ffmpeg pipewire-jack plasma-login-manager"
# 注入 Verify 列表
echo "$KDE_PKGS" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed $KDE_PKGS
success "KDE Plasma installed."
# ------------------------------------------------------------------------------
# 2. Software Store & Network (Smart Mirror Selection)
# ------------------------------------------------------------------------------
section "Step 2/5" "Software Store & Network"
log "Configuring Discover & Flatpak..."
FLATPAK_PKGS="flatpak flatpak-kcm"
echo "$FLATPAK_PKGS" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed $FLATPAK_PKGS
exe flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
# --- Network Detection Logic ---
CURRENT_TZ=$(readlink -f /etc/localtime)
IS_CN_ENV=false
if [[ "$CURRENT_TZ" == *"Shanghai"* ]]; then
IS_CN_ENV=true
info_kv "Region" "China (Timezone)"
elif [ "$CN_MIRROR" == "1" ]; then
IS_CN_ENV=true
info_kv "Region" "China (Manual Env)"
elif [ "$DEBUG" == "1" ]; then
IS_CN_ENV=true
warn "DEBUG MODE: Forcing China Environment"
fi
# --- Mirror Configuration ---
if [ "$IS_CN_ENV" = true ]; then
log "Enabling China Optimizations..."
select_flathub_mirror
success "Optimizations Enabled."
else
log "Using Global Official Sources."
fi
# NOPASSWD for yay
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"
# ------------------------------------------------------------------------------
# 3. Install Dependencies (FZF Selection + Retry Logic)
# ------------------------------------------------------------------------------
section "Step 3/5" "KDE Dependencies"
LIST_FILE="$PARENT_DIR/kde-applist.txt"
UNDO_SCRIPT="$PARENT_DIR/undochange.sh"
# --- Critical Failure Handler ---
critical_failure_handler() {
local failed_pkg="$1"
# Disable trap to prevent loops
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# Package: $failed_pkg #\033[0m"
echo -e "\033[0;31m# Status: Package not found after install attempt. #\033[0m"
echo -e "\033[0;31m# #\033[0m"
echo -e "\033[0;31m# Would you like to restore snapshot (undo changes)? #\033[0m"
echo -e "\033[0;31m################################################################\033[0m"
echo ""
while true; do
read -p "Execute System Recovery? [y/n]: " -r choice
case "$choice" in
[yY][eE][sS]|[yY])
if [ -f "$UNDO_SCRIPT" ]; then
warn "Executing recovery script: $UNDO_SCRIPT"
bash "$UNDO_SCRIPT"
exit 1
else
error "Recovery script not found at: $UNDO_SCRIPT"
exit 1
fi
;;
[nN][oO]|[nN])
warn "User chose NOT to recover. System might be in a broken state."
error "Installation aborted due to failure in: $failed_pkg"
exit 1
;;
*)
echo -e "\033[1;33mInvalid input. Please enter 'y' to recover or 'n' to abort.\033[0m"
;;
esac
done
}
# --- Verification Function ---
verify_installation() {
local pkg="$1"
if pacman -Q "$pkg" &>/dev/null; then return 0; else return 1; fi
}
if [ -f "$LIST_FILE" ]; then
REPO_APPS=()
AUR_APPS=()
# ---------------------------------------------------------
# 3.1 Countdown Logic
# ---------------------------------------------------------
if ! grep -q -vE "^\s*#|^\s*$" "$LIST_FILE"; then
warn "App list is empty. Skipping."
else
echo ""
echo -e " Selected List: ${BOLD}$LIST_FILE${NC}"
echo -e " ${H_YELLOW}>>> Default installation will start in 60 seconds.${NC}"
echo -e " ${H_CYAN}>>> Press ANY KEY to customize selection...${NC}"
if read -t 60 -n 1 -s -r; then
USER_INTERVENTION=true
else
USER_INTERVENTION=false
fi
# ---------------------------------------------------------
# 3.2 FZF Selection Logic
# ---------------------------------------------------------
SELECTED_RAW=""
if [ "$USER_INTERVENTION" = true ]; then
clear
echo -e "\n Loading package list..."
# Visual: Name <TAB> # Description
SELECTED_RAW=$(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=right:45%:wrap:border-left \
--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
warn "User cancelled selection. Skipping Step 3."
# Empty arrays
fi
else
log "Timeout reached (60s). Auto-confirming ALL packages."
# Simulate FZF output for consistent processing
SELECTED_RAW=$(grep -vE "^\s*#|^\s*$" "$LIST_FILE" | sed -E 's/[[:space:]]+#/\t#/')
fi
# ---------------------------------------------------------
# 3.3 Categorize Selection
# ---------------------------------------------------------
if [ -n "$SELECTED_RAW" ]; then
log "Processing selection..."
while IFS= read -r line; do
# Extract Name (Before TAB)
raw_pkg=$(echo "$line" | cut -f1 -d$'\t' | xargs)
[[ -z "$raw_pkg" ]] && continue
# Legacy compatibility (imagemagick)
[ "$raw_pkg" == "imagemagic" ] && raw_pkg="imagemagick"
# Identify AUR vs Repo
if [[ "$raw_pkg" == AUR:* ]]; then
clean_name="${raw_pkg#AUR:}"
AUR_APPS+=("$clean_name")
elif [[ "$raw_pkg" == *"-git" ]]; then
# Implicit AUR if ending in -git (Legacy logic support)
AUR_APPS+=("$raw_pkg")
else
REPO_APPS+=("$raw_pkg")
fi
done <<< "$SELECTED_RAW"
fi
fi
info_kv "Scheduled" "Repo: ${#REPO_APPS[@]}" "AUR: ${#AUR_APPS[@]}"
# 注入 FZF 选中的包到 Verify 列表
echo "${REPO_APPS[@]}" >> "$VERIFY_LIST"
echo "${AUR_APPS[@]}" >> "$VERIFY_LIST"
# ---------------------------------------------------------
# 3.4 Install Applications
# ---------------------------------------------------------
# --- A. Install Repo Apps (BATCH MODE) ---
if [ ${#REPO_APPS[@]} -gt 0 ]; then
log "Phase 1: Batch Installing Repository Packages..."
# Filter installed
REPO_QUEUE=()
for pkg in "${REPO_APPS[@]}"; do
if ! pacman -Qi "$pkg" &>/dev/null; then
REPO_QUEUE+=("$pkg")
fi
done
if [ ${#REPO_QUEUE[@]} -gt 0 ]; then
# Batch Install
exe runuser -u "$TARGET_USER" -- yay -S --noconfirm --needed --answerdiff=None --answerclean=None "${REPO_QUEUE[@]}"
# Verify Loop
log "Verifying batch installation..."
for pkg in "${REPO_QUEUE[@]}"; do
if ! verify_installation "$pkg"; then
warn "Verification failed for '$pkg'. Retrying individually..."
exe runuser -u "$TARGET_USER" -- yay -S --noconfirm --needed "$pkg"
if ! verify_installation "$pkg"; then
critical_failure_handler "$pkg (Repo)"
else
success "Verified: $pkg"
fi
fi
done
success "Batch phase verified."
else
log "All selected repo packages are already installed."
fi
fi
# --- B. Install AUR Apps (SEQUENTIAL + RETRY) ---
if [ ${#AUR_APPS[@]} -gt 0 ]; then
log "Phase 2: Installing AUR Packages (Sequential)..."
log "Hint: Use Ctrl+C to skip a specific package download step."
for aur_pkg in "${AUR_APPS[@]}"; do
if pacman -Qi "$aur_pkg" &>/dev/null; then
log "Skipping '$aur_pkg' (Already installed)."
continue
fi
log "Installing AUR: $aur_pkg ..."
install_success=false
max_retries=2
for (( i=0; i<=max_retries; i++ )); do
if [ $i -gt 0 ]; then
warn "Retry $i/$max_retries for '$aur_pkg' in 3 seconds..."
sleep 3
fi
runuser -u "$TARGET_USER" -- yay -S --noconfirm --needed --answerdiff=None --answerclean=None "$aur_pkg"
EXIT_CODE=$?
# Handle Ctrl+C skip
if [ $EXIT_CODE -eq 130 ]; then
warn "Skipped '$aur_pkg' by user request (Ctrl+C)."
break # Skip retries for this package
fi
if verify_installation "$aur_pkg"; then
install_success=true
success "Installed $aur_pkg"
break
else
warn "Attempt $((i+1)) failed for $aur_pkg"
fi
done
if [ "$install_success" = false ] && [ $EXIT_CODE -ne 130 ]; then
# Trigger critical failure if not skipped by user
critical_failure_handler "$aur_pkg (AUR)"
fi
done
fi
else
warn "kde-applist.txt not found."
fi
# ------------------------------------------------------------------------------
# 4. Dotfiles Deployment
# ------------------------------------------------------------------------------
section "Step 4/5" "KDE Config Deployment"
DOTFILES_SOURCE="$PARENT_DIR/kde-dotfiles"
if [ -d "$DOTFILES_SOURCE" ]; then
log "Deploying KDE configurations..."
# 1. Backup Existing .config
BACKUP_NAME="config_backup_kde_$(date +%s).tar.gz"
if [ -d "$HOME_DIR/.config" ]; then
log "Backing up ~/.config to $BACKUP_NAME..."
exe runuser -u "$TARGET_USER" -- tar -czf "$HOME_DIR/$BACKUP_NAME" -C "$HOME_DIR" .config
fi
# 2. Explicitly Copy .config and .local
# --- Process .config ---
if [ -d "$DOTFILES_SOURCE/.config" ]; then
log "Merging .config..."
if [ ! -d "$HOME_DIR/.config" ]; then mkdir -p "$HOME_DIR/.config"; fi
exe cp -rf "$DOTFILES_SOURCE/.config/"* "$HOME_DIR/.config/" 2>/dev/null || true
exe cp -rf "$DOTFILES_SOURCE/.config/." "$HOME_DIR/.config/" 2>/dev/null || true
log "Fixing permissions for .config..."
exe chown -R "$TARGET_USER" "$HOME_DIR/.config"
fi
# --- Process .local ---
if [ -d "$DOTFILES_SOURCE/.local" ]; then
log "Merging .local..."
if [ ! -d "$HOME_DIR/.local" ]; then mkdir -p "$HOME_DIR/.local"; fi
exe cp -rf "$DOTFILES_SOURCE/.local/"* "$HOME_DIR/.local/" 2>/dev/null || true
exe cp -rf "$DOTFILES_SOURCE/.local/." "$HOME_DIR/.local/" 2>/dev/null || true
log "Fixing permissions for .local..."
exe chown -R "$TARGET_USER" "$HOME_DIR/.local"
fi
success "KDE Dotfiles applied and permissions fixed."
else
warn "Folder 'kde-dotfiles' not found in repo. Skipping config."
fi
# ------------------------------------------------------------------------------
# 4.5 Deploy Resource Files (README)
# ------------------------------------------------------------------------------
log "Deploying desktop resources..."
SOURCE_README="$PARENT_DIR/resources/KDE-README.txt"
DESKTOP_DIR="$HOME_DIR/Desktop"
if [ ! -d "$DESKTOP_DIR" ]; then
exe runuser -u "$TARGET_USER" -- mkdir -p "$DESKTOP_DIR"
fi
if [ -f "$SOURCE_README" ]; then
log "Copying KDE-README.txt..."
exe cp "$SOURCE_README" "$DESKTOP_DIR/"
exe chown "$TARGET_USER" "$DESKTOP_DIR/KDE-README.txt"
success "Readme deployed."
else
warn "resources/KDE-README.txt not found."
fi
# ------------------------------------------------------------------------------
# 5. Enable Display Manager (SDDM)
# ------------------------------------------------------------------------------
section "Step 5/5" "Enable Display Manager"
if [ "$SKIP_DM" = true ]; then
log "Display Manager setup skipped (Conflict found or user opted out)."
else
systemctl enable plasmalogin
fi
# === 隐藏多余的 Desktop 图标 ===
section "Config" "Hiding useless .desktop files"
log "Hiding useless .desktop files"
run_hide_desktop_file
as_user shorin link
# ------------------------------------------------------------------------------
# Cleanup
# ------------------------------------------------------------------------------
section "Cleanup" "Restoring State"
rm -f "$SUDO_TEMP_FILE"
success "Done."
log "Module 06 completed."

View File

@ -0,0 +1,361 @@
#!/bin/bash
# 04c-quickshell-setup.sh
# 1. 引用工具库
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [[ -f "$SCRIPT_DIR/00-utils.sh" ]]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found in $SCRIPT_DIR."
exit 1
fi
# ==============================================================================
# 核心辅助函数定义
# ==============================================================================
# --- 函数:强制安全拷贝(解决覆盖冲突) ---
force_copy() {
local src="$1"
local target_dir="$2"
if [[ -z "$src" || -z "$target_dir" ]]; then
warn "force_copy: Missing arguments"
return 1
fi
local item_name
item_name=$(basename "$src")
# 只有当拷贝的不是 "目录下的所有内容" (即路径不以 /. 结尾) 时,才执行精确删除
if [[ "$src" != */. ]]; then
# 清理 target_dir 结尾多余的斜杠或 /.
local clean_target="${target_dir%/}"
clean_target="${clean_target%/.}"
# 先安全删除目标路径的同名内容,防止目录覆盖文件的冲突
as_user rm -rf "${clean_target}/${item_name}"
fi
# 执行安全拷贝
exe as_user cp -rf "$src" "$target_dir"
}
# --- 函数:静默删除 niri 绑定 ---
niri_remove_bind() {
local target_key="$1"
local config_file="$HOME_DIR/.config/niri/dms/binds.kdl"
if [[ ! -f "$config_file" ]]; then
return 1
fi
# 使用 Python 处理,无日志,无备份
python3 -c "
import sys, re
file_path = '$config_file'
target_key = sys.argv[1]
try:
with open(file_path, 'r') as f:
content = f.read()
pattern = re.compile(r'(?m)^\s*(?!//).*?' + re.escape(target_key) + r'(?=\s|\{)')
while True:
match = pattern.search(content)
if not match:
break
start_idx = match.start()
open_brace_idx = content.find('{', start_idx)
if open_brace_idx == -1:
break
balance = 0
end_idx = -1
for i in range(open_brace_idx, len(content)):
char = content[i]
if char == '{':
balance += 1
elif char == '}':
balance -= 1
if balance == 0:
end_idx = i + 1
break
if end_idx != -1:
if end_idx < len(content) and content[end_idx] == '\n':
end_idx += 1
content = content[:start_idx] + content[end_idx:]
else:
break
with open(file_path, 'w') as f:
f.write(content)
except Exception:
pass
" "$target_key"
}
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST"
log "Installing DMS..."
# ==============================================================================
# Identify User & DM Check
# ==============================================================================
log "Identifying user..."
detect_target_user
if [[ -z "$TARGET_USER" || ! -d "$HOME_DIR" ]]; then
error "Target user invalid or home directory does not exist."
exit 1
fi
info_kv "Target" "$TARGET_USER"
# DM Check
check_dm_conflict
log "Target user for DMS installation: $TARGET_USER"
# 下载并执行安装脚本
INSTALLER_SCRIPT="/tmp/dms_install.sh"
DMS_URL="https://install.danklinux.com"
log "Downloading DMS installer wrapper..."
if curl -fsSL "$DMS_URL" -o "$INSTALLER_SCRIPT"; then
chmod +x "$INSTALLER_SCRIPT"
chown "$TARGET_USER" "$INSTALLER_SCRIPT"
log "Executing DMS installer as user ($TARGET_USER)..."
log "NOTE: If the installer asks for input, this script might hang."
pacman -S --noconfirm vulkan-headers
if runuser -u "$TARGET_USER" -- bash -c "cd ~ && $INSTALLER_SCRIPT"; then
success "DankMaterialShell installed successfully."
else
warn "DMS installer returned an error code. You may need to install it manually."
exit 1
fi
rm -f "$INSTALLER_SCRIPT"
else
warn "Failed to download DMS installer script from $DMS_URL."
fi
# ==============================================================================
# dms 随图形化环境自动启动
# ==============================================================================
section "Config" "dms autostart"
DMS_AUTOSTART_LINK="$HOME_DIR/.config/systemd/user/niri.service.wants/dms.service"
DMS_NIRI_CONFIG_FILE="$HOME_DIR/.config/niri/config.kdl"
DMS_HYPR_CONFIG_FILE="$HOME_DIR/.config/hypr/hyprland.conf"
if [[ -L "$DMS_AUTOSTART_LINK" ]]; then
log "Detect DMS systemd service enabled, disabling ...."
rm -f "$DMS_AUTOSTART_LINK"
fi
DMS_NIRI_INSTALLED="false"
DMS_HYPR_INSTALLED="false"
if command -v niri &>/dev/null; then
DMS_NIRI_INSTALLED="true"
elif command -v hyprland &>/dev/null; then
DMS_HYPR_INSTALLED="true"
fi
if [[ "$DMS_NIRI_INSTALLED" == "true" ]]; then
if ! grep -E -q "^[[:space:]]*spawn-at-startup.*dms.*run" "$DMS_NIRI_CONFIG_FILE"; then
log "Enabling DMS autostart in niri config.kdl..."
echo 'spawn-at-startup "dms" "run"' >> "$DMS_NIRI_CONFIG_FILE"
echo 'spawn-at-startup "xhost" "+si:localuser:root"' >> "$DMS_NIRI_CONFIG_FILE"
else
log "DMS autostart already exists in niri config.kdl, skipping."
fi
elif [[ "$DMS_HYPR_INSTALLED" == "true" ]]; then
log "Configuring Hyprland autostart..."
if ! grep -q "exec-once.*dms run" "$DMS_HYPR_CONFIG_FILE"; then
log "Adding DMS autostart to hyprland.conf"
echo 'exec-once = dms run' >> "$DMS_HYPR_CONFIG_FILE"
echo 'exec-once = xhost +si:localuser:root'>> "$DMS_HYPR_CONFIG_FILE"
else
log "DMS autostart already exists in Hyprland config, skipping."
fi
fi
# ==============================================================================
# fcitx5 configuration and locale
# ==============================================================================
section "Config" "input method"
if [[ "$DMS_NIRI_INSTALLED" == "true" ]]; then
if ! grep -q "fcitx5" "$DMS_NIRI_CONFIG_FILE"; then
log "Enabling fcitx5 autostart in niri config.kdl..."
echo 'spawn-at-startup "fcitx5" "-d"' >> "$DMS_NIRI_CONFIG_FILE"
else
log "Fcitx5 autostart already exists, skipping."
fi
if grep -q "^[[:space:]]*environment[[:space:]]*{" "$DMS_NIRI_CONFIG_FILE"; then
log "Existing environment block found. Injecting fcitx variables..."
if ! grep -q 'XMODIFIERS "@im=fcitx"' "$DMS_NIRI_CONFIG_FILE"; then
sed -i '/^[[:space:]]*environment[[:space:]]*{/a \ LC_CTYPE "en_US.UTF-8"\n XMODIFIERS "@im=fcitx"\n LANG "zh_CN.UTF-8"' "$DMS_NIRI_CONFIG_FILE"
else
log "Environment variables for fcitx already exist, skipping."
fi
else
log "No environment block found. Appending new block..."
cat << EOT >> "$DMS_NIRI_CONFIG_FILE"
environment {
LC_CTYPE "en_US.UTF-8"
XMODIFIERS "@im=fcitx"
LANGUAGE "zh_CN.UTF-8"
LANG "zh_CN.UTF-8"
}
EOT
fi
chown -R "$TARGET_USER:" "$PARENT_DIR/quickshell-dotfiles"
# === [ 核心修复点 ] ===
# 精准清除目标路径中会导致冲突的非目录文件(软链接)
as_user rm -rf "$HOME_DIR/.local/share/fcitx5"
as_user rm -rf "$HOME_DIR/.config/fcitx5"
# =======================
force_copy "$PARENT_DIR/quickshell-dotfiles/." "$HOME_DIR/"
elif [[ "$DMS_HYPR_INSTALLED" == "true" ]]; then
if ! grep -q "fcitx5" "$DMS_HYPR_CONFIG_FILE"; then
log "Adding fcitx5 autostart to hyprland.conf"
echo 'exec-once = fcitx5 -d' >> "$DMS_HYPR_CONFIG_FILE"
cat << EOT >> "$DMS_HYPR_CONFIG_FILE"
# --- Added by Shorin-Setup Script ---
# Fcitx5 Input Method Variables
env = XMODIFIERS,@im=fcitx
env = LC_CTYPE,en_US.UTF-8
# Locale Settings
env = LANG,zh_CN.UTF-8
# ----------------------------------
EOT
else
log "Fcitx5 configuration already exists in Hyprland config, skipping."
fi
chown -R "$TARGET_USER:" "$PARENT_DIR/quickshell-dotfiles"
# === [ 核心修复点 ] ===
as_user rm -rf "$HOME_DIR/.local/share/fcitx5"
as_user rm -rf "$HOME_DIR/.config/fcitx5"
# 这里我顺手修正了原本脚本的一个小 Bug:
# 如果 quickshell-dotfiles 包含 .config 和 .local应复制到 ~ 下,而不是 ~/.config/ 下,否则会变成 ~/.config/.config
force_copy "$PARENT_DIR/quickshell-dotfiles/." "$HOME_DIR/"
fi
# ==============================================================================
# filemanager
# ==============================================================================
section "Config" "file manager"
if [[ "$DMS_NIRI_INSTALLED" == "true" ]]; then
log "DMS niri detected, configuring nautilus"
FM_PKGS="ffmpegthumbnailer gvfs-smb nautilus-open-any-terminal xdg-terminal-exec file-roller gnome-keyring gst-plugins-base gst-plugins-good gst-libav nautilus"
echo "$FM_PKGS" >> "$VERIFY_LIST"
exe as_user paru -S --noconfirm --needed $FM_PKGS
# 默认终端处理
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
as_user mkdir -p "$HOME_DIR/Templates"
as_user touch "$HOME_DIR/Templates/new"
as_user touch "$HOME_DIR/Templates/new.sh"
as_user bash -c "echo '#!/bin/bash' >> '$HOME_DIR/Templates/new.sh'"
chown -R "$TARGET_USER:" "$HOME_DIR/Templates"
configure_nautilus_user
elif [[ "$DMS_HYPR_INSTALLED" == "true" ]]; then
log "DMS hyprland detected, skipping file manager."
fi
# ==============================================================================
# screenshare
# ==============================================================================
section "Config" "screenshare"
if [[ "$DMS_NIRI_INSTALLED" == "true" ]]; then
log "DMS niri detected, configuring xdg-desktop-portal"
echo "xdg-desktop-portal-gnome" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed xdg-desktop-portal-gnome
if ! grep -q '/usr/lib/xdg-desktop-portal-gnome' "$DMS_NIRI_CONFIG_FILE"; then
log "Configuring environment in niri config.kdl"
echo 'spawn-sh-at-startup "dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=niri & /usr/lib/xdg-desktop-portal-gnome"' >> "$DMS_NIRI_CONFIG_FILE"
fi
elif [[ "$DMS_HYPR_INSTALLED" == "true" ]]; then
log "DMS hyprland detected, configuring xdg-desktop-portal"
echo "xdg-desktop-portal-hyprland" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed xdg-desktop-portal-hyprland
if ! grep -q '/usr/lib/xdg-desktop-portal-hyprland' "$DMS_HYPR_CONFIG_FILE"; then
log "Configuring environment in hyprland.conf"
echo 'exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=hyprland & /usr/lib/xdg-desktop-portal-hyprland' >> "$DMS_HYPR_CONFIG_FILE"
fi
fi
# ==============================================================================
# Validation Check: DMS & Core Components (Blackbox Audit)
# ==============================================================================
section "Config" "components validation"
log "Verifying DMS and core components installation..."
MISSING_COMPONENTS=()
if ! command -v dms &>/dev/null ; then
MISSING_COMPONENTS+=("dms")
fi
if ! command -v quickshell &>/dev/null; then
MISSING_COMPONENTS+=("quickshell")
fi
if [[ ${#MISSING_COMPONENTS[@]} -gt 0 ]]; then
error "FATAL: Official DMS installer failed to provide core binaries!"
warn "Missing core commands: ${MISSING_COMPONENTS[*]}"
write_log "FATAL" "DMS Blackbox installation failed. Missing: ${MISSING_COMPONENTS[*]}"
echo -e " ${H_YELLOW}>>> Exiting installer. Please check upstream DankLinux repo or network. ${NC}"
exit 1
else
success "Blackbox components validated successfully."
fi
# ==============================================================================
# Dispaly Manager
# ==============================================================================
section "Config" "Dispaly Manager"
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
log "Module 04c completed."

388
scripts/04d-gnome.sh Normal file
View File

@ -0,0 +1,388 @@
#!/bin/bash
# ==============================================================================
# GNOME Setup Script (04d-gnome.sh) - Fixed D-Bus & Extensions & Verify & DMCheck
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
# 检查 utils 脚本
if [ -f "$SCRIPT_DIR/00-utils.sh" ]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found."
exit 1
fi
log "Initializing installation..."
check_root
# 初始化 Verify 列表
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST"
# ==============================================================================
# Identify User & DM Check
# ==============================================================================
detect_target_user
TARGET_UID=$(id -u "$TARGET_USER")
info_kv "Target User" "$TARGET_USER"
info_kv "Home Dir" "$HOME_DIR"
# 调用 Utils 函数进行冲突检测 (会自动设置 $SKIP_DM 变量)
check_dm_conflict
# ==================================
# temp sudo without passwd
# ==================================
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..."
cleanup_sudo() {
if [ -f "$SUDO_TEMP_FILE" ]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
#=================================================
# Step 1: Install base pkgs
#=================================================
section "Step 1" "Install base pkgs"
log "Installing GNOME and base tools..."
GNOME_BASE_PKGS="gnome-desktop gnome-backgrounds gnome-tweaks gdm ghostty celluloid loupe gnome-control-center bazaar flatpak file-roller nautilus-python firefox nm-connection-editor pacman-contrib dnsmasq ttf-jetbrains-mono-nerd"
echo "$GNOME_BASE_PKGS" >> "$VERIFY_LIST"
if exe as_user yay -S --noconfirm --needed --answerdiff=None --answerclean=None $GNOME_BASE_PKGS; then
GNOME_FM_PKGS="ffmpegthumbnailer gvfs-smb nautilus-open-any-terminal file-roller gnome-keyring gst-plugins-base gst-plugins-good gst-libav nautilus"
echo "$GNOME_FM_PKGS" >> "$VERIFY_LIST"
exe pacman -S --noconfirm --needed $GNOME_FM_PKGS
log "Packages installed successfully."
else
log "Installation failed."
return 1
fi
# Enable Display Manager (GDM)
if [ "$SKIP_DM" = true ]; then
log "Display Manager setup skipped (Conflict found or user opted out)."
else
log "Enabling GDM..."
exe systemctl enable gdm.service
success "GDM enabled."
fi
#=================================================
# Step 2: Set default terminal (修复:加入 D-Bus)
#=================================================
section "Step 2" "Set default terminal"
log "Setting GNOME default terminal to Ghostty..."
# 使用 sudo -u 切换用户,并启动临时 dbus-launch 以确保 gsettings 生效
sudo -u "$TARGET_USER" bash <<EOF
# D-Bus Fix
if [ -z "\$DBUS_SESSION_BUS_ADDRESS" ]; then
eval \$(dbus-launch --sh-syntax)
trap "kill \$DBUS_SESSION_BUS_PID" EXIT
fi
gsettings set org.gnome.desktop.default-applications.terminal exec 'ghostty'
gsettings set org.gnome.desktop.default-applications.terminal exec-arg '-e'
EOF
#=================================================
# Step 3: Set locale
#=================================================
section "Step 3" "Set locale"
log "Configuring GNOME locale for user $TARGET_USER..."
ACCOUNT_FILE="/var/lib/AccountsService/users/$TARGET_USER"
ACCOUNT_DIR=$(dirname "$ACCOUNT_FILE")
mkdir -p "$ACCOUNT_DIR"
cat > "$ACCOUNT_FILE" <<EOF
[User]
Languages=zh_CN.UTF-8
EOF
#=================================================
# Step 4: Configure Shortcuts (修复:加入 D-Bus)
#=================================================
section "Step 4" "Configure Shortcuts"
log "Configuring shortcuts..."
sudo -u "$TARGET_USER" bash <<EOF
# ================= D-Bus Fix =================
# 在非图形化环境修改 dconf 必须手动启动 session bus
if [ -z "\$DBUS_SESSION_BUS_ADDRESS" ] || [ ! -e "\${DBUS_SESSION_BUS_ADDRESS#unix:path=}" ]; then
echo " -> Starting temporary D-Bus session for shortcuts..."
eval \$(dbus-launch --sh-syntax)
trap "kill \$DBUS_SESSION_BUS_PID" EXIT
fi
# =============================================
echo " ➜ Applying shortcuts for user: $(whoami)..."
# 1. 窗口管理
SCHEMA="org.gnome.desktop.wm.keybindings"
gsettings set \$SCHEMA close "['<Super>q']"
gsettings set \$SCHEMA show-desktop "['<Super>h']"
gsettings set \$SCHEMA toggle-fullscreen "['<Alt><Super>f']"
gsettings set \$SCHEMA toggle-maximized "['<Super>f']"
gsettings set \$SCHEMA maximize "[]"
gsettings set \$SCHEMA minimize "[]"
gsettings set \$SCHEMA unmaximize "[]"
gsettings set \$SCHEMA switch-to-workspace-left "['<Shift><Super>q']"
gsettings set \$SCHEMA switch-to-workspace-right "['<Shift><Super>e']"
gsettings set \$SCHEMA move-to-workspace-left "['<Control><Super>q']"
gsettings set \$SCHEMA move-to-workspace-right "['<Control><Super>e']"
gsettings set \$SCHEMA switch-applications "['<Alt>Tab']"
gsettings set \$SCHEMA switch-applications-backward "['<Shift><Alt>Tab']"
gsettings set \$SCHEMA switch-group "['<Alt>grave']"
gsettings set \$SCHEMA switch-group-backward "['<Shift><Alt>grave']"
gsettings set \$SCHEMA switch-input-source "[]"
gsettings set \$SCHEMA switch-input-source-backward "[]"
# 2. Shell 全局
SCHEMA="org.gnome.shell.keybindings"
gsettings set \$SCHEMA screenshot "['<Shift><Control><Super>a']"
gsettings set \$SCHEMA screenshot-window "['<Control><Super>a']"
gsettings set \$SCHEMA show-screenshot-ui "['<Alt><Super>a']"
gsettings set \$SCHEMA toggle-application-view "['<Super>g']"
gsettings set \$SCHEMA toggle-quick-settings "['<Control><Super>s']"
gsettings set \$SCHEMA toggle-message-tray "[]"
# 3. 自定义快捷键
SCHEMA="org.gnome.settings-daemon.plugins.media-keys"
gsettings set \$SCHEMA magnifier "['<Alt><Super>0']"
gsettings set \$SCHEMA screenreader "[]"
add_custom() {
local index="\$1"
local name="\$2"
local cmd="\$3"
local bind="\$4"
local path="/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom\$index/"
local key_schema="org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:\$path"
gsettings set "\$key_schema" name "\$name"
gsettings set "\$key_schema" command "\$cmd"
gsettings set "\$key_schema" binding "\$bind"
echo "\$path"
}
# 重置列表以避免冲突
gsettings set \$SCHEMA custom-keybindings "[]"
P0=\$(add_custom 0 "openbrowser" "firefox" "<Super>b")
P1=\$(add_custom 1 "openterminal" "ghostty" "<Super>t")
P2=\$(add_custom 2 "missioncenter" "missioncenter" "<Super>grave")
P3=\$(add_custom 3 "opennautilus" "nautilus" "<Super>e")
P4=\$(add_custom 4 "editscreenshot" "gradia --screenshot" "<Shift><Super>s")
P5=\$(add_custom 5 "gnome-control-center" "gnome-control-center" "<Control><Alt>s")
CUSTOM_LIST="['\$P0', '\$P1', '\$P2', '\$P3', '\$P4', '\$P5']"
gsettings set \$SCHEMA custom-keybindings "\$CUSTOM_LIST"
echo " ➜ Shortcuts synced successfully."
EOF
#=================================================
# Step 5: Extensions
#=================================================
section "Step 5" "Install Extensions"
log "Installing Extensions CLI..."
EXT_CLI_PKGS="gnome-extensions-cli ttf-jetbrains-maple-mono-nf-xx-xx"
echo "$EXT_CLI_PKGS" >> "$VERIFY_LIST"
sudo -u $TARGET_USER yay -S --noconfirm --needed --answerdiff=None --answerclean=None $EXT_CLI_PKGS
EXTENSION_LIST=(
"arch-update@RaphaelRochet"
"aztaskbar@aztaskbar.gitlab.com"
"blur-my-shell@aunetx"
"caffeine@patapon.info"
"clipboard-indicator@tudmotu.com"
"color-picker@tuberry"
"desktop-cube@schneegans.github.com"
"fuzzy-application-search@mkhl.codeberg.page"
"lockkeys@vaina.lt"
"middleclickclose@paolo.tranquilli.gmail.com"
"steal-my-focus-window@steal-my-focus-window"
"tilingshell@ferrarodomenico.com"
"user-theme@gnome-shell-extensions.gcampax.github.com"
"kimpanel@kde.org"
"rounded-window-corners@fxgn"
"appindicatorsupport@rgcjonas.gmail.com"
)
log "Downloading extensions..."
sudo -u $TARGET_USER gnome-extensions-cli install "${EXTENSION_LIST[@]}" 2>/dev/null
section "Step 5.2" "Enable GNOME Extensions"
# 【核心修复】:为启用扩展添加 D-Bus 支持
sudo -u "$TARGET_USER" bash <<EOF
# D-Bus Fix
if [ -z "\$DBUS_SESSION_BUS_ADDRESS" ]; then
eval \$(dbus-launch --sh-syntax)
trap "kill \$DBUS_SESSION_BUS_PID" EXIT
fi
echo " ➜ Activating extensions via gsettings (D-Bus Active)..."
enable_extension() {
local uuid="\$1"
local current_list=\$(gsettings get org.gnome.shell enabled-extensions)
if [[ "\$current_list" == *"\$uuid"* ]]; then
echo " -> Extension \$uuid already enabled."
else
echo " -> Enabling extension: \$uuid"
if [ "\$current_list" = "@as []" ]; then
gsettings set org.gnome.shell enabled-extensions "['\$uuid']"
else
new_list="\${current_list%]}, '\$uuid']"
gsettings set org.gnome.shell enabled-extensions "\$new_list"
fi
fi
}
# 数组遍历,更整洁
declare -a ext_array=(
"user-theme@gnome-shell-extensions.gcampax.github.com"
"arch-update@RaphaelRochet"
"aztaskbar@aztaskbar.gitlab.com"
"blur-my-shell@aunetx"
"caffeine@patapon.info"
"clipboard-indicator@tudmotu.com"
"color-picker@tuberry"
"desktop-cube@schneegans.github.com"
"fuzzy-application-search@mkhl.codeberg.page"
"lockkeys@vaina.lt"
"middleclickclose@paolo.tranquilli.gmail.com"
"steal-my-focus-window@steal-my-focus-window"
"tilingshell@ferrarodomenico.com"
"kimpanel@kde.org"
"rounded-window-corners@fxgn"
"appindicatorsupport@rgcjonas.gmail.com"
)
for ext in "\${ext_array[@]}"; do
enable_extension "\$ext"
done
EOF
# 编译扩展 Schema
log "Compiling extension schemas..."
chown -R $TARGET_USER:$TARGET_USER $HOME_DIR/.local/share/gnome-shell/extensions
sudo -u "$TARGET_USER" bash <<EOF
EXT_DIR="$HOME_DIR/.local/share/gnome-shell/extensions"
echo " ➜ Compiling schemas in \$EXT_DIR..."
if [ -d "\$EXT_DIR" ]; then
for dir in "\$EXT_DIR"/*; do
if [ -d "\$dir/schemas" ]; then
glib-compile-schemas "\$dir/schemas"
fi
done
fi
EOF
#=================================================
# Firefox Policies
#=================================================
section "Firefox" "Configuring Firefox GNOME Integration"
FF_GNOME_PKGS="gnome-browser-connector"
echo "$FF_GNOME_PKGS" >> "$VERIFY_LIST"
exe sudo -u $TARGET_USER yay -S --noconfirm --needed --answerdiff=None --answerclean=None $FF_GNOME_PKGS
POL_DIR="/etc/firefox/policies"
exe mkdir -p "$POL_DIR"
echo '{
"policies": {
"Extensions": {
"Install": [
"https://addons.mozilla.org/firefox/downloads/latest/gnome-shell-integration/latest.xpi"
]
}
}
}' > "$POL_DIR/policies.json"
exe chmod 755 "$POL_DIR" && exe chmod 644 "$POL_DIR/policies.json"
log "Firefox policies updated."
#=================================================
# Nautilus Fix & Input Method
#=================================================
configure_nautilus_user
section "Step 6" "Input method"
log "Configure input method environment..."
if ! grep -q "fcitx" "/etc/environment" 2>/dev/null; then
cat << EOT >> /etc/environment
XIM="fcitx"
GTK_IM_MODULE=fcitx
QT_IM_MODULE=fcitx
XMODIFIERS=@im=fcitx
XDG_CURRENT_DESKTOP=GNOME
EOT
fi
#=================================================
# Dotfiles
#=================================================
section "Dotfiles" "Deploying dotfiles"
GNOME_DOTFILES_DIR=$PARENT_DIR/gnome-dotfiles
log "Ensuring .config exists..."
sudo -u $TARGET_USER mkdir -p $HOME_DIR/.config
log "Copying dotfiles..."
if [ -d "$GNOME_DOTFILES_DIR" ]; then
cp -rf "$GNOME_DOTFILES_DIR/." "$HOME_DIR/"
else
warn "Dotfiles directory not found: $GNOME_DOTFILES_DIR"
fi
as_user mkdir -p "$HOME_DIR/Templates"
as_user touch "$HOME_DIR/Templates/new"
# 修复:确保 new.sh 是用户所有,且内容正确
sudo -u "$TARGET_USER" bash -c "echo '#!/usr/bin/env bash' > $HOME_DIR/Templates/new.sh"
sudo -u "$TARGET_USER" chmod +x "$HOME_DIR/Templates/new.sh"
log "Fixing permissions..."
chown -R $TARGET_USER: $HOME_DIR/.config
chown -R $TARGET_USER: $HOME_DIR/.local
if command -v flatpak &>/dev/null; then
sudo -u "$TARGET_USER" flatpak override --user --filesystem=xdg-config/fontconfig
fi
log "Installing shell tools..."
SHELL_TOOLS_PKGS="thefuck starship eza fish zoxide jq timg imagemagick shorin-contrib-git bat"
echo "$SHELL_TOOLS_PKGS" >> "$VERIFY_LIST"
exe as_user paru -S --noconfirm --needed $SHELL_TOOLS_PKGS
as_user shorin link
# === 隐藏多余的 Desktop 图标 ===
section "Config" "Hiding useless .desktop files"
log "Hiding useless .desktop files"
run_hide_desktop_file
log "Installation Complete! Please reboot."
cleanup_sudo

View File

@ -0,0 +1,148 @@
#!/bin/bash
# 04e-illogical-impulse-end4-quickshell.sh
# 1. 引用工具库
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [ -f "$SCRIPT_DIR/00-utils.sh" ]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found."
exit 1
fi
log "installing Illogical Impulse End4 (Quickshell)..."
# ==============================================================================
# Identify User & DM Check
# ==============================================================================
log "Identifying user..."
detect_target_user
info_kv "Target" "$TARGET_USER"
# DM Check
check_dm_conflict
log "Target user for End4 installation: $TARGET_USER"
# ==============================================================================
# install
# ==============================================================================
section "Desktop" "illogical-impulse"
# 下载并执行安装脚本
INSTALLER_SCRIPT="/tmp/end4_install.sh"
II_URL="https://ii.clsty.link/get"
log "Downloading Illogical Impulse installer wrapper..."
if curl -fsSL "$II_URL" -o "$INSTALLER_SCRIPT"; then
chmod +x "$INSTALLER_SCRIPT"
chown "$TARGET_USER" "$INSTALLER_SCRIPT"
log "Executing End4 installer as user ($TARGET_USER)..."
log "NOTE: If the installer asks for input, this script might hang."
if runuser -u "$TARGET_USER" -- bash -c "cd ~ && $INSTALLER_SCRIPT"; then
success "Illogical Impulse End4 installed successfully."
else
# 安装失败不应该导致整个系统安装退出,所以只警告
warn "End4 installer returned an error code. You may need to install it manually."
fi
rm -f "$INSTALLER_SCRIPT"
else
warn "Failed to download installer script from $II_URL."
fi
# ==============================================================================
# Input Method & Environment (End4 Config)
# ==============================================================================
section "end4" "Input Method and Environment Configuration"
# 1. 定义变量与路径
END4_HYPR_DOT_DIR="$HOME_DIR/.config/hypr"
CUSTOM_DIR="$END4_HYPR_DOT_DIR/custom"
END4_HYPR_CUS_ENV="$CUSTOM_DIR/env.conf"
END4_HYPR_CUS_EXEC="$CUSTOM_DIR/execs.conf"
SOURCE_DOTFILES="$PARENT_DIR/quickshell-dotfiles"
# 2. 部署配置文件
if [ -d "$SOURCE_DOTFILES" ]; then
log "Deploying Quickshell dotfiles to $HOME_DIR/.config/..."
chown -R "$TARGET_USER:" "$SOURCE_DOTFILES"
as_user cp -rf "$SOURCE_DOTFILES/." "$HOME_DIR/"
else
warn "Source directory not found: $SOURCE_DOTFILES"
warn "Skipping dotfiles copy."
fi
# 确保 custom 目录存在 (防止因拷贝未发生而导致后续报错)
if [ ! -d "$CUSTOM_DIR" ]; then
mkdir -p "$CUSTOM_DIR"
log "Created missing directory: $CUSTOM_DIR"
fi
# 3. 配置环境变量 (env.conf)
# 使用 grep 检查是否已经存在 fcitx 配置,防止重复追加
if ! grep -q "XMODIFIERS,@im=fcitx" "$END4_HYPR_CUS_ENV" 2>/dev/null; then
log "Injecting Fcitx5 environment variables into env.conf..."
# 补充了 QT, GTK, SDL 的输入法变量,确保在各种应用中都能唤起输入法
cat << EOT >> "$END4_HYPR_CUS_ENV"
# --- Added by Shorin-Setup Script ---
# Fcitx5 Input Method Variables
env = XMODIFIERS,@im=fcitx
env = LC_CTYPE,en_US.UTF-8
# Locale Settings
env = LANG,zh_CN.UTF-8
# ----------------------------------
EOT
else
log "Fcitx5 environment variables already exist in env.conf, skipping."
fi
# 4. 配置自动启动 (execs.conf)
# 同样检查防止重复添加
if ! grep -q "^[[:space:]]*exec-once = fcitx5 -d" "$END4_HYPR_CUS_EXEC" 2>/dev/null; then
log "Adding Fcitx5 autostart command to execs.conf..."
echo "exec-once = fcitx5 -d" >> "$END4_HYPR_CUS_EXEC"
else
log "Fcitx5 autostart already exists in execs.conf, skipping."
fi
# 5. 统一修复权限 (Critical Step)
# 必须在所有写入操作完成后执行,确保新追加的内容也属于目标用户
log "Applying permission fixes for user: $TARGET_USER..."
chown -R "$TARGET_USER" "$HOME_DIR/.config"
success "End4 input method and environment configured."
# ==============================================================================
# screenshare
# ==============================================================================
section "end4" "Screenshare"
pacman -S --noconfirm --needed xdg-desktop-portal-hyprland
# === 隐藏多余的 Desktop 图标 ===
section "Config" "Hiding useless .desktop files"
log "Hiding useless .desktop files"
run_hide_desktop_file
# ==============================================================================
# autologin
# ==============================================================================
section "Config" "Display Manager"
# 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
log "Module 04e (End4) completed."

View File

@ -0,0 +1,137 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [ -f "$SCRIPT_DIR/00-utils.sh" ]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found."
exit 1
fi
check_root
# ==============================================================================
# Identify User & DM Check
# ==============================================================================
log "Identifying user..."
DETECTED_USER=$(awk -F: '$3 == 1000 {print $1}' /etc/passwd)
TARGET_USER="${DETECTED_USER:-$(read -p "Target user: " u && echo $u)}"
HOME_DIR="/home/$TARGET_USER"
info_kv "Target" "$TARGET_USER"
# DM Check
KNOWN_DMS=("gdm" "sddm" "lightdm" "lxdm" "slim" "xorg-xdm" "ly" "greetd" "plasma-login-manager")
SKIP_AUTOLOGIN=false
DM_FOUND=""
for dm in "${KNOWN_DMS[@]}"; do
if pacman -Q "$dm" &>/dev/null; then
DM_FOUND="$dm"
break
fi
done
if [ -n "$DM_FOUND" ]; then
info_kv "Conflict" "${H_RED}$DM_FOUND${NC}"
SKIP_AUTOLOGIN=true
else
read -t 20 -p "$(echo -e " ${H_CYAN}Enable TTY auto-login? [Y/n] (Default Y): ${NC}")" choice || true
[[ "${choice:-Y}" =~ ^[Yy]$ ]] && SKIP_AUTOLOGIN=false || SKIP_AUTOLOGIN=true
fi
log "Target user for Noctalia installation: $TARGET_USER"
# ==================================
# temp sudo without passwd
# ==================================
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..."
cleanup_sudo() {
if [ -f "$SUDO_TEMP_FILE" ]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
# ==============================================================================
# install core pkgs
# ==============================================================================
CORE_PKGS=(
"niri"
"xdg-desktop-portal-gnome"
"kitty"
"firefox"
"noctalia-shell"
"qt6-multimedia-ffmpeg"
"polkit-gnome"
"matugen"
)
exe as_user yay -S --noconfirm --needed --answerdiff=None --answerclean=None "${CORE_PKGS[@]}"
# ==============================================================================
# File Manager
# ==============================================================================
# ==============================================================================
# Dotfiles
# ==============================================================================
# ==============================================================================
# fcitx5 configuration and locale
# ==============================================================================
# ==============================================================================
# screenshare
# ==============================================================================
# ==============================================================================
# tty autologin
# ==============================================================================
section "Config" "tty autostart"
SVC_DIR="$HOME_DIR/.config/systemd/user"
# 确保目录存在
as_user mkdir -p "$SVC_DIR/default.target.wants"
# tty自动登录
if [ "$SKIP_AUTOLOGIN" = false ]; then
log "Configuring Niri Auto-start (TTY)..."
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"
fi
# ===================================================
# window manager autostart (if don't have any of dm)
# ===================================================
section "Config" "WM autostart"
# 如果安装了niri
if [ "$SKIP_AUTOLOGIN" = false ] && command -v niri &>/dev/null; then
SVC_FILE="$SVC_DIR/niri-autostart.service"
LINK="$SVC_DIR/default.target.wants/niri-autostart.service"
# 创建niri自动登录服务
cat <<EOT >"$SVC_FILE"
[Unit]
Description=Niri Session Autostart
After=graphical-session-pre.target
StartLimitIntervalSec=60
StartLimitBurst=3
[Service]
ExecStart=/usr/bin/niri-session
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.target
EOT
# 启用服务
as_user ln -sf "$SVC_FILE" "$LINK"
# 确保权限
chown -R "$TARGET_USER" "$SVC_DIR"
success "Niri auto-start enabled"
fi

View File

@ -0,0 +1,142 @@
#!/bin/bash
# ==============================================================================
# 1. Load Utilities
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [ -f "$SCRIPT_DIR/00-utils.sh" ]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found."
exit 1
fi
section "Start" "Installing Caelestia (Quickshell)..."
# ==============================================================================
# 2. Identify User & Display Manager Check
# ==============================================================================
log "Identifying target user..."
# Detect user ID 1000 or prompt manually
detect_target_user
info_kv "Target User" "$TARGET_USER"
info_kv "Home Dir" "$HOME_DIR"
check_dm_conflict
# ==============================================================================
# 3. Temporary Sudo Access
# ==============================================================================
# Grant passwordless sudo temporarily for the installer to run smoothly
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 "Privilege escalation: Temporary passwordless sudo enabled."
cleanup_sudo() {
if [ -f "$SUDO_TEMP_FILE" ]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
# ==============================================================================
# 4. Installation (Caelestia)
# ==============================================================================
section "Repo" "Cloning Caelestia Repository"
CAELESTIA_REPO="https://github.com/caelestia-dots/caelestia.git"
CAELESTIA_DIR="$HOME_DIR/.local/share/caelestia"
# Clone to .local (Caelestia uses symlinks, not direct copies)
log "Cloning repository to $CAELESTIA_DIR ..."
if [ -d $CAELESTIA_DIR ]; then
warn "Repository clone failed or already exists. Deleting..."
rm -rf "$CAELESTIA_DIR"
fi
if exe as_user git clone "$CAELESTIA_REPO" "$CAELESTIA_DIR"; then
chown -R $TARGET_USER $CAELESTIA_DIR
log "repo cloned."
fi
log "Ensuring fish shell is installed..."
exe pacman -Syu --needed --noconfirm fish
section "Install" "Running Caelestia Installer"
# Switch to user, go home, and run the installer
if as_user sh -c "cd && fish $CAELESTIA_DIR/install.fish --noconfirm"; then
chown -R $TARGET_USER $HOME_DIR/.config
success "Caelestia installation script completed."
fi
# ==============================================================================
# 5. Post-Configuration
# ==============================================================================
section "Config" "Locale and Input Method"
HYPR_CONFIG="$CAELESTIA_DIR/hypr/hyprland.conf"
# 5.1 Fcitx5 Configuration
if [ -f "$HYPR_CONFIG" ]; then
if ! grep -q "fcitx5" "$HYPR_CONFIG"; then
log "Injecting Fcitx5 config into Hyprland..."
echo "exec-once = fcitx5 -d" >> "$HYPR_CONFIG"
echo "env = LC_CTYPE, en_US.UTF-8" >> "$HYPR_CONFIG"
chown -R "$TARGET_USER:" "$PARENT_DIR/quickshell-dotfiles"
as_user cp -rf "$PARENT_DIR/quickshell-dotfiles/." "$HOME_DIR/"
fi
# 5.2 Chinese Locale Check
# Fix: Ensure grep reads from input correctly
LOCALE_AVAILABLE=$(locale -a)
if echo "$LOCALE_AVAILABLE" | grep -q "zh_CN.utf8" && ! grep -q "zh_CN" "$HYPR_CONFIG"; then
log "Chinese locale detected. Configuring Hyprland environment..."
echo "env = LANG, zh_CN.UTF-8" >> "$HYPR_CONFIG"
fi
else
warn "Hyprland config file not found: $HYPR_CONFIG"
fi
success "Post-configuration completed."
# ==============================================================================
# file manager
# ==============================================================================
section "config" "file manager"
if ! command -v thunar; then
exe pacman -S --needed --noconfirm thunar tumbler ffmpegthumbnailer poppler-glib gvfs-smb file-roller thunar-archive-plugin gnome-keyring polkit-gnome
fi
# === 隐藏多余的 Desktop 图标 ===
section "Config" "Hiding useless .desktop files"
log "Hiding useless .desktop files"
run_hide_desktop_file
# ==============================================================================
# 6. dispaly manager
# ==============================================================================
section "Config" "Display Manager"
# 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
section "End" "Module 04e (Caelestia) Completed"

View File

@ -0,0 +1,179 @@
#!/usr/bin/env bash
# --- Import Utilities ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [[ -f "$SCRIPT_DIR/00-utils.sh" ]]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found in $SCRIPT_DIR."
exit 1
fi
check_root
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST" # 确保每次运行生成全新的订单
# --- Identify User & DM Check ---
log "Identifying target user..."
detect_target_user
if [[ -z "$TARGET_USER" || ! -d "$HOME_DIR" ]]; then
error "Target user invalid or home directory does not exist."
exit 1
fi
info_kv "Target User" "$TARGET_USER"
check_dm_conflict
# --- Temporary Sudo Privileges ---
log "Granting temporary sudo privileges..."
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"
cleanup_sudo() {
if [[ -f "$SUDO_TEMP_FILE" ]]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
# --- Installation: Core Components ---
AUR_HELPER="paru"
section "Shorin DMS" "Core Components"
log "Installing core shell components..."
CORE_PKGS="quickshell-git dms-shell-bin niri xwayland-satellite kitty xdg-desktop-portal-gnome niri-sidebar-git nwg-look cava cliphist wl-clipboard dgop dsearch-bin qt5-multimedia satty mpv cups-pk-helper kimageformats"
echo "$CORE_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $CORE_PKGS
# --- Dotfiles & Wallpapers ---
section "Shorin DMS" "Dotfiles & Wallpapers"
log "Deploying user dotfiles..."
DOTFILES_SRC="$PARENT_DIR/dms-dotfiles"
chown -R "$TARGET_USER:" "$DOTFILES_SRC"
force_copy "$DOTFILES_SRC/." "$HOME_DIR"
log "Deploying wallpapers..."
WALLPAPER_SOURCE_DIR="$PARENT_DIR/resources/Wallpapers"
WALLPAPER_DIR="$HOME_DIR/Pictures/Wallpapers"
chown -R "$TARGET_USER:" "$WALLPAPER_SOURCE_DIR"
as_user mkdir -p "$WALLPAPER_DIR"
force_copy "$WALLPAPER_SOURCE_DIR/." "$WALLPAPER_DIR/"
# --- File Manager & Terminal Setup ---
section "Shorin DMS" "File Manager & Terminal"
log "Installing Nautilus, Thunar and dependencies..."
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 as_user "$AUR_HELPER" -S --noconfirm --needed $FM_PKGS2
log "Installing terminal utilities..."
TERM_PKGS="xdg-terminal-exec bat fuzzel wf-recorder wl-screenrec-git ttf-jetbrains-maple-mono-nf-xx-xx eza zoxide starship jq fish libnotify timg imv cava imagemagick wl-clipboard cliphist shorin-contrib-git slurp"
echo "$TERM_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $TERM_PKGS
# shorin-contrib
as_user shorin link
log "Configuring default terminal and templates..."
# 默认终端处理
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
as_user mkdir -p "$HOME_DIR/Templates"
as_user touch "$HOME_DIR/Templates/new" "$HOME_DIR/Templates/new.sh"
as_user bash -c "echo '#!/usr/bin/env bash' >> '$HOME_DIR/Templates/new.sh'"
chown -R "$TARGET_USER:" "$HOME_DIR/Templates"
log "Applying Nautilus bugfixes and bookmarks..."
configure_nautilus_user
as_user sed -i "s/shorin/$TARGET_USER/g" "$HOME_DIR/.config/gtk-3.0/bookmarks"
# --- Flatpak & Theme Integration ---
section "Shorin DMS" "Flatpak & Theme Integration"
if command -v flatpak &>/dev/null; then
log "Configuring Flatpak overrides and themes..."
echo "bazaar" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed bazaar
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
as_user ln -sf /usr/share/themes "$HOME_DIR/.local/share/themes"
fi
# === update module ===
if command -v kitty &>/dev/null; then
exe ln -sf /usr/bin/kitty /usr/local/bin/xterm
fi
log "Installing theme components and browser..."
THEME_PKGS="matugen adw-gtk-theme python-pywalfox firefox nwg-look"
echo "$THEME_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $THEME_PKGS
log "Configuring Firefox Pywalfox policy..."
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"
# --- Desktop Cleanup & Tutorials ---
section "Config" "Desktop Cleanup"
log "Hiding unnecessary .desktop icons..."
run_hide_desktop_file
log "Copying tutorial files..."
force_copy "$PARENT_DIR/resources/必看-Shorin-DMS-Niri使用方法.txt" "$HOME_DIR"
# niri blur toggle 脚本
curl -L shorin.xyz/niri-blur-toggle | as_user bash
# --- Finalization & Auto-Login ---
section "Final" "Auto-Login & Cleanup"
rm -f "$SUDO_TEMP_FILE"
# 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

View File

@ -0,0 +1,219 @@
#!/usr/bin/env bash
# --- Import Utilities ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [[ -f "$SCRIPT_DIR/00-utils.sh" ]]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found in $SCRIPT_DIR."
exit 1
fi
check_root
# ========================================================================
# 初始化验证清单
# ========================================================================
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST"
# --- Identify User & DM Check ---
log "Identifying target user..."
detect_target_user
if [[ -z "$TARGET_USER" || ! -d "$HOME_DIR" ]]; then
error "Target user invalid or home directory does not exist."
exit 1
fi
info_kv "Target User" "$TARGET_USER"
check_dm_conflict
# --- Temporary Sudo Privileges ---
log "Granting temporary sudo privileges..."
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"
cleanup_sudo() {
if [[ -f "$SUDO_TEMP_FILE" ]]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
# ========================================================================
# exec
# ========================================================================
AUR_HELPER="paru"
# --- Installation: Core Components ---
section "Shorin Hyprniri" "Core Components & Utilities"
# 清理可能冲突的依赖
declare -a target_pkgs=(
"hyprcursor-git"
"hyprgraphics-git"
"hyprland-git"
"hyprland-guiutils-git"
"hyprlang-git"
"hyprlock-git"
"hyprpicker-git"
"hyprtoolkit-git"
"hyprutils-git"
"xdg-desktop-portal-hyprland-git"
)
# 2. 过滤出系统中实际已安装的包
declare -a installed_pkgs=()
for pkg in "${target_pkgs[@]}"; do
# 使用 pacman -Qq 检查是否安装,抑制输出以保持终端干净
if pacman -Qq "$pkg" >/dev/null 2>&1; then
installed_pkgs+=("$pkg")
fi
done
# 3. 只有当存在已安装的包时,才执行卸载命令
if [[ ${#installed_pkgs[@]} -gt 0 ]]; then
exe as_user "$AUR_HELPER" -Rns --noconfirm "${installed_pkgs[@]}"
fi
log "Installing Hyprland core components..."
CORE_PKGS="vulkan-headers hyprland quickshell-git dms-shell-bin matugen cava cups-pk-helper kimageformats kitty adw-gtk-theme nwg-look breeze-cursors wl-clipboard cliphist dsearch"
echo "$CORE_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $CORE_PKGS
log "Installing terminal utilities..."
TERM_PKGS="fish jq zoxide socat imagemagick imv starship eza ttf-jetbrains-maple-mono-nf-xx-xx fuzzel shorin-contrib-git timg wl-screenrec-git wf-recorder "
echo "$TERM_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $TERM_PKGS
log "Installing file manager and dependencies..."
FM_PKGS="xdg-terminal-exec 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 "
echo "$FM_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $FM_PKGS
log "Installing screenshot and screencast tools..."
SCREEN_PKGS="satty grim slurp xdg-desktop-portal-hyprland"
echo "$SCREEN_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $SCREEN_PKGS
# --- Environment Configurations ---
section "Shorin Hyprniri" "Environment Configuration"
log "Configuring default terminal and templates..."
# 默认终端处理
if ! grep -q "kitty" "$HOME_DIR/.config/xdg-terminals.list"; then
echo 'kitty.desktop' >> "$HOME_DIR/.config/xdg-terminals.list"
fi
as_user mkdir -p "$HOME_DIR/Templates"
as_user touch "$HOME_DIR/Templates/new" "$HOME_DIR/Templates/new.sh"
if [[ -f "$HOME_DIR/Templates/new.sh" ]] && grep -q "#!" "$HOME_DIR/Templates/new.sh"; then
log "Template new.sh already initialized."
else
as_user bash -c "echo '#!/usr/bin/env bash' >> '$HOME_DIR/Templates/new.sh'"
fi
chown -R "$TARGET_USER:" "$HOME_DIR/Templates"
# --- Dotfiles & Wallpapers ---
section "Shorin Hyprniri" "Dotfiles & Wallpapers"
log "Deploying user dotfiles from repository..."
DOTFILES_REPO_LINK="https://github.com/SHORiN-KiWATA/shorin-dms-hyprniri.git"
exe git clone --depth 1 "$DOTFILES_REPO_LINK" "$PARENT_DIR/shorin-dms-hyprniri-dotfiles"
chown -R "$TARGET_USER:" "$PARENT_DIR/shorin-dms-hyprniri-dotfiles"
force_copy "$PARENT_DIR/shorin-dms-hyprniri-dotfiles/dotfiles/." "$HOME_DIR"
as_user shorin link
log "Deploying wallpapers..."
WALLPAPER_SOURCE_DIR="$PARENT_DIR/resources/Wallpapers"
WALLPAPER_DIR="$HOME_DIR/Pictures/Wallpapers"
chown -R "$TARGET_USER:" "$WALLPAPER_SOURCE_DIR"
as_user mkdir -p "$WALLPAPER_DIR"
force_copy "$WALLPAPER_SOURCE_DIR/." "$WALLPAPER_DIR/"
# --- Browser Setup ---
section "Shorin Hyprniri" "Browser Setup"
log "Installing Firefox and Pywalfox..."
BROWSER_PKGS="firefox python-pywalfox"
echo "$BROWSER_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $BROWSER_PKGS
log "Configuring Firefox Pywalfox extension policy..."
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"
# --- Flatpak & Theme Integration ---
section "Shorin Hyprniri" "Flatpak & Theme Integration"
if command -v flatpak &>/dev/null; then
log "Configuring Flatpak overrides and theme integrations..."
echo "bazaar" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed bazaar
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
as_user ln -sf /usr/share/themes "$HOME_DIR/.local/share/themes"
else
warn "Flatpak is not installed. Skipping overrides."
fi
log "Applying file manager bookmarks..."
as_user sed -i "s/shorin/$TARGET_USER/g" "$HOME_DIR/.config/gtk-3.0/bookmarks"
# === update module ===
if command -v kitty &>/dev/null; then
exe ln -sf /usr/bin/kitty /usr/local/bin/xterm
fi
# --- Desktop Cleanup & Tutorials ---
section "Config" "Desktop Cleanup"
log "Hiding unnecessary .desktop icons..."
run_hide_desktop_file
chown -R "$TARGET_USER:" "$HOME_DIR/.local/share"
log "Copying tutorial files..."
force_copy "$PARENT_DIR/resources/必看-shoirn-hyprniri使用方法.txt" "$HOME_DIR"
# ========================================================================
# exec-end
# ========================================================================
# --- Finalization & Auto-Login ---
section "Final" "Auto-Login & Cleanup"
rm -f "$SUDO_TEMP_FILE"
# 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

151
scripts/04j-minimal-niri.sh Normal file
View File

@ -0,0 +1,151 @@
#!/usr/bin/env bash
# =======================================================================
# Initialization & Utilities
# =======================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [[ -f "$SCRIPT_DIR/00-utils.sh" ]]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found in $SCRIPT_DIR."
exit 1
fi
check_root
# 初始化安装验证文件
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST"
# =======================================================================
# Identify User & DM Check
# =======================================================================
log "Identifying target user..."
detect_target_user
if [[ -z "$TARGET_USER" || ! -d "$HOME_DIR" ]]; then
error "Target user invalid or home directory does not exist."
exit 1
fi
info_kv "Target User" "$TARGET_USER"
check_dm_conflict
# =======================================================================
# Temporary Sudo Privileges
# =======================================================================
log "Granting temporary sudo privileges..."
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"
cleanup_sudo() {
if [[ -f "$SUDO_TEMP_FILE" ]]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
# =======================================================================
# Execution Phase
# =======================================================================
AUR_HELPER="paru"
# --- 1. Dotfiles ---
section "Minimal Niri" "Dotfiles"
force_copy "$PARENT_DIR/minimal-niri-dotfiles/." "$HOME_DIR"
# --- 2. Bookmarks ---
BOOKMARKS_FILE="$HOME_DIR/.config/gtk-3.0/bookmarks"
if [[ -f "$BOOKMARKS_FILE" ]]; then
as_user sed -i "s/shorin/$TARGET_USER/g" "$BOOKMARKS_FILE"
fi
# --- 3. Niri output.kdl ---
OUTPUT_KDL="$HOME_DIR/.config/niri/output.kdl"
# 注意: DOTFILES_REPO 需确保在 00-utils.sh 或外部已定义
if [[ "$TARGET_USER" != "shorin" ]]; then
as_user touch "$OUTPUT_KDL"
else
as_user cp "$PARENT_DIR/minimal-niri-dotfiles/.config/niri/output-example.kdl" "$OUTPUT_KDL"
fi
# --- 4. Core Components ---
section "Minimal Niri" "Core Components"
NIRI_PKGS=(niri xwayland-satellite xdg-desktop-portal-gnome fuzzel waybar polkit-gnome mako)
echo "${NIRI_PKGS[*]}" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed "${NIRI_PKGS[@]}"
# --- 5. Terminal ---
section "Minimal Niri" "Terminal"
TERMINAL_PKGS=(zsh foot ttf-jetbrains-maple-mono-nf-xx-xx starship eza zoxide zsh-syntax-highlighting zsh-autosuggestions zsh-completions imagemagick jq bat)
echo "${TERMINAL_PKGS[*]}" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed "${TERMINAL_PKGS[@]}"
# --- 6. File Manager ---
section "Minimal Niri" "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 poppler-glib thunar-archive-plugin 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 "$AUR_HELPER" -S --noconfirm --needed xdg-terminal-exec
# 修复:如果不包含 foot则追加
XDG_TERMS_LIST="$HOME_DIR/.config/xdg-terminals.list"
if ! grep -qs "foot" "$XDG_TERMS_LIST"; then
# 确保目录存在
mkdir -p "$(dirname "$XDG_TERMS_LIST")"
echo 'foot.desktop' >> "$XDG_TERMS_LIST"
chown "$TARGET_USER:" "$XDG_TERMS_LIST" 2>/dev/null || true
fi
sudo -u "$TARGET_USER" dbus-run-session gsettings set com.github.stunkymonkey.nautilus-open-any-terminal terminal foot
# 注意: 确保 configure_nautilus_user 在 00-utils.sh 中已定义
configure_nautilus_user
# --- 7. Tools ---
section "Minimal Niri" "Tools"
TOOLS_PKGS=(imv cliphist wl-clipboard shorinclip-git shorin-contrib-git hyprlock breeze-cursors nwg-look adw-gtk-theme pavucontrol pulsemixer satty)
echo "${TOOLS_PKGS[*]}" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed "${TOOLS_PKGS[@]}"
as_user shorin link
# --- 8. Flatpak Overrides ---
if command -v flatpak &>/dev/null; then
section "Minimal Niri" "Flatpak Config"
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
run_hide_desktop_file
force_copy "$PARENT_DIR/resources/Minimal-Niri使用方法.txt" "$HOME_DIR"
section "Final" "Cleanup & Boot Configuration"
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

View File

@ -0,0 +1,180 @@
#!/usr/bin/env bash
# --- Import Utilities ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
if [[ -f "$SCRIPT_DIR/00-utils.sh" ]]; then
source "$SCRIPT_DIR/00-utils.sh"
else
echo "Error: 00-utils.sh not found in $SCRIPT_DIR."
exit 1
fi
check_root
VERIFY_LIST="/tmp/shorin_install_verify.list"
rm -f "$VERIFY_LIST" # 确保每次运行生成全新的订单
# --- Identify User & DM Check ---
log "Identifying target user..."
detect_target_user
if [[ -z "$TARGET_USER" || ! -d "$HOME_DIR" ]]; then
error "Target user invalid or home directory does not exist."
exit 1
fi
info_kv "Target User" "$TARGET_USER"
check_dm_conflict
# --- Temporary Sudo Privileges ---
log "Granting temporary sudo privileges..."
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"
cleanup_sudo() {
if [[ -f "$SUDO_TEMP_FILE" ]]; then
rm -f "$SUDO_TEMP_FILE"
log "Security: Temporary sudo privileges revoked."
fi
}
trap cleanup_sudo EXIT INT TERM
# --- Installation: Core Components ---
AUR_HELPER="paru"
section "Shorin Noctalia" "Core Components"
log "Installing core shell components..."
CORE_PKGS="noctalia-shell niri xwayland-satellite kitty xdg-desktop-portal-gnome niri-sidebar-git satty mpv polkit-gnome "
echo "$CORE_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $CORE_PKGS
# --- Dotfiles & Wallpapers ---
section "Shorin Noctalia" "Dotfiles & Wallpapers"
log "Deploying user dotfiles..."
DOTFILES_SRC="$PARENT_DIR/noctalia-dotfiles"
chown -R "$TARGET_USER:" "$DOTFILES_SRC"
force_copy "$DOTFILES_SRC/." "$HOME_DIR"
log "Deploying wallpapers..."
WALLPAPER_SOURCE_DIR="$PARENT_DIR/resources/Wallpapers"
WALLPAPER_DIR="$HOME_DIR/Pictures/Wallpapers"
chown -R "$TARGET_USER:" "$WALLPAPER_SOURCE_DIR"
as_user mkdir -p "$WALLPAPER_DIR"
force_copy "$WALLPAPER_SOURCE_DIR/." "$WALLPAPER_DIR/"
# --- File Manager & Terminal Setup ---
section "Shorin Noctalia" "File Manager & Terminal"
log "Installing Nautilus, Thunar and dependencies..."
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 as_user "$AUR_HELPER" -S --noconfirm --needed $FM_PKGS2
# 创建模板文件
as_user mkdir -p "$HOME_DIR/Templates"
as_user touch "$HOME_DIR/Templates/new" "$HOME_DIR/Templates/new.sh"
as_user bash -c "echo '#!/usr/bin/env bash' >> '$HOME_DIR/Templates/new.sh'"
chown -R "$TARGET_USER:" "$HOME_DIR/Templates"
# Nautilus 配置
log "Applying Nautilus bugfixes and bookmarks..."
configure_nautilus_user
# bookmarks修复
as_user sed -i "s/shorin/$TARGET_USER/g" "$HOME_DIR/.config/gtk-3.0/bookmarks"
# --- Terminal Utilities ---
section "Shorin Noctalia" "Terminal Utilities"
log "Installing terminal utilities..."
TERM_PKGS="xdg-terminal-exec bat fuzzel wf-recorder wl-screenrec-git ttf-jetbrains-maple-mono-nf-xx-xx eza zoxide starship jq fish libnotify timg imv cava imagemagick wl-clipboard cliphist shorin-contrib-git slurp"
echo "$TERM_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $TERM_PKGS
# shorin-contrib
log "Linking shorin-contrib..."
as_user shorin link
# 默认终端处理
log "Configuring default terminal and templates..."
if ! grep -q "kitty" "$HOME_DIR/.config/xdg-terminals.list"; then
echo 'kitty.desktop' >> "$HOME_DIR/.config/xdg-terminals.list"
fi
sudo -u "$TARGET_USER" dbus-run-session gsettings set com.github.stunkymonkey.nautilus-open-any-terminal terminal kitty
# xterm链接
if command -v kitty &>/dev/null; then
exe ln -sf /usr/bin/kitty /usr/local/bin/xterm
fi
# --- Flatpak & Theme Integration ---
section "Shorin Noctalia" "Flatpak & Theme Integration"
if command -v flatpak &>/dev/null; then
log "Configuring Flatpak overrides and themes..."
echo "bazaar" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed bazaar
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
as_user ln -sf /usr/share/themes "$HOME_DIR/.local/share/themes"
fi
# === Theme Components & Browser ===
log "Installing theme components and browser..."
THEME_PKGS="matugen adw-gtk-theme python-pywalfox firefox nwg-look breeze-cursors"
echo "$THEME_PKGS" >> "$VERIFY_LIST"
exe as_user "$AUR_HELPER" -S --noconfirm --needed $THEME_PKGS
# 配置 Firefox Pywalfox 与 uBlock Origin 插件安装政策
log "Configuring Firefox extensions policy..."
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"
# --- Desktop Cleanup & Tutorials ---
section "Config" "Desktop Cleanup"
log "Hiding unnecessary .desktop icons..."
run_hide_desktop_file
log "Copying tutorial files..."
force_copy "$PARENT_DIR/resources/必看-Shorin-Noctalia-Niri使用方法.txt" "$HOME_DIR"
# niri blur toggle 脚本
curl -L shorin.xyz/niri-blur-toggle | as_user bash
# --- Finalization & Auto-Login ---
section "Final" "Auto-Login & Cleanup"
rm -f "$SUDO_TEMP_FILE"
# --- display manager setup ---
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

View File

@ -0,0 +1,135 @@
#!/bin/bash
# ==============================================================================
# Script: 05-verify-desktop.sh
# Description:
# 1. 黑盒环境启发式验证 (dms / quickshell)。
# 2. 显式包发货单对账 (pacman -T)。
# 3. 用户配置文件/软链接部署完整性验证。
# 一旦任何一环发现缺失,立即中断并退出 (exit 1)。
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/00-utils.sh"
VERIFY_LIST="/tmp/shorin_install_verify.list"
section "Verification" "Auditing System State"
# ==============================================================================
# 1. 特殊环境启发式验证 (仅针对 Shorin DMS 系列)
# ==============================================================================
# if [[ "$DESKTOP_ENV" == "shorindms" || "$DESKTOP_ENV" == "shorindmsgit" ]]; then
# log "Performing specialized heuristic checks for DMS blackbox..."
# DMS_ERRORS=0
# if ! command -v quickshell &>/dev/null && ! pacman -Qq | grep -q "quickshell"; then
# echo -e " \033[1;31m->\033[0m \033[1;33mquickshell (or related package)\033[0m is MISSING!"
# DMS_ERRORS=1
# fi
# if ! command -v dms &>/dev/null && ! pacman -Qq | grep -q "dms-shell"; then
# echo -e " \033[1;31m->\033[0m \033[1;33mdms-shell (or related package)\033[0m is MISSING!"
# DMS_ERRORS=1
# fi
# if [ "$DMS_ERRORS" -ne 0 ]; then
# echo ""
# error "DMS CORE VALIDATION FAILED!"
# write_log "FATAL" "DMS heuristic validation failed. quickshell or dms-shell is missing."
# echo -e " ${H_YELLOW}>>> Exiting installer. The official DMS script might have failed. ${NC}"
# exit 1
# else
# success "DMS core components verified."
# fi
# fi
# ==============================================================================
# 2. 清单统实验证 (发货单对账)
# ==============================================================================
if [ -f "$VERIFY_LIST" ]; then
mapfile -t CHECK_PKGS < <(cat "$VERIFY_LIST" | tr ' ' '\n' | sed '/^[[:space:]]*$/d' | sort -u)
if [ ${#CHECK_PKGS[@]} -gt 0 ]; then
log "Verifying ${#CHECK_PKGS[@]} explicit packages..."
MISSING_PKGS=$(pacman -T "${CHECK_PKGS[@]}" 2>/dev/null)
if [ -n "$MISSING_PKGS" ]; then
echo ""
error "SOFTWARE INSTALLATION INCOMPLETE!"
echo -e " ${DIM}The following packages failed to install:${NC}"
echo "$MISSING_PKGS" | awk '{print " \033[1;31m->\033[0m \033[1;33m" $0 "\033[0m"}'
echo ""
if declare -f write_log >/dev/null; then
write_log "FATAL" "Missing packages: $(echo "$MISSING_PKGS" | tr '\n' ' ')"
fi
error "Cannot proceed with a broken desktop environment."
echo -e " ${H_YELLOW}>>> Exiting installer. Please check your network or AUR helpers. ${NC}"
exit 1
else
success "All explicit packages successfully verified."
rm -f "$VERIFY_LIST"
fi
fi
fi
# ==============================================================================
# 3. 配置文件部署验证 (Dotfiles Audit)
# ==============================================================================
log "Identifying target user for config audit..."
detect_target_user
if [ -z "$TARGET_USER" ]; then
warn "Could not reliably detect user 1000. Skipping dotfiles audit."
else
HOME_DIR="/home/$TARGET_USER"
CONFIG_ERRORS=0
# KISS 的检查小函数
check_config_exists() {
local path="$1"
# -e 可以完美识别常规目录、文件,以及目标有效的软链接
if [ ! -e "$path" ]; then
echo -e " \033[1;31m->\033[0m \033[1;33m$path\033[0m is MISSING or BROKEN!"
CONFIG_ERRORS=$((CONFIG_ERRORS + 1))
else
log " [OK] $path"
fi
}
log "Auditing dotfiles for ${DESKTOP_ENV^^}..."
case "$DESKTOP_ENV" in
shorinniri)
check_config_exists "$HOME_DIR/.config/niri"
check_config_exists "$HOME_DIR/.config/waybar"
check_config_exists "$HOME_DIR/.config/matugen"
check_config_exists "$HOME_DIR/.config/waypaper"
;;
shorindms|shorindmsgit)
check_config_exists "$HOME_DIR/.config/niri/dms"
;;
hyprniri)
check_config_exists "$HOME_DIR/.config/hypr"
;;
*)
log "No specific config checks mapped for $DESKTOP_ENV. Skipping."
;;
esac
if [ "$CONFIG_ERRORS" -gt 0 ]; then
echo ""
error "DOTFILES DEPLOYMENT FAILED!"
if declare -f write_log >/dev/null; then
write_log "FATAL" "Dotfiles audit failed. $CONFIG_ERRORS paths missing or broken."
fi
echo -e " ${H_YELLOW}>>> Exiting installer. The repository clone or symlink step might have failed. ${NC}"
exit 1
else
success "Configuration files and symlinks deployed correctly."
fi
fi
# 全部通关!
exit 0

346
scripts/07-grub-theme.sh Normal file
View File

@ -0,0 +1,346 @@
#!/bin/bash
# ==============================================================================
# 07-grub-theme.sh - GRUB Theming & Advanced Configuration
# ==============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/00-utils.sh"
check_root
# ------------------------------------------------------------------------------
# 0. Pre-check: Is GRUB installed?
# ------------------------------------------------------------------------------
if ! command -v grub-mkconfig >/dev/null 2>&1; then
echo ""
warn "GRUB (grub-mkconfig) not found on this system."
log "Skipping GRUB theme installation."
exit 0
fi
section "Phase 7" "GRUB Customization & Theming"
# --- Helper Functions ---
set_grub_value() {
local key="$1"
local value="$2"
local conf_file="/etc/default/grub"
local escaped_value
escaped_value=$(printf '%s\n' "$value" | sed 's,[\/&],\\&,g')
if grep -q -E "^#\s*$key=" "$conf_file"; then
exe sed -i -E "s,^#\s*$key=.*,$key=\"$escaped_value\"," "$conf_file"
elif grep -q -E "^$key=" "$conf_file"; then
exe sed -i -E "s,^$key=.*,$key=\"$escaped_value\"," "$conf_file"
else
log "Appending new key: $key"
echo "$key=\"$escaped_value\"" >> "$conf_file"
fi
}
manage_kernel_param() {
local action="$1"
local param="$2"
local conf_file="/etc/default/grub"
local line
line=$(grep "^GRUB_CMDLINE_LINUX_DEFAULT=" "$conf_file" || true)
local params
params=$(echo "$line" | sed -e 's/GRUB_CMDLINE_LINUX_DEFAULT=//' -e 's/"//g')
local param_key
if [[ "$param" == *"="* ]]; then param_key="${param%%=*}"; else param_key="$param"; fi
params=$(echo "$params" | sed -E "s/\b${param_key}(=[^ ]*)?\b//g")
if [ "$action" == "add" ]; then params="$params $param"; fi
params=$(echo "$params" | tr -s ' ' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
exe sed -i "s,^GRUB_CMDLINE_LINUX_DEFAULT=.*,GRUB_CMDLINE_LINUX_DEFAULT=\"$params\"," "$conf_file"
}
cleanup_minegrub() {
local minegrub_found=false
if [ -f "/etc/grub.d/05_twomenus" ] || [ -f "/boot/grub/mainmenu.cfg" ]; then
minegrub_found=true
log "Found Minegrub artifacts. Cleaning up..."
[ -f "/etc/grub.d/05_twomenus" ] && exe rm -f /etc/grub.d/05_twomenus
[ -f "/boot/grub/mainmenu.cfg" ] && exe rm -f /boot/grub/mainmenu.cfg
fi
if command -v grub-editenv >/dev/null 2>&1; then
if grub-editenv - list 2>/dev/null | grep -q "^config_file="; then
minegrub_found=true
log "Unsetting Minegrub GRUB environment variable..."
exe grub-editenv - unset config_file
fi
fi
if [ "$minegrub_found" == "true" ]; then
success "Minegrub double-menu configuration completely removed."
fi
}
# ------------------------------------------------------------------------------
# 1. Advanced GRUB Configuration
# ------------------------------------------------------------------------------
section "Step 1/7" "General GRUB Settings"
if [ -L "/boot/grub" ]; then
LINK_TARGET=$(readlink -f "/boot/grub" || true)
if [[ "$LINK_TARGET" == "/efi/grub" ]] || [[ "$LINK_TARGET" == "/boot/efi/grub" ]]; then
log "Detected /boot/grub linked to ESP ($LINK_TARGET). Enabling GRUB savedefault..."
set_grub_value "GRUB_DEFAULT" "saved"
set_grub_value "GRUB_SAVEDEFAULT" "true"
else
log "Skipping savedefault: /boot/grub links to $LINK_TARGET (not /efi/grub or /boot/efi/grub)."
fi
else
log "Skipping savedefault: /boot/grub is not a symbolic link."
fi
log "Configuring kernel boot parameters for detailed logs and performance..."
manage_kernel_param "remove" "quiet"
manage_kernel_param "remove" "splash"
manage_kernel_param "add" "loglevel=5"
manage_kernel_param "add" "nowatchdog"
CPU_VENDOR=$(LC_ALL=C lscpu 2>/dev/null | awk '/Vendor ID:/ {print $3}' || true)
if [ "${CPU_VENDOR:-}" == "GenuineIntel" ]; then
log "Intel CPU detected. Disabling iTCO_wdt watchdog."
manage_kernel_param "add" "modprobe.blacklist=iTCO_wdt"
elif [ "${CPU_VENDOR:-}" == "AuthenticAMD" ]; then
log "AMD CPU detected. Disabling sp5100_tco watchdog."
manage_kernel_param "add" "modprobe.blacklist=sp5100_tco"
fi
success "Kernel parameters updated."
# ------------------------------------------------------------------------------
# 2. Sync Themes to System
# ------------------------------------------------------------------------------
section "Step 2/7" "Sync Themes to System Directory"
SOURCE_BASE="$PARENT_DIR/grub-themes"
# 【核心改变】使用 Arch Linux 官方标准的主题存放目录
DEST_DIR="/usr/share/grub/themes"
# 确保目标目录存在
if [ ! -d "$DEST_DIR" ]; then
exe mkdir -p "$DEST_DIR"
fi
if [ -d "$SOURCE_BASE" ]; then
log "Syncing repository themes to $DEST_DIR..."
for dir in "$SOURCE_BASE"/*; do
if [ -d "$dir" ] && [ -f "$dir/theme.txt" ]; then
THEME_BASENAME=$(basename "$dir")
if [ ! -d "$DEST_DIR/$THEME_BASENAME" ]; then
log "Installing $THEME_BASENAME to system..."
exe cp -r "$dir" "$DEST_DIR/"
fi
fi
done
success "Local themes installed to $DEST_DIR."
else
warn "Directory 'grub-themes' not found in repo. Only online/existing themes available."
fi
log "Scanning $DEST_DIR for available themes..."
THEME_PATHS=()
THEME_NAMES=()
# 直接扫描这个干净的系统级目录,无需任何额外处理
mapfile -t FOUND_DIRS < <(find "$DEST_DIR" -mindepth 1 -maxdepth 1 -type d | sort 2>/dev/null || true)
for dir in "${FOUND_DIRS[@]:-}"; do
if [ -n "$dir" ] && [ -f "$dir/theme.txt" ]; then
DIR_NAME=$(basename "$dir")
if [[ "$DIR_NAME" != "minegrub" && "$DIR_NAME" != "minegrub-world-selection" ]]; then
THEME_PATHS+=("$dir")
THEME_NAMES+=("$DIR_NAME")
fi
fi
done
if [ ${#THEME_NAMES[@]} -eq 0 ]; then
log "No valid local theme folders found. Proceeding to online menu."
fi
# ------------------------------------------------------------------------------
# 3. Select Theme (TUI Menu)
# ------------------------------------------------------------------------------
section "Step 3/7" "Theme Selection"
INSTALL_MINEGRUB=false
SKIP_THEME=false
MINEGRUB_OPTION_NAME="Minegrub"
SKIP_OPTION_NAME="No theme (Skip/Clear)"
MINEGRUB_IDX=$((${#THEME_NAMES[@]} + 1))
SKIP_IDX=$((${#THEME_NAMES[@]} + 2))
TITLE_TEXT="Select GRUB Theme (60s Timeout)"
LINE_STR="───────────────────────────────────────────────────────"
echo -e "\n${H_PURPLE}${LINE_STR}${NC}"
echo -e "${H_PURPLE}${NC} ${BOLD}${TITLE_TEXT}${NC}"
echo -e "${H_PURPLE}${LINE_STR}${NC}"
for i in "${!THEME_NAMES[@]}"; do
NAME="${THEME_NAMES[$i]}"
DISPLAY_NAME=$(echo "$NAME" | sed -E 's/^[0-9]+//')
DISPLAY_IDX=$((i+1))
if [ "$i" -eq 0 ]; then
COLOR_STR=" ${H_CYAN}[$DISPLAY_IDX]${NC} ${DISPLAY_NAME} - ${H_GREEN}Default${NC}"
else
COLOR_STR=" ${H_CYAN}[$DISPLAY_IDX]${NC} ${DISPLAY_NAME}"
fi
echo -e "${H_PURPLE}${NC} ${COLOR_STR}"
done
MG_COLOR_STR=" ${H_CYAN}[$MINEGRUB_IDX]${NC} ${MINEGRUB_OPTION_NAME}"
echo -e "${H_PURPLE}${NC} ${MG_COLOR_STR}"
SKIP_COLOR_STR=" ${H_CYAN}[$SKIP_IDX]${NC} ${H_YELLOW}${SKIP_OPTION_NAME}${NC}"
echo -e "${H_PURPLE}${NC} ${SKIP_COLOR_STR}"
echo -e "${H_PURPLE}${LINE_STR}${NC}\n"
echo -ne " ${H_YELLOW}Enter choice [1-$SKIP_IDX]: ${NC}"
read -t 60 USER_CHOICE || true
if [ -z "${USER_CHOICE:-}" ]; then echo ""; fi
USER_CHOICE=${USER_CHOICE:-1}
if ! [[ "$USER_CHOICE" =~ ^[0-9]+$ ]] || [ "$USER_CHOICE" -lt 1 ] || [ "$USER_CHOICE" -gt "$SKIP_IDX" ]; then
log "Invalid choice or timeout. Defaulting to first option..."
USER_CHOICE=1
fi
if [ "$USER_CHOICE" -eq "$SKIP_IDX" ]; then
SKIP_THEME=true
info_kv "Selected" "None (Clear Theme)"
elif [ "$USER_CHOICE" -eq "$MINEGRUB_IDX" ]; then
INSTALL_MINEGRUB=true
info_kv "Selected" "Minegrub (Online Repository)"
else
SELECTED_INDEX=$((USER_CHOICE-1))
if [ -n "${THEME_NAMES[$SELECTED_INDEX]:-}" ]; then
THEME_PATH="${THEME_PATHS[$SELECTED_INDEX]}/theme.txt"
THEME_NAME="${THEME_NAMES[$SELECTED_INDEX]}"
info_kv "Selected" "Local: $THEME_NAME"
else
warn "Local theme array empty but selected. Defaulting to Minegrub."
INSTALL_MINEGRUB=true
fi
fi
# ------------------------------------------------------------------------------
# 4. Install & Configure Theme
# ------------------------------------------------------------------------------
section "Step 4/7" "Theme Configuration"
GRUB_CONF="/etc/default/grub"
if [ "$SKIP_THEME" == "true" ]; then
log "Clearing GRUB theme configuration..."
cleanup_minegrub
if [ -f "$GRUB_CONF" ]; then
if grep -q "^GRUB_THEME=" "$GRUB_CONF"; then
exe sed -i 's|^GRUB_THEME=|#GRUB_THEME=|' "$GRUB_CONF"
success "Disabled existing GRUB_THEME in configuration."
else
log "No active GRUB_THEME found to disable."
fi
fi
elif [ "$INSTALL_MINEGRUB" == "true" ]; then
log "Preparing to install Minegrub theme..."
if ! command -v git >/dev/null 2>&1; then
error "'git' is required to clone Minegrub but was not found. Skipping."
else
TEMP_MG_DIR=$(mktemp -d -t minegrub_install_XXXXXX)
log "Cloning Lxtharia/double-minegrub-menu..."
if exe git clone --depth 1 "https://github.com/Lxtharia/double-minegrub-menu.git" "$TEMP_MG_DIR"; then
if [ -f "$TEMP_MG_DIR/install.sh" ]; then
log "Executing Minegrub install.sh..."
(
cd "$TEMP_MG_DIR" || exit 1
exe chmod +x install.sh
exe ./install.sh
)
if [ $? -eq 0 ]; then
success "Minegrub theme successfully installed via its script."
else
error "Minegrub install.sh exited with an error."
fi
else
error "install.sh not found in the cloned repository!"
fi
else
error "Failed to clone Minegrub repository."
fi
[ -n "$TEMP_MG_DIR" ] && rm -rf "$TEMP_MG_DIR"
fi
else
cleanup_minegrub
if [ -f "$GRUB_CONF" ]; then
if grep -q "^GRUB_THEME=" "$GRUB_CONF"; then
exe sed -i "s|^GRUB_THEME=.*|GRUB_THEME=\"$THEME_PATH\"|" "$GRUB_CONF"
elif grep -q "^#GRUB_THEME=" "$GRUB_CONF"; then
exe sed -i "s|^#GRUB_THEME=.*|GRUB_THEME=\"$THEME_PATH\"|" "$GRUB_CONF"
else
echo "GRUB_THEME=\"$THEME_PATH\"" >> "$GRUB_CONF"
fi
if grep -q "^GRUB_TERMINAL_OUTPUT=\"console\"" "$GRUB_CONF"; then
exe sed -i 's/^GRUB_TERMINAL_OUTPUT="console"/#GRUB_TERMINAL_OUTPUT="console"/' "$GRUB_CONF"
fi
if ! grep -q "^GRUB_GFXMODE=" "$GRUB_CONF"; then
echo 'GRUB_GFXMODE=auto' >> "$GRUB_CONF"
fi
success "Configured GRUB to use theme: $THEME_NAME"
else
error "$GRUB_CONF not found."
exit 1
fi
fi
# ------------------------------------------------------------------------------
# 5. Add Shutdown/Reboot Menu Entries
# ------------------------------------------------------------------------------
section "Step 5/7" "Menu Entries"
log "Adding Power Options to GRUB menu..."
cp /etc/grub.d/40_custom /etc/grub.d/99_custom
echo 'menuentry "Reboot" --class restart {reboot}' >> /etc/grub.d/99_custom
echo 'menuentry "Shutdown" --class shutdown {halt}' >> /etc/grub.d/99_custom
success "Added grub menuentry 99-shutdown"
# ------------------------------------------------------------------------------
# 7. Apply Changes
# ------------------------------------------------------------------------------
section "Step 7/7" "Apply Changes"
log "Generating new GRUB configuration..."
if exe grub-mkconfig -o /boot/grub/grub.cfg; then
success "GRUB updated successfully."
else
error "Failed to update GRUB."
warn "You may need to run 'grub-mkconfig' manually."
fi
log "Module 07 completed."

496
scripts/99-apps.sh Normal file
View File

@ -0,0 +1,496 @@
#!/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."

102
scripts/de-undochange.sh Normal file
View File

@ -0,0 +1,102 @@
#!/bin/bash
# ==============================================================================
# undochange.sh - Emergency System Rollback Tool (Btrfs Assistant Edition)
# ==============================================================================
# Usage: sudo ./undochange.sh
# Description: Reverts system to "Before Shorin Setup" using btrfs-assistant
# This performs a Subvolume Rollback, not just a file diff undo.
# ==============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
TARGET_DESC="Before Desktop Environments"
# 1. Check Root
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}Error: Please run as root (sudo ./undochange.sh)${NC}"
exit 1
fi
# 2. Check Dependencies
if ! command -v snapper &> /dev/null; then
echo -e "${RED}Error: Snapper is not installed.${NC}"
exit 1
fi
if ! command -v btrfs-assistant &> /dev/null; then
echo -e "${RED}Error: btrfs-assistant is not installed.${NC}"
echo "Cannot perform subvolume rollback."
exit 1
fi
echo -e "${YELLOW}>>> Initializing Emergency Rollback (Target: '$TARGET_DESC')...${NC}"
# --- Helper Function: Rollback Logic from quickload ---
# Args: $1 = Subvolume Name (e.g., @ or @home), $2 = Snapper Config (e.g., root or home)
perform_rollback() {
local subvol="$1"
local snap_conf="$2"
echo -e "Checking config: ${YELLOW}$snap_conf${NC} for subvolume: ${YELLOW}$subvol${NC}..."
# 1. Get Snapper ID
# logic: list snapshots -> filter by description -> take the last one -> get ID
local snap_id=$(snapper -c "$snap_conf" list --columns number,description | grep "$TARGET_DESC" | tail -n 1 | awk '{print $1}')
if [ -z "$snap_id" ]; then
echo -e "${RED} [SKIP] Snapshot '$TARGET_DESC' not found in config '$snap_conf'.${NC}"
return 1
fi
echo -e " Found Snapshot ID: ${GREEN}$snap_id${NC}"
# 2. Map to Btrfs-Assistant Index
# Logic from quickload: Match Subvolume Name ($2) and Snapper ID ($3) to get Index ($1)
local ba_index=$(btrfs-assistant -l | awk -v v="$subvol" -v s="$snap_id" '$2==v && $3==s {print $1}')
if [ -z "$ba_index" ]; then
echo -e "${RED} [FAIL] Could not map Snapper ID $snap_id to Btrfs-Assistant index.${NC}"
return 1
fi
# 3. Execute Restore
echo -e " Executing rollback (Index: $ba_index)..."
if btrfs-assistant -r "$ba_index"; then
echo -e " ${GREEN}Success.${NC}"
return 0
else
echo -e " ${RED}Restore command failed.${NC}"
return 1
fi
}
# --- Main Execution ---
# 3. Rollback Root (Critical)
# Arch layout usually maps config 'root' to subvolume '@'
echo -e "${YELLOW}>>> Restoring Root Filesystem...${NC}"
if ! perform_rollback "@" "root"; then
echo -e "${RED}CRITICAL FAILURE: Failed to restore root partition.${NC}"
echo "Aborting operation to prevent partial system state."
exit 1
fi
# 4. Rollback Home (Optional)
# Only attempt if 'home' config exists
if snapper list-configs | grep -q "^home "; then
echo -e "${YELLOW}>>> Restoring Home Filesystem...${NC}"
# Arch layout usually maps config 'home' to subvolume '@home'
perform_rollback "@home" "home"
else
echo -e "No 'home' snapper config found, skipping home restore."
fi
# 5. Reboot
echo -e "${GREEN}System rollback successful.${NC}"
echo -e "${YELLOW}Rebooting in 3 seconds...${NC}"
sleep 3
reboot