516 lines
18 KiB
Bash
Executable File
516 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -Eeuo pipefail
|
||
|
||
readonly PROJECT="niri-beyond-glass"
|
||
readonly NIRI_TAG="v26.04"
|
||
readonly XWS_TAG="v0.8.2"
|
||
readonly RUST_TOOLCHAIN="1.85.1"
|
||
readonly DMS_REV="069ddab041c738236a8910e4c39b65d9628d3018"
|
||
readonly NIXPKGS_REV="d407951447dcd00442e97087bf374aad70c04cea"
|
||
readonly ARCHIVE_URL="https://git.k1412.top/wuyang/niri-beyond-glass/archive/main.tar.gz"
|
||
|
||
wallpaper=""
|
||
config_only=0
|
||
dry_run=0
|
||
rebuild=0
|
||
source_dir="${NBG_SOURCE_DIR:-}"
|
||
tmp_dir=""
|
||
selected_wallpaper=""
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
用法:install.sh [选项]
|
||
|
||
--wallpaper PATH 使用指定的静态壁纸
|
||
--config-only 只更新配置(要求 DMS 运行层已经安装)
|
||
--rebuild 强制重新编译 Niri 和 Xwayland Satellite
|
||
--dry-run 显示将执行的主要步骤,不修改系统
|
||
-h, --help 显示帮助
|
||
EOF
|
||
}
|
||
|
||
log() {
|
||
printf '\033[1;36m[Niri]\033[0m %s\n' "$*"
|
||
}
|
||
|
||
die() {
|
||
printf '\033[1;31m错误:\033[0m %s\n' "$*" >&2
|
||
exit 1
|
||
}
|
||
|
||
run() {
|
||
if (( dry_run )); then
|
||
printf '+'
|
||
printf ' %q' "$@"
|
||
printf '\n'
|
||
else
|
||
"$@"
|
||
fi
|
||
}
|
||
|
||
cleanup() {
|
||
if [[ -n "$tmp_dir" && -d "$tmp_dir" ]]; then
|
||
rm -rf "$tmp_dir"
|
||
fi
|
||
}
|
||
trap cleanup EXIT
|
||
|
||
while (($#)); do
|
||
case "$1" in
|
||
--wallpaper)
|
||
(($# >= 2)) || die "--wallpaper 缺少路径"
|
||
wallpaper="$2"
|
||
shift 2
|
||
;;
|
||
--config-only)
|
||
config_only=1
|
||
shift
|
||
;;
|
||
--rebuild)
|
||
rebuild=1
|
||
shift
|
||
;;
|
||
--dry-run)
|
||
dry_run=1
|
||
shift
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
die "未知参数:$1"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
[[ ${EUID:-$(id -u)} -ne 0 ]] || die "请用普通用户运行,脚本会在需要时调用 sudo。"
|
||
[[ -z "$wallpaper" || -f "$wallpaper" ]] || die "壁纸不存在:$wallpaper"
|
||
|
||
if [[ -r /etc/os-release ]]; then
|
||
# shellcheck disable=SC1091
|
||
. /etc/os-release
|
||
else
|
||
die "无法识别操作系统。"
|
||
fi
|
||
|
||
if [[ "${ID:-}" != "ubuntu" || "${VERSION_ID:-}" != "24.04" ]]; then
|
||
die "只支持 Ubuntu 24.04;当前是 ${PRETTY_NAME:-未知系统}。"
|
||
fi
|
||
|
||
case "$(uname -m)" in
|
||
x86_64|aarch64) ;;
|
||
*) die "目前只支持 x86_64 和 aarch64。" ;;
|
||
esac
|
||
|
||
fetch_source() {
|
||
if [[ -n "$source_dir" ]]; then
|
||
[[ -f "$source_dir/config/niri/config.kdl" ]] || die "NBG_SOURCE_DIR 不是有效源码目录。"
|
||
return
|
||
fi
|
||
|
||
tmp_dir="$(mktemp -d)"
|
||
log "下载配置仓库"
|
||
curl -fL --retry 3 "$ARCHIVE_URL" -o "$tmp_dir/source.tar.gz"
|
||
mkdir -p "$tmp_dir/source"
|
||
tar -xzf "$tmp_dir/source.tar.gz" -C "$tmp_dir/source" --strip-components=1
|
||
source_dir="$tmp_dir/source"
|
||
}
|
||
|
||
install_packages() {
|
||
log "安装 Ubuntu 24.04 依赖"
|
||
run sudo apt-get update
|
||
run sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||
build-essential clang git curl ca-certificates pkg-config \
|
||
meson ninja-build cmake hwdata \
|
||
libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev \
|
||
libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev \
|
||
libseat-dev libpipewire-0.3-dev libpango1.0-dev \
|
||
libdisplay-info-dev libcairo2-dev libglib2.0-dev \
|
||
libxcb1-dev libxcb-composite0-dev libxcb-randr0-dev \
|
||
libxcb-res0-dev libxcb-xfixes0-dev libxcb-cursor-dev \
|
||
xwayland xdg-desktop-portal-gnome xdg-desktop-portal-gtk \
|
||
alacritty \
|
||
nautilus policykit-1-gnome pipewire-pulse wireplumber \
|
||
brightnessctl playerctl grim slurp wl-clipboard libnotify-bin \
|
||
accountsservice jq nix-bin nix-setup-systemd \
|
||
fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji fonts-font-awesome \
|
||
fonts-inter fonts-jetbrains-mono
|
||
}
|
||
|
||
install_dms_runtime() {
|
||
local state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
|
||
local profile="$state_home/$PROJECT/nix-profile"
|
||
local dms_flake="github:AvengeMedia/DankMaterialShell/$DMS_REV"
|
||
local nixpkgs="github:NixOS/nixpkgs/$NIXPKGS_REV"
|
||
|
||
if [[ -x "$profile/bin/dms" && -x "$profile/bin/quickshell" &&
|
||
-x "$profile/bin/matugen" && -x "$profile/bin/dgop" &&
|
||
-x "$profile/bin/dsearch" ]]; then
|
||
log "DMS 运行层已经安装,跳过"
|
||
return
|
||
fi
|
||
|
||
log "安装隔离的 DMS 1.5.3 与 Quickshell 0.3.0"
|
||
run sudo usermod -aG nix-users "$USER"
|
||
run sudo systemctl enable --now nix-daemon.socket
|
||
run mkdir -p "$state_home/$PROJECT"
|
||
|
||
if (( dry_run )); then
|
||
printf '+ sudo -u %q -g nix-users nix profile install --profile %q ...\n' \
|
||
"$USER" "$profile"
|
||
return
|
||
fi
|
||
|
||
local attempt
|
||
for attempt in 1 2 3 4 5; do
|
||
if sudo -u "$USER" -g nix-users env \
|
||
HOME="$HOME" USER="$USER" \
|
||
NIX_CONFIG="experimental-features = nix-command flakes" \
|
||
nix profile install --profile "$profile" \
|
||
"$dms_flake#dms-shell" \
|
||
"$nixpkgs#quickshell" \
|
||
"$nixpkgs#matugen" \
|
||
"$nixpkgs#dgop" \
|
||
"$nixpkgs#dsearch"; then
|
||
break
|
||
fi
|
||
(( attempt < 5 )) || die "DMS 运行层安装失败,请检查网络后重新运行脚本。"
|
||
log "下载中断,正在重试($attempt/5)"
|
||
sleep 3
|
||
done
|
||
|
||
[[ -x "$profile/bin/dms" && -x "$profile/bin/quickshell" ]] ||
|
||
die "DMS 或 Quickshell 安装不完整。"
|
||
}
|
||
|
||
install_rust() {
|
||
export RUSTUP_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/$PROJECT/rustup"
|
||
export CARGO_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/$PROJECT/cargo"
|
||
export PATH="$CARGO_HOME/bin:$PATH"
|
||
|
||
if [[ ! -x "$CARGO_HOME/bin/rustup" ]]; then
|
||
log "安装隔离的 Rust $RUST_TOOLCHAIN 工具链"
|
||
if (( dry_run )); then
|
||
printf '+ curl https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal --default-toolchain %q\n' "$RUST_TOOLCHAIN"
|
||
return
|
||
fi
|
||
local rustup_installer
|
||
rustup_installer="$(mktemp)"
|
||
curl --proto '=https' --tlsv1.2 -fsSL --retry 5 --retry-all-errors \
|
||
https://sh.rustup.rs -o "$rustup_installer"
|
||
RUSTUP_MAX_RETRIES=5 sh "$rustup_installer" -y --no-modify-path \
|
||
--profile minimal --default-toolchain "$RUST_TOOLCHAIN"
|
||
rm -f "$rustup_installer"
|
||
elif ! "$CARGO_HOME/bin/rustup" run "$RUST_TOOLCHAIN" rustc --version >/dev/null 2>&1; then
|
||
RUSTUP_MAX_RETRIES=5 run "$CARGO_HOME/bin/rustup" toolchain install \
|
||
"$RUST_TOOLCHAIN" --profile minimal
|
||
fi
|
||
|
||
run "$CARGO_HOME/bin/rustup" set auto-self-update disable
|
||
run "$CARGO_HOME/bin/rustup" default "$RUST_TOOLCHAIN"
|
||
}
|
||
|
||
version_ge() {
|
||
[[ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" == "$2" ]]
|
||
}
|
||
|
||
install_libdisplay_info() {
|
||
local installed=""
|
||
installed="$(pkg-config --modversion libdisplay-info 2>/dev/null || true)"
|
||
if [[ -n "$installed" ]] && version_ge "$installed" "0.3.0"; then
|
||
return
|
||
fi
|
||
|
||
log "编译 libdisplay-info 0.3.0(Ubuntu 24.04 自带版本较旧)"
|
||
local build_root="${XDG_CACHE_HOME:-$HOME/.cache}/$PROJECT/build"
|
||
local src="$build_root/libdisplay-info"
|
||
run mkdir -p "$build_root"
|
||
if [[ ! -d "$src/.git" ]]; then
|
||
run git clone --depth 1 --branch 0.3.0 \
|
||
https://gitlab.freedesktop.org/emersion/libdisplay-info.git "$src"
|
||
fi
|
||
run meson setup "$src/build" "$src" --prefix=/usr/local --buildtype=release --wipe
|
||
run ninja -C "$src/build"
|
||
run sudo ninja -C "$src/build" install
|
||
run sudo ldconfig
|
||
}
|
||
|
||
install_niri() {
|
||
local current=""
|
||
current="$(niri --version 2>/dev/null || true)"
|
||
if (( ! rebuild )) && [[ "$current" == *"26.4"* || "$current" == *"26.04"* ]]; then
|
||
log "已安装兼容的 $current,跳过编译"
|
||
return
|
||
fi
|
||
|
||
log "编译 Niri $NIRI_TAG"
|
||
local build_root="${XDG_CACHE_HOME:-$HOME/.cache}/$PROJECT/build"
|
||
local src="$build_root/niri"
|
||
run mkdir -p "$build_root"
|
||
if [[ ! -d "$src/.git" ]]; then
|
||
run git clone --depth 1 --branch "$NIRI_TAG" https://github.com/niri-wm/niri.git "$src"
|
||
else
|
||
run git -C "$src" fetch --depth 1 origin "refs/tags/$NIRI_TAG:refs/tags/$NIRI_TAG"
|
||
run git -C "$src" checkout -f "$NIRI_TAG"
|
||
fi
|
||
run cargo build --manifest-path "$src/Cargo.toml" --release --locked
|
||
|
||
run sudo install -Dm755 "$src/target/release/niri" /usr/local/bin/niri
|
||
run sudo install -Dm755 "$src/resources/niri-session" /usr/local/bin/niri-session
|
||
run sudo install -Dm644 "$src/resources/niri.service" /usr/local/lib/systemd/user/niri.service
|
||
run sudo install -Dm644 "$src/resources/niri-shutdown.target" /usr/local/lib/systemd/user/niri-shutdown.target
|
||
run sudo install -Dm644 "$src/resources/niri.desktop" /usr/local/share/wayland-sessions/niri.desktop
|
||
run sudo install -Dm644 "$src/resources/niri-portals.conf" /usr/local/share/xdg-desktop-portal/niri-portals.conf
|
||
}
|
||
|
||
install_xwayland_satellite() {
|
||
if (( ! rebuild )) && command -v xwayland-satellite >/dev/null 2>&1; then
|
||
return
|
||
fi
|
||
|
||
log "编译 Xwayland Satellite $XWS_TAG"
|
||
local build_root="${XDG_CACHE_HOME:-$HOME/.cache}/$PROJECT/build"
|
||
local src="$build_root/xwayland-satellite"
|
||
run mkdir -p "$build_root"
|
||
if [[ ! -d "$src/.git" ]]; then
|
||
run git clone --depth 1 --branch "$XWS_TAG" \
|
||
https://github.com/Supreeeme/xwayland-satellite.git "$src"
|
||
else
|
||
run git -C "$src" fetch --depth 1 origin "refs/tags/$XWS_TAG:refs/tags/$XWS_TAG"
|
||
run git -C "$src" checkout -f "$XWS_TAG"
|
||
fi
|
||
run cargo build --manifest-path "$src/Cargo.toml" --release --locked
|
||
run sudo install -Dm755 "$src/target/release/xwayland-satellite" /usr/local/bin/xwayland-satellite
|
||
}
|
||
|
||
backup_and_install_config() {
|
||
local config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||
local state_home="${XDG_STATE_HOME:-$HOME/.local/state}"
|
||
local stamp
|
||
stamp="$(date +%Y%m%d-%H%M%S)"
|
||
local backup_root="$state_home/$PROJECT/backups/$stamp"
|
||
local backup="$backup_root/config"
|
||
local original="$state_home/$PROJECT/original"
|
||
local names=(niri waybar fuzzel mako swaylock niri-beyond-glass DankMaterialShell alacritty)
|
||
|
||
select_wallpaper "$config_home/niri-beyond-glass/wallpaper"
|
||
|
||
log "备份并安装用户配置"
|
||
run mkdir -p "$backup"
|
||
local name
|
||
for name in "${names[@]}"; do
|
||
if [[ -e "$config_home/$name" ]]; then
|
||
run cp -a "$config_home/$name" "$backup/$name"
|
||
fi
|
||
done
|
||
if [[ -d "$state_home/DankMaterialShell" ]]; then
|
||
run mkdir -p "$backup_root/state"
|
||
run cp -a "$state_home/DankMaterialShell" "$backup_root/state/DankMaterialShell"
|
||
fi
|
||
|
||
run mkdir -p "$original/config" "$original/absent"
|
||
for name in "${names[@]}"; do
|
||
if [[ ! -e "$original/config/$name" && ! -e "$original/absent/$name" ]]; then
|
||
if [[ -e "$config_home/$name" ]]; then
|
||
run cp -a "$config_home/$name" "$original/config/$name"
|
||
else
|
||
run touch "$original/absent/$name"
|
||
fi
|
||
fi
|
||
done
|
||
run touch "$original/.captured"
|
||
|
||
if (( dry_run )); then
|
||
printf '+ install DMS, Niri and Alacritty style while preserving wallpaper %q\n' \
|
||
"$selected_wallpaper"
|
||
return
|
||
fi
|
||
|
||
mkdir -p \
|
||
"$config_home/niri" \
|
||
"$config_home/niri/dms" \
|
||
"$config_home/niri-beyond-glass" \
|
||
"$config_home/DankMaterialShell/themes" \
|
||
"$config_home/alacritty" \
|
||
"$config_home/systemd/user" \
|
||
"$state_home/DankMaterialShell" \
|
||
"$HOME/.local/bin" \
|
||
"$HOME/Pictures/Screenshots"
|
||
|
||
install -m644 "$source_dir/config/niri/config.kdl" "$config_home/niri/config.kdl"
|
||
sed "s|__HOME__|$HOME|g" "$source_dir/config/niri/beyond-glass.kdl" \
|
||
> "$config_home/niri/beyond-glass.kdl"
|
||
install -m644 "$source_dir"/config/niri/dms/*.kdl "$config_home/niri/dms/"
|
||
|
||
local theme_file="$config_home/DankMaterialShell/themes/beyond-glass.json"
|
||
install -m644 "$source_dir/config/dms/beyond-glass.json" "$theme_file"
|
||
|
||
local settings_tmp session_tmp
|
||
settings_tmp="$(mktemp)"
|
||
session_tmp="$(mktemp)"
|
||
jq --arg wallpaper "$selected_wallpaper" --arg theme "$theme_file" '
|
||
.customThemeFile = $theme
|
||
| .greeterWallpaperPath = $wallpaper
|
||
| .lockScreenWallpaperPath = $wallpaper
|
||
| .lockScreenVideoPath = ""
|
||
' "$source_dir/config/dms/settings.json" > "$settings_tmp"
|
||
jq --arg wallpaper "$selected_wallpaper" '
|
||
.wallpaperPath = $wallpaper
|
||
| .wallpaperPathLight = $wallpaper
|
||
| .wallpaperPathDark = $wallpaper
|
||
| .monitorWallpapers = {}
|
||
| .monitorWallpapersLight = {}
|
||
| .monitorWallpapersDark = {}
|
||
| .perMonitorWallpaper = false
|
||
' "$source_dir/config/dms/session.json" > "$session_tmp"
|
||
install -m644 "$settings_tmp" "$config_home/DankMaterialShell/settings.json"
|
||
install -m644 "$session_tmp" "$state_home/DankMaterialShell/session.json"
|
||
rm -f "$settings_tmp" "$session_tmp"
|
||
touch "$config_home/DankMaterialShell/.firstlaunch" \
|
||
"$config_home/DankMaterialShell/.changelog-1.5"
|
||
|
||
install -m644 "$source_dir/config/alacritty/alacritty.toml" \
|
||
"$config_home/alacritty/alacritty.toml"
|
||
install -m644 "$source_dir/config/systemd/dms.service" \
|
||
"$config_home/systemd/user/dms.service"
|
||
install -m755 "$source_dir/scripts/niri-beyond-browser" \
|
||
"$HOME/.local/bin/niri-beyond-browser"
|
||
install -m755 "$source_dir/scripts/niri-beyond-refresh" \
|
||
"$HOME/.local/bin/niri-beyond-refresh"
|
||
|
||
local profile="$state_home/$PROJECT/nix-profile"
|
||
local binary
|
||
for binary in dms quickshell qs matugen dgop dsearch; do
|
||
if [[ -x "$profile/bin/$binary" ]]; then
|
||
ln -sfn "$profile/bin/$binary" "$HOME/.local/bin/$binary"
|
||
fi
|
||
done
|
||
|
||
printf '%s\n' "$selected_wallpaper" > "$config_home/niri-beyond-glass/wallpaper"
|
||
}
|
||
|
||
select_wallpaper() {
|
||
local state_file="$1"
|
||
local selected="$wallpaper"
|
||
|
||
if [[ -z "$selected" ]]; then
|
||
local existing=""
|
||
if [[ -r "$state_file" ]]; then
|
||
existing="$(head -n1 "$state_file")"
|
||
fi
|
||
if [[ -n "$existing" && -f "$existing" ]]; then
|
||
selected="$existing"
|
||
fi
|
||
fi
|
||
|
||
if [[ -z "$selected" ]]; then
|
||
local session="${XDG_STATE_HOME:-$HOME/.local/state}/DankMaterialShell/session.json"
|
||
local existing=""
|
||
if [[ -r "$session" ]]; then
|
||
existing="$(jq -r '.wallpaperPath // empty' "$session" 2>/dev/null || true)"
|
||
fi
|
||
if [[ -n "$existing" && -f "$existing" ]]; then
|
||
selected="$existing"
|
||
fi
|
||
fi
|
||
|
||
if [[ -z "$selected" ]] && command -v gsettings >/dev/null 2>&1; then
|
||
local uri=""
|
||
uri="$(gsettings get org.gnome.desktop.background picture-uri-dark 2>/dev/null \
|
||
| tr -d "'" || true)"
|
||
uri="${uri#file://}"
|
||
if [[ -n "$uri" && -f "$uri" ]]; then
|
||
selected="$uri"
|
||
fi
|
||
fi
|
||
|
||
if [[ -z "$selected" ]]; then
|
||
local candidates=(
|
||
/usr/share/backgrounds/warty-final-ubuntu.png
|
||
/usr/share/backgrounds/ubuntu-default-greyscale-wallpaper.png
|
||
)
|
||
local candidate
|
||
for candidate in "${candidates[@]}"; do
|
||
if [[ -f "$candidate" ]]; then
|
||
selected="$candidate"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [[ -z "$selected" ]]; then
|
||
selected="$(find /usr/share/backgrounds -maxdepth 2 -type f \
|
||
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' \) \
|
||
-print -quit 2>/dev/null || true)"
|
||
fi
|
||
|
||
[[ -n "$selected" && -f "$selected" ]] ||
|
||
die "没有找到可用壁纸;可用 --wallpaper PATH 指定目标机上的图片。"
|
||
selected_wallpaper="$selected"
|
||
log "保留目标机壁纸:$selected_wallpaper"
|
||
}
|
||
|
||
activate_dms() {
|
||
local profile="${XDG_STATE_HOME:-$HOME/.local/state}/$PROJECT/nix-profile"
|
||
|
||
if (( dry_run )); then
|
||
printf '+ stop legacy shell processes and enable dms.service\n'
|
||
return
|
||
fi
|
||
|
||
if ! systemctl --user daemon-reload 2>/dev/null; then
|
||
log "当前没有可用的 systemd 用户会话;DMS 将在下次 Niri 登录时启动"
|
||
return
|
||
fi
|
||
systemctl --user disable --now waybar.service mako.service 2>/dev/null || true
|
||
pkill -x waybar 2>/dev/null || true
|
||
pkill -x mako 2>/dev/null || true
|
||
pkill -x swaybg 2>/dev/null || true
|
||
pkill -x swayidle 2>/dev/null || true
|
||
systemctl --user enable dms.service
|
||
|
||
"$profile/bin/dms" auth resolve-lock --quiet >/dev/null 2>&1 || true
|
||
|
||
if [[ -n "${WAYLAND_DISPLAY:-}" ]]; then
|
||
systemctl --user restart dms.service
|
||
fi
|
||
}
|
||
|
||
validate_config() {
|
||
if (( dry_run )); then
|
||
printf '+ niri validate -c %q\n' "${XDG_CONFIG_HOME:-$HOME/.config}/niri/config.kdl"
|
||
return
|
||
fi
|
||
command -v niri >/dev/null 2>&1 || die "找不到 niri,无法验证配置。"
|
||
niri validate -c "${XDG_CONFIG_HOME:-$HOME/.config}/niri/config.kdl"
|
||
}
|
||
|
||
main() {
|
||
fetch_source
|
||
|
||
if (( ! config_only )); then
|
||
install_packages
|
||
install_rust
|
||
install_libdisplay_info
|
||
install_niri
|
||
install_xwayland_satellite
|
||
install_dms_runtime
|
||
else
|
||
local profile="${XDG_STATE_HOME:-$HOME/.local/state}/$PROJECT/nix-profile"
|
||
[[ -x "$profile/bin/dms" && -x "$profile/bin/quickshell" ]] ||
|
||
die "--config-only 需要先完成一次完整安装。"
|
||
fi
|
||
|
||
backup_and_install_config
|
||
validate_config
|
||
activate_dms
|
||
|
||
log "安装完成"
|
||
printf '\nDMS 个性化已迁移,目标机原壁纸和显示器布局未修改。\n'
|
||
printf '当前若在 Niri 中,顶栏会自动重启;否则注销后选择“Niri”登录。\n'
|
||
printf 'GNOME 和 GDM 均未修改;快捷键说明:Super+Shift+/。\n'
|
||
}
|
||
|
||
main
|