#!/usr/bin/env bash # # fedora-niri-dms-setup.sh # Turns a fresh "Fedora Custom Operating System" (terminal-only) install # into a niri + DankMaterialShell desktop with the DMS greeter. # # Usage (as your normal user, NOT root): # curl -fsSL https://gitea.schantli.ca/jschantli/fedora-niri-dms/raw/branch/main/fedora-niri-dms-setup.sh -o setup.sh # bash setup.sh # # Safe to re-run: every step checks before changing things. set -euo pipefail # ---------- helpers ---------------------------------------------------------- BOLD=$'\e[1m'; GREEN=$'\e[32m'; YELLOW=$'\e[33m'; RED=$'\e[31m'; RESET=$'\e[0m' step() { echo; echo "${BOLD}${GREEN}==> $*${RESET}"; } info() { echo " $*"; } warn() { echo "${YELLOW} ! $*${RESET}"; } die() { echo "${RED}ERROR: $*${RESET}" >&2; exit 1; } ask() { # ask "Question?" -> returns 0 for yes, 1 for no (default no) local reply read -r -p " $1 [y/N] " reply [[ "${reply,,}" == y || "${reply,,}" == yes ]] } dnf_install() { sudo dnf install -y "$@"; } # ---------- pre-flight ------------------------------------------------------- [[ $EUID -eq 0 ]] && die "Run this as your normal user (it uses sudo when needed), not as root." [[ -r /etc/fedora-release ]] || die "This script is for Fedora only." command -v sudo >/dev/null || die "sudo is not installed." FEDORA_VER=$(rpm -E %fedora) VIRT=$(systemd-detect-virt 2>/dev/null || true) echo "${BOLD}Fedora ${FEDORA_VER} -> niri + DankMaterialShell${RESET}" [[ -n "$VIRT" && "$VIRT" != "none" ]] && warn "Running in a VM ($VIRT). niri needs 3D acceleration enabled in the VM settings, or you'll get a black screen." # Ask optional questions up front so the rest can run unattended. echo echo "${BOLD}Optional extras${RESET} (answer now, then the script runs on its own):" WANT_STEAM=false; ask "Install Steam + gamescope?" && WANT_STEAM=true WANT_OPENVPN=false; ask "Install OpenVPN support for NetworkManager (for PIA .ovpn files)?" && WANT_OPENVPN=true WANT_FPRINT=false; ask "Set up the fingerprint reader (Framework laptops)?" && WANT_FPRINT=true WANT_EASYFX=false; ask "Install EasyEffects (Framework speaker profile)?" && WANT_EASYFX=true sudo -v # cache sudo credentials early # keep sudo alive for the duration of the script while true; do sudo -n true; sleep 50; kill -0 "$$" || exit; done 2>/dev/null & # ---------- 1. dnf tuning + updates ----------------------------------------- step "Tuning dnf and updating the system" if ! grep -q '^max_parallel_downloads' /etc/dnf/dnf.conf 2>/dev/null; then echo 'max_parallel_downloads=10' | sudo tee -a /etc/dnf/dnf.conf >/dev/null info "Enabled parallel downloads" fi # dnf5-plugins provides 'copr' and 'config-manager'; not included in a minimal install dnf_install dnf5-plugins pciutils sudo dnf upgrade --refresh -y # ---------- 2. RPM Fusion + OpenH264 ----------------------------------------- step "Enabling RPM Fusion (free + non-free) and OpenH264" if ! rpm -q rpmfusion-free-release >/dev/null 2>&1; then dnf_install \ "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${FEDORA_VER}.noarch.rpm" \ "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${FEDORA_VER}.noarch.rpm" else info "RPM Fusion already enabled" fi sudo dnf config-manager setopt fedora-cisco-openh264.enabled=1 || warn "Could not enable OpenH264 repo" # ---------- 3. Graphics ------------------------------------------------------ step "Installing graphics drivers" dnf_install mesa-dri-drivers mesa-vulkan-drivers vulkan-loader GPU_INFO=$(lspci -nn | grep -Ei 'vga|3d|display' || true) info "Detected: ${GPU_INFO:-unknown}" NVIDIA=false if grep -qi 'amd\|ati' <<<"$GPU_INFO"; then info "AMD GPU -> freeworld VA-API drivers" if rpm -q mesa-va-drivers >/dev/null 2>&1; then sudo dnf swap -y mesa-va-drivers mesa-va-drivers-freeworld else dnf_install mesa-va-drivers-freeworld fi fi if grep -qi 'intel' <<<"$GPU_INFO"; then info "Intel GPU -> intel-media-driver" dnf_install intel-media-driver fi if grep -qi 'nvidia' <<<"$GPU_INFO"; then info "NVIDIA GPU -> akmod-nvidia" dnf_install akmod-nvidia xorg-x11-drv-nvidia-cuda NVIDIA=true fi # ---------- 4. Codecs -------------------------------------------------------- step "Installing full multimedia codecs" if rpm -q ffmpeg-free >/dev/null 2>&1; then sudo dnf swap -y ffmpeg-free ffmpeg --allowerasing else dnf_install ffmpeg --allowerasing fi sudo dnf group install -y multimedia --allowerasing || warn "Multimedia group had conflicts; core codecs are still installed" # ---------- 5. Audio, Bluetooth, power, firmware ----------------------------- step "Installing PipeWire audio" dnf_install pipewire pipewire-pulseaudio pipewire-alsa \ pipewire-jack-audio-connection-kit wireplumber alsa-sof-firmware step "Installing Bluetooth" dnf_install bluez sudo systemctl enable --now bluetooth step "Installing power profiles and firmware updater" dnf_install tuned-ppd fwupd sudo systemctl enable --now tuned tuned-ppd # ---------- 6. niri + DankMaterialShell -------------------------------------- step "Installing niri and DankMaterialShell" sudo dnf copr enable -y avengemedia/dms # niri "recommends" these, but DMS replaces all of them. Waybar in particular # gives you a second bar on top of the DMS one, and mako fights DMS over # notifications. Keep them out (and remove them on a re-run). DMS_REPLACES=(waybar mako fuzzel swaybg swayidle swaylock alacritty) EXCLUDE=$(IFS=,; echo "${DMS_REPLACES[*]}") if ! dnf_install niri dms xwayland-satellite --exclude="$EXCLUDE"; then warn "Install with exclusions failed; retrying without them" dnf_install niri dms xwayland-satellite fi REMOVE=() for pkg in "${DMS_REPLACES[@]}"; do rpm -q "$pkg" >/dev/null 2>&1 || continue # only remove it if nothing hard-depends on it if rpm -q --whatrequires "$pkg" >/dev/null 2>&1; then warn "Keeping $pkg (another package requires it)" else REMOVE+=("$pkg") fi done if (( ${#REMOVE[@]} )); then info "Removing tools DMS replaces: ${REMOVE[*]}" sudo dnf remove -y "${REMOVE[@]}" fi info "Starting DMS with niri" systemctl --user add-wants niri.service dms # ---------- 6a. xwayland-satellite menu fix ----------------------------------- # xwayland-satellite <= 0.8.2 makes X11 popup menus (e.g. Steam's) flicker and # vanish under niri. The fix is upstream commit add2795 but not yet in a Fedora # release. Build that commit into /usr/local/bin (which comes before /usr/bin in # PATH), and install a boot-time check that deletes it once Fedora ships a newer # version, so Fedora's package takes over again automatically. XWLS_BROKEN_MAX=0.8.2 XWLS_FIX_REV=add2795134593faafce60e404a0a75df68e9ee0c XWLS_FEDORA=$(rpm -q --qf '%{VERSION}' xwayland-satellite 2>/dev/null || echo 0) # true if $1 <= $2 (version compare) ver_le() { [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" == "$1" ]]; } if ver_le "$XWLS_FEDORA" "$XWLS_BROKEN_MAX"; then step "Building fixed xwayland-satellite (Fedora has $XWLS_FEDORA, menus are broken up to $XWLS_BROKEN_MAX)" if [[ -x /usr/local/bin/xwayland-satellite ]]; then info "Already built, skipping" else dnf_install cargo clang git pkgconf libxcb-devel xcb-util-cursor-devel XWLS_BUILD=$(mktemp -d) if cargo install --git https://github.com/Supreeeme/xwayland-satellite \ --rev "$XWLS_FIX_REV" --locked --root "$XWLS_BUILD"; then sudo install -m 755 "$XWLS_BUILD/bin/xwayland-satellite" /usr/local/bin/xwayland-satellite info "Installed to /usr/local/bin/xwayland-satellite" else warn "Build failed; keeping Fedora's xwayland-satellite (Steam menus may misbehave)" fi rm -rf "$XWLS_BUILD" fi if [[ -x /usr/local/bin/xwayland-satellite ]]; then info "Installing the auto-cleanup check" sudo mkdir -p /usr/local/libexec sudo tee /usr/local/libexec/xwls-git-check >/dev/null </dev/null) || exit 0 newest=\$(printf '%s\n%s\n' "$XWLS_BROKEN_MAX" "\$cur" | sort -V | tail -1) if [[ "\$newest" != "$XWLS_BROKEN_MAX" ]]; then rm -f /usr/local/bin/xwayland-satellite systemctl disable xwls-git-check.service rm -f /etc/systemd/system/xwls-git-check.service /usr/local/libexec/xwls-git-check systemctl daemon-reload echo "Fedora xwayland-satellite \$cur installed; removed custom build" fi EOF sudo chmod 755 /usr/local/libexec/xwls-git-check sudo tee /etc/systemd/system/xwls-git-check.service >/dev/null <<'EOF' [Unit] Description=Remove custom xwayland-satellite build once Fedora's is newer Before=display-manager.service greetd.service [Service] Type=oneshot ExecStart=/usr/local/libexec/xwls-git-check [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable xwls-git-check.service fi else info "Fedora's xwayland-satellite $XWLS_FEDORA already includes the menu fix" fi # ---------- 6b. niri config --------------------------------------------------- step "Configuring niri for DMS and Ptyxis" NIRI_CFG="$HOME/.config/niri/config.kdl" mkdir -p "$(dirname "$NIRI_CFG")" if [[ ! -f "$NIRI_CFG" ]]; then # niri writes its default config on first launch; create it now instead so # it can be adjusted before you ever log in. DEFAULT_CFG=$(rpm -ql niri 2>/dev/null | grep -m1 'default-config.kdl' || true) if [[ -n "$DEFAULT_CFG" && -f "$DEFAULT_CFG" ]]; then cp "$DEFAULT_CFG" "$NIRI_CFG" info "Copied niri's default config from $DEFAULT_CFG" else NIRI_VER=$(rpm -q --qf '%{VERSION}' niri) if curl -fsSL "https://raw.githubusercontent.com/niri-wm/niri/v${NIRI_VER}/resources/default-config.kdl" -o "$NIRI_CFG"; then info "Downloaded niri ${NIRI_VER} default config" else rm -f "$NIRI_CFG" warn "Couldn't get niri's default config. Log into niri once, then re-run this script to apply the tweaks." fi fi fi # Our settings live in their own file, included at the END of config.kdl. # niri applies includes in order and a later bind overrides an earlier one, so # these win over niri's defaults without editing them. Re-running the script # simply rewrites this file. NIRI_INC="$HOME/.config/niri/dms-setup.kdl" cat > "$NIRI_INC" <<'EOF' // Managed by fedora-niri-dms-setup; re-running the script overwrites this file. // Put your own tweaks in config.kdl AFTER the include line to override these. binds { // Ptyxis instead of Alacritty Mod+T hotkey-overlay-title="Open a Terminal: Ptyxis" { spawn "ptyxis"; } // DMS replaces fuzzel and swaylock Mod+D hotkey-overlay-title="App Launcher (DMS)" { spawn "dms" "ipc" "call" "spotlight" "toggle"; } Super+Alt+L hotkey-overlay-title="Lock the Screen (DMS)" { spawn "dms" "ipc" "call" "lock" "lock"; } // DMS panels Mod+Alt+V hotkey-overlay-title="Clipboard History (DMS)" { spawn "dms" "ipc" "call" "clipboard" "toggle"; } Mod+Alt+N hotkey-overlay-title="Notifications (DMS)" { spawn "dms" "ipc" "call" "notifications" "toggle"; } Mod+Alt+C hotkey-overlay-title="Control Centre (DMS)" { spawn "dms" "ipc" "call" "control-center" "toggle"; } Mod+Alt+S hotkey-overlay-title="DMS Settings" { spawn "dms" "ipc" "call" "settings" "toggle"; } } EOF if [[ -f "$NIRI_CFG" ]]; then [[ -f "$NIRI_CFG.orig" ]] || cp "$NIRI_CFG" "$NIRI_CFG.orig" BEFORE=$(mktemp) cp "$NIRI_CFG" "$BEFORE" # The one edit an include can't do: stop Waybar autostarting (DMS is the bar). sed -i -E 's|^([[:space:]]*)spawn-at-startup "waybar"|\1// spawn-at-startup "waybar" // DMS provides the bar|' "$NIRI_CFG" if ! grep -qF 'include "dms-setup.kdl"' "$NIRI_CFG"; then printf '\n// fedora-niri-dms-setup: DMS keybinds and Ptyxis (see dms-setup.kdl)\ninclude "dms-setup.kdl"\n' >> "$NIRI_CFG" fi if command -v niri >/dev/null && ! niri validate -c "$NIRI_CFG" >/dev/null 2>&1; then warn "niri reports a config problem; reverting this run's changes" cp "$BEFORE" "$NIRI_CFG" else info "Super+T opens Ptyxis; Super+D launcher, Super+Alt+L lock," info "Super+Alt+V clipboard, Super+Alt+N notifications, Super+Alt+C control centre, Super+Alt+S settings" info "Original config saved as $NIRI_CFG.orig" fi rm -f "$BEFORE" fi # ---------- 7. Desktop essentials -------------------------------------------- step "Installing desktop essentials" dnf_install \ ptyxis nautilus firefox \ xdg-desktop-portal-gnome xdg-desktop-portal-gtk \ gnome-keyring gnome-keyring-pam \ google-noto-sans-fonts google-noto-color-emoji-fonts \ xdg-user-dirs flatpak fastfetch info "Auto-unlocking the keyring at login" sudo authselect enable-feature with-pam-gnome-keyring || warn "authselect failed; keyring may ask for a password at login" info "Creating home folders" xdg-user-dirs-update info "Adding Flathub" flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo # ---------- 7b. Shell: zsh + plugins + Starship ------------------------------ step "Setting up zsh with autosuggestions, highlighting and Starship" dnf_install zsh zsh-autosuggestions zsh-syntax-highlighting \ eza bat fzf zoxide fontconfig xz if ! command -v starship >/dev/null; then if ! dnf_install starship 2>/dev/null; then info "Starship not in Fedora's repos, trying the atim/starship COPR" if ! { sudo dnf copr enable -y atim/starship && dnf_install starship; }; then info "COPR failed, using Starship's official installer" curl -fsSL https://starship.rs/install.sh | sudo sh -s -- -y -b /usr/local/bin \ || warn "Starship install failed; the prompt will fall back to zsh's default" fi fi fi # JetBrains Mono Nerd Font, so Starship and eza icons render properly NF_DIR="$HOME/.local/share/fonts/JetBrainsMonoNerd" if [[ ! -d "$NF_DIR" ]]; then info "Installing JetBrains Mono Nerd Font" mkdir -p "$NF_DIR" if curl -fsSL https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.tar.xz \ | tar -xJ -C "$NF_DIR"; then fc-cache -f "$NF_DIR" >/dev/null else rm -rf "$NF_DIR" warn "Nerd Font download failed; icons may show as boxes" fi fi # ~/.zshrc (managed by this script; put your own additions in ~/.zshrc.local) ZSHRC_MARK="# managed by fedora-niri-dms-setup" if [[ -f "$HOME/.zshrc" ]] && ! grep -qF "$ZSHRC_MARK" "$HOME/.zshrc"; then cp "$HOME/.zshrc" "$HOME/.zshrc.bak" info "Existing ~/.zshrc backed up to ~/.zshrc.bak" fi cat > "$HOME/.zshrc" <<'EOF' # managed by fedora-niri-dms-setup # Re-running the script overwrites this file. Put your own additions in ~/.zshrc.local # ---- history ---- HISTFILE=~/.zsh_history HISTSIZE=50000 SAVEHIST=50000 setopt SHARE_HISTORY HIST_IGNORE_ALL_DUPS HIST_IGNORE_SPACE setopt AUTO_CD INTERACTIVE_COMMENTS # ---- completion ---- autoload -Uz compinit && compinit zstyle ':completion:*' menu select zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}" # ---- keys ---- bindkey -e bindkey '^[[H' beginning-of-line bindkey '^[[F' end-of-line bindkey '^[[3~' delete-char bindkey '^[[1;5C' forward-word bindkey '^[[1;5D' backward-word # Up/Down search history for what you've already typed autoload -Uz up-line-or-beginning-search down-line-or-beginning-search zle -N up-line-or-beginning-search zle -N down-line-or-beginning-search bindkey '^[[A' up-line-or-beginning-search bindkey '^[[B' down-line-or-beginning-search # ---- aliases ---- if command -v eza >/dev/null; then alias ls='eza --icons --group-directories-first' alias ll='eza -lh --icons --git --group-directories-first' alias la='eza -lah --icons --git --group-directories-first' alias tree='eza --tree --icons' fi command -v bat >/dev/null && alias cat='bat --paging=never' # ---- tools ---- command -v fzf >/dev/null && source <(fzf --zsh) # Ctrl+R history, Ctrl+T files command -v zoxide >/dev/null && eval "$(zoxide init zsh)" # z command -v starship >/dev/null && eval "$(starship init zsh)" # ---- plugins (syntax highlighting must load last) ---- [[ -r /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh ]] && \ source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh [[ -r ~/.zshrc.local ]] && source ~/.zshrc.local [[ -r /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]] && \ source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh # ---- greeting ---- command -v fastfetch >/dev/null && fastfetch EOF touch "$HOME/.zshrc.local" if [[ "$(getent passwd "$USER" | cut -d: -f7)" != */zsh ]]; then sudo usermod --shell "$(command -v zsh)" "$USER" info "Default shell changed to zsh" fi info "Setting Ptyxis to use the Nerd Font" gsettings set org.gnome.Ptyxis use-system-font false 2>/dev/null \ && gsettings set org.gnome.Ptyxis font-name 'JetBrainsMono Nerd Font 11' 2>/dev/null \ || warn "Couldn't set the Ptyxis font now; pick 'JetBrainsMono Nerd Font' in Ptyxis preferences" # ---------- 8. Login screen -------------------------------------------------- step "Installing the DMS greeter" if ! dnf_install dms-greeter; then info "Not found in the dms COPR, trying avengemedia/danklinux" sudo dnf copr enable -y avengemedia/danklinux dnf_install dms-greeter fi dms greeter enable dms greeter sync || warn "Greeter theme sync failed; re-run 'dms greeter sync' after first login" dms greeter status || true sudo systemctl set-default graphical.target # ---------- 9. Optional extras ----------------------------------------------- if $WANT_STEAM; then step "Installing Steam and gamescope" dnf_install steam gamescope fi if $WANT_OPENVPN; then step "Installing NetworkManager OpenVPN support" dnf_install NetworkManager-openvpn info "Import a PIA config later with:" info " nmcli connection import type openvpn file ~/Downloads/.ovpn" fi if $WANT_FPRINT; then step "Setting up the fingerprint reader" dnf_install fprintd fprintd-pam sudo authselect enable-feature with-fingerprint || warn "Could not enable fingerprint auth" info "Enrol a finger now (press Ctrl+C to skip and run 'fprintd-enroll' later)" fprintd-enroll || warn "Enrolment skipped or failed; run 'fprintd-enroll' later" fi if $WANT_EASYFX; then step "Installing EasyEffects" flatpak install -y --user flathub com.github.wwmm.easyeffects info "Load Framework's speaker profile from their knowledge base after first login" fi # ---------- 10. Btrfs snapshots (snapper) ----------------------------------- # Snapshot the root filesystem before and after every dnf transaction, so a bad # update can be undone from Btrfs Assistant. Runs last so the finished setup # is the first baseline snapshot. if [[ "$(findmnt -no FSTYPE /)" == "btrfs" ]]; then step "Setting up Btrfs snapshots" dnf_install snapper libdnf5-plugin-actions btrfs-assistant if [[ ! -f /etc/snapper/configs/root ]]; then sudo snapper -c root create-config / fi # Keep the 10 newest dnf snapshot pairs (5 marked important); no hourly ones. sudo snapper -c root set-config \ NUMBER_LIMIT=10 NUMBER_LIMIT_IMPORTANT=5 \ TIMELINE_CREATE=no \ ALLOW_USERS="$USER" SYNC_ACL=yes sudo systemctl enable --now snapper-cleanup.timer # dnf5 hook: pre snapshot before each transaction, post snapshot after it. sudo mkdir -p /etc/dnf/libdnf5-plugins/actions.d sudo tee /etc/dnf/libdnf5-plugins/actions.d/snapper.actions >/dev/null <<'EOF' # Managed by fedora-niri-dms-setup # Take a snapper pre/post snapshot pair around every dnf transaction. pre_transaction::::/usr/bin/sh -c echo\ "tmp.snapper_descr=$(ps\ -o\ command\ --no-headers\ -p\ '${pid}')" pre_transaction::::/usr/bin/sh -c echo\ "tmp.snapper_pre=$(snapper\ -c\ root\ create\ -c\ number\ -t\ pre\ -p\ -d\ '${tmp.snapper_descr}')" post_transaction::::/usr/bin/sh -c [\ -n\ "${tmp.snapper_pre}"\ ]\ &&\ snapper\ -c\ root\ create\ -c\ number\ -t\ post\ --pre-number\ "${tmp.snapper_pre}"\ -d\ "${tmp.snapper_descr}"\ ;\ echo\ tmp.snapper_pre\ ;\ echo\ tmp.snapper_descr EOF if ! sudo snapper -c root list --columns description 2>/dev/null | grep -q 'fedora-niri-dms-setup baseline'; then sudo snapper -c root create -c important -d "fedora-niri-dms-setup baseline" info "Baseline snapshot of the finished setup created" fi info "Every dnf install/update now takes a before/after snapshot." info "Browse or restore them in Btrfs Assistant, or list with: snapper -c root list" else warn "Root filesystem isn't Btrfs; skipping snapshots" fi # ---------- done ------------------------------------------------------------- step "All done" info "Next boot goes to the DMS login screen. Pick the 'niri' session." info "Press Super+Shift+/ after logging in to see all keybinds." $NVIDIA && warn "NVIDIA: wait ~5 minutes for the driver module to finish building before rebooting (check with: modinfo -F version nvidia)." [[ -n "$VIRT" && "$VIRT" != "none" ]] && warn "VM: make sure 3D acceleration is on before rebooting." info "If the screen stays black: Ctrl+Alt+F2, log in, and run 'niri-session' or 'dms greeter status'." echo if ask "Reboot now?"; then sudo reboot fi