2
This commit is contained in:
150
scripts/00-btrfs-init.sh
Normal file
150
scripts/00-btrfs-init.sh
Normal 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
612
scripts/00-utils.sh
Normal 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
173
scripts/01-base.sh
Normal 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
186
scripts/02-musthave.sh
Normal 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
|
||||
91
scripts/02a-dualboot-fix.sh
Normal file
91
scripts/02a-dualboot-fix.sh
Normal 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
153
scripts/03-user.sh
Normal 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
211
scripts/03b-gpu-driver.sh
Normal 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."
|
||||
87
scripts/03c-snapshot-before-desktop.sh
Normal file
87
scripts/03c-snapshot-before-desktop.sh
Normal 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
617
scripts/04-niri-setup.sh
Normal 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."
|
||||
435
scripts/04b-kdeplasma-setup.sh
Normal file
435
scripts/04b-kdeplasma-setup.sh
Normal 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."
|
||||
361
scripts/04c-dms-quickshell.sh
Normal file
361
scripts/04c-dms-quickshell.sh
Normal 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
388
scripts/04d-gnome.sh
Normal 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
|
||||
148
scripts/04e-illogical-impulse-end4-quickshell.sh
Normal file
148
scripts/04e-illogical-impulse-end4-quickshell.sh
Normal 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."
|
||||
137
scripts/04f-ambxst-quickshell.sh
Normal file
137
scripts/04f-ambxst-quickshell.sh
Normal 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
|
||||
142
scripts/04g-caelestia-quickshell.sh
Normal file
142
scripts/04g-caelestia-quickshell.sh
Normal 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"
|
||||
179
scripts/04h-shorindms-quickshell.sh
Normal file
179
scripts/04h-shorindms-quickshell.sh
Normal 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
|
||||
219
scripts/04i-shorin-hyprniri-quickshell.sh
Normal file
219
scripts/04i-shorin-hyprniri-quickshell.sh
Normal 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
151
scripts/04j-minimal-niri.sh
Normal 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
|
||||
180
scripts/04k-shorin-noctalia-quickshell.sh
Normal file
180
scripts/04k-shorin-noctalia-quickshell.sh
Normal 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
|
||||
135
scripts/05-verify-desktop.sh
Normal file
135
scripts/05-verify-desktop.sh
Normal 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
346
scripts/07-grub-theme.sh
Normal 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
496
scripts/99-apps.sh
Normal 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
102
scripts/de-undochange.sh
Normal 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
|
||||
Reference in New Issue
Block a user