#!/bin/bash ################################################################################ # # opensuse-personalizer.sh # Interactive post-install setup script for OpenSUSE installations # ################################################################################ ################################################################################ # # Constants # ################################################################################ ######################################## # OpenSUSE patterns PATTERNS=( # (Base system) patterns-base-apparmor patterns-base-base patterns-base-enhanced_base # (Desktop environment) patterns-gnome-gnome_basic ) ######################################## # Zypper packages # To remove RM=( yast2-* ) # To remove (and prevent reinstallation of) AL=( gnome-clocks gnome-packagekit gnome-software tigervnc xterm yast2 ) # To install IN=( # (Necessary packages) git-core flatpak opi # (Theming) papirus-icon-theme fira-code-fonts gdouros-symbola-fonts # (Shell utilities) fish fzf ispell zenity fortune toilet cowsay wmctrl libnotify-tools # (Programs) emacs inkscape gimp shotcut audacity geary nextcloud-desktop steam blender # (GNOME utilities) gnome-session-wayland gnome-characters gnome-disk-utility gnome-font-viewer gnome-dictionary gnome-logs gnome-screenshot gnome-weather eog dconf-editor baobab totem menulibre Fragments # (Misc.) distribution-logos-openSUSE distribution-logos-openSUSE-icons distribution-logos-openSUSE-Tumbleweed ) ######################################## # Flatpak packages FLATPAK_PACKAGES=( com.discordapp.Discord com.spotify.Client com.github.tchx84.Flatseal org.nickvision.tagger ) ######################################## # GNOME extensions GNOME_EXTENSIONS=( AlphabeticalAppGrid@stuarthayhurst color-picker@tuberry places-menu@gnome-shell-extensions.gcampax.github.com ) ######################################## # URLs PACKMAN_REPO_URL="https://ftp.gwdg.de/pub/linux/misc/packman/suse/openSUSE_Tumbleweed/" FLATHUB_REMOTE_URL="https://flathub.org/repo/flathub.flatpakrepo" PERSONALIZER_REPO_URL="https://gitlab.com/jessieh/opensuse-personalizer.git" ######################################## # Directories and files PERSONALIZER_REPO_DIR=/tmp/opensuse-personalizer ################################################################################ # # Pre-setup prompts # ################################################################################ ######################################## # Pre-setup # Ensure we're not running as root if [ "${EUID}" == 0 ]; then echo "DO NOT RUN THIS SCRIPT AS ROOT!" echo "Please run this script as a regular user." exit 1 fi # Ensure "dialog" is installed echo "Installing dialog utility..." sudo zypper --non-interactive in dialog ######################################## # Prompt for system chassis system_chassis=$(hostnamectl chassis) function string_eq () { [[ "${1}" == "${2}" ]] && echo ON || echo OFF } function prompt_chassis_type () { entry=$(dialog --title "Chassis Type" --stdout --no-cancel --erase-on-exit \ --radiolist "Confirm the chassis type for this system:" 0 0 0 \ "Desktop" "Stationary form factor" $(string_eq "${system_chassis}" "desktop") \ "Laptop" "Clamshell form factor" $(string_eq "${system_chassis}" "laptop") \ "Tablet" "Touchscreen form factor" $(string_eq "${system_chassis}" "tablet") \ "Convertible" "Hybrid form factor" $(string_eq "${system_chassis}" "convertible")) # Ensure entry is lowercase entry=${entry,,} case "${entry}" in "desktop"|"laptop"|"convertible"|"tablet") if dialog --title "Chassis Type" --defaultno --yesno "Use ${entry} as chassis type?" 0 0 then system_chassis="${entry}" return 0 else return 1 fi ;; *) dialog --title "Chassis Type" --msgbox "Please choose a chassis type for this system." 0 0 return 1 ;; esac } # Repeatedly show the prompt until we receive valid input until prompt_chassis_type; do : ; done # Set chassis type echo "Setting chassis type to ${system_chassis}" sudo hostnamectl chassis "${system_chassis}" ######################################## # Prompt for system name system_name=$(hostnamectl hostname) function prompt_system_name () { entry=$(dialog --title "System Name" --stdout --no-cancel --erase-on-exit \ --inputbox "All lowercase letters, no spaces or symbols:" 0 0 \ "${system_name}") # Filter all non-letter characters out of the user's entry entry=${entry//[^[:alpha:]]/} # Ensure entry is lowercase entry=${entry,,} case "${entry}" in "") dialog --title "Chassis Type" --msgbox "Please enter a name for this system." 0 0 return 1 ;; *) if dialog --title "Chassis Type" --defaultno --yesno "Use ${entry} as system name?" 0 0 then system_name="${entry}" return 0 else return 1 fi ;; esac } # Repeatedly show the prompt until we receive valid input until prompt_system_name; do : ; done # Set hostname and pretty hostname echo "Setting hostname to ${system_name}..." sudo hostnamectl hostname "${system_name}" echo "Setting pretty hostname to ${system_name^}" sudo hostnamectl hostname --pretty "${system_name^}" ######################################## # Prompt for optional additions optional_additions="" function prompt_optional_additions () { ENTRY=$(dialog --title "Optional Additions" --stdout --no-cancel --single-quoted --output-separator '|' \ --checklist "Select any additional configuration changes that you would like applied to the system:" 0 0 0 \ "Xournal++" "Install Xournal++ for taking handwritten notes" OFF \ "EasyEffects" "Install EasyEffects for applying effects (e.g. equalizers) to system audio devices" OFF \ "Media Control Key Shortcuts" "Set up media control keyboard shortcuts (for systems without dedicated media control keys)" OFF \ "Power Button Screen Lock Override" "Override the power button's default behavior with a screen lock shortcut (for tablets and some convertibles)" OFF ) case "${ENTRY}" in "") if dialog --keep-window --erase-on-exit --title "Chassis Type" --defaultno --yesno "Apply only baseline system configuration?" 0 0 then return 0 else return 1 fi ;; *) if ADDITIONS_LIST=$(echo "${ENTRY}" | sed "s/|/\\\\n * /g" | sed "s/'//g") dialog --keep-window --erase-on-exit --title "Chassis Type" --defaultno --yesno "Confirm your selection of the following additions:\n${ADDITIONS_LIST}" 0 0 then optional_additions="${ENTRY}" return 0 else return 1 fi ;; esac } # Repeatedly show the prompt until we receive valid input until prompt_optional_additions; do : ; done ################################################################################ # # Feature detection # ################################################################################ detected_features=() # Mobile system detection case "${system_chassis}" in "latop"|"convertible"|"tablet") detected_features+=('Mobile') ;; esac # x86-64-v3 CPU support detection CPU_FLAGS=$(cat /proc/cpuinfo | grep flags | head -n 1 | cut -d: -f2) if echo CPU_FLAGS | awk '/avx/&&/avx2/&&/bmi1/&&/bmi2/&&/f16c/&&/fma/&&/abm/&&/movbe/&&/xsave/ {found=1} END {exit !found}' then detected_features+=('x86-64-v3') fi # Touchpad detection if cat /proc/bus/input/devices | grep -i Touchpad then detected_features+=('Touchpad') fi # Windows multi-boot detection if sudo grep menuentry /boot/grub2/grub.cfg | grep -Po ".*Windows.*" then detected_features+=('Windows') fi ################################################################################ # # Package setup # ################################################################################ ######################################## # Package operations echo "Setting up packages..." # Kill PackageKit so that it doesn't interrupt us sudo systemctl stop packagekit sudo zypper --non-interactive in ${PATTERNS[@]} if [[ "${detected_features[@]}" == *"Mobile"* ]]; then sudo zypper in patterns-desktop-mobile fi if [[ "${detected_features[@]}" == *"x86-64-v3"* ]]; then sudo zypper in patterns-glibc-hwcaps-x86_64_v3 fi sudo zypper --non-interactive remove ${RM[@]} ${AL[@]} sudo zypper --non-interactive addlock ${AL[@]} sudo zypper --non-interactive install ${IN[@]} # Install any codecs not hosted by OpenSUSE sudo zypper --non-interactive addrepo -cfp 90 "${PACKMAN_REPO_URL}" packman sudo zypper --non-interactive install --from packman ffmpeg gstreamer-plugins-{good,bad,ugly,libav} libavcodec-full ######################################## # Flatpak operations echo "Setting up Flatpak remotes..." sudo flatpak remote-add --if-not-exists flathub "${FLATHUB_REMOTE}" echo "Setting up Flatpak packages..." sudo flatpak install --noninteractive ${FLATPAK_PACKAGES[@]} ######################################## # (OPTIONAL) Extra packages if [[ "${optional_additions}" == *"Xournal++"* ]]; then echo "Installing Xournal++..." sudo zypper al texlive sudo zypper in xournalpp fi if [[ "${optional_additions}" == *"EasyEffects"* ]]; then echo "Installing EasyEffects..." sudo zypper in easyeffects fi ################################################################################ # # System configuration # ################################################################################ ######################################## # GRUB configuration # Silence GRUB echo "Configuring GRUB..." echo "# Automatically added by opensuse-personalizer.sh GRUB_TIMEOUT=0 GRUB_HIDDEN_TIMEOUT=0 GRUB_QUIET=true GRUB_HIDDEN_TIMEOUT_QUIET=true" | sudo tee --append /etc/default/grub sudo grub2-mkconfig -o /boot/grub2/grub.cfg ######################################## # PolicyKit configuration # Create a local policy that unlocks basic networking settings for users echo "Configuring PolicyKit..." sudo mkdir -p /etc/polkit/localauthority/50-local.d/ echo "[Allow users to modify system network settings] Identity=unix-group:users Action=org.freedesktop.NetworkManager.settings.modify.system ResultAny=auth_admin_keep ResultInactive=auth_admin_keep ResultActive=yes" | sudo tee /etc/polkit/localauthority/50-local.d/10-NetworkManager.pkla ################################################################################ # # GNOME keyboard shortcuts # ################################################################################ echo "Configuring GNOME keyboard shortcuts..." ######################################## # Navigation gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-left "['Left']" gsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-left "['Right']" gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-left "['Left']" gsettings set org.gnome.desktop.wm.keybindings move-to-workspace-left "['Right']" # Swap "switch applications" (grouped alt+tab) for "switch windows" (non-grouped alt+tab) shortcuts gsettings set org.gnome.shell.window-switcher current-workspace-only false gsettings set org.gnome.desktop.wm.keybindings switch-windows $(gsettings get org.gnome.desktop.wm.keybindings switch-applications) gsettings set org.gnome.desktop.wm.keybindings switch-windows-backward $(gsettings get org.gnome.desktop.wm.keybindings switch-applications-backward) gsettings set org.gnome.desktop.wm.keybindings switch-applications [] gsettings set org.gnome.desktop.wm.keybindings switch-applications-backward [] ######################################## # (OPTIONAL) Media if [[ "${optional_additions}" == *"Media Control Key Shortcuts"* ]]; then gsettings set org.gnome.settings-daemon.plugins.media-keys mic-mute "['Home']" gsettings set org.gnome.settings-daemon.plugins.media-keys next "['Page_Down']" gsettings set org.gnome.settings-daemon.plugins.media-keys previous "['Page_Up']" gsettings set org.gnome.settings-daemon.plugins.media-keys volume-down "['Page_Down']" gsettings set org.gnome.settings-daemon.plugins.media-keys volume-mute "['End']" gsettings set org.gnome.settings-daemon.plugins.media-keys volume-up "['Page_Up']" fi ######################################## # Windows gsettings set org.gnome.desktop.wm.keybindings close "['w']" gsettings set org.gnome.desktop.wm.keybindings toggle-fullscreen "['f']" ######################################## # Custom shortcuts # Helper function (because gsettings doesn't make this simple) function add_custom_gnome_keybinding () { EXISTING_KEYBINDINGS=$(gsettings get org.gnome.settings-daemon.plugins.media-keys custom-keybindings) KEYBINDING_INDEX=$(expr $(echo $EXISTING_KEYBINDINGS | grep -oE '[0-9]+' | sort -n | tail -n 1) + 1) KEYBINDING_PATH=/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom${KEYBINDING_INDEX}/ if [[ "$EXISTING_KEYBINDINGS" == "@as []" ]]; then gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "['${KEYBINDING_PATH}']" else gsettings set org.gnome.settings-daemon.plugins.media-keys custom-keybindings "$(echo ${EXISTING_KEYBINDINGS} | sed s/.$//), '${KEYBINDING_PATH}']" fi gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:${KEYBINDING_PATH} name "${1}" gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:${KEYBINDING_PATH} command "${2}" gsettings set org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:${KEYBINDING_PATH} binding "${3}" } # Add custom shortcuts add_custom_gnome_keybinding "Launch Terminal" "gnome-terminal" "Return" add_custom_gnome_keybinding "Toggle Night Light" "gnome-toggle-night-light" "n" # (OPTIONAL) Add custom shortcuts if [[ "${detected_features[@]}" == *"Touchpad"* ]]; then add_custom_gnome_keybinding "Toggle Touchpad Lock" "gnome-toggle-touchpad-lock" "t" fi if [[ "${optional_additions}" == *"Power Button Screen Lock Override"* ]]; then add_custom_gnome_keybinding "Lock Screen" "gdm-lock-screen" "PowerOff" fi ################################################################################ # # GNOME appearance configuration # ################################################################################ echo "Configuring GNOME appearance settings..." gsettings set org.gnome.desktop.interface icon-theme "Papirus" gsettings set org.gnome.desktop.interface clock-format '24h' gsettings set org.gnome.desktop.interface clock-show-date true gsettings set org.gnome.desktop.interface clock-show-weekday true gsettings set org.gnome.desktop.interface font-antialiasing "rgba" gsettings set org.gnome.desktop.interface font-name "Cantarell 10" gsettings set org.gnome.desktop.interface document-font-name "Cantarell 10" gsettings set org.gnome.desktop.interface monospace-font-name "Fira Code 10" gsettings set org.gnome.settings-daemon.plugins.color night-light-enabled true gsettings set org.gnome.settings-daemon.plugins.color night-light-temperature 3700 gsettings set org.gnome.settings-daemon.plugins.color night-light-schedule-automatic false gsettings set org.gnome.settings-daemon.plugins.color night-light-schedule-from 22 gsettings set org.gnome.settings-daemon.plugins.color night-light-schedule-to 6 ################################################################################ # # GNOME app folder configuration # ################################################################################ echo "Configuring GNOME app folders..." ######################################## # Favorites gsettings set org.gnome.shell.favorite-apps "['org.gnome.Terminal.desktop', 'firefox.desktop', 'emacsclient.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Nautilus.desktop']" ######################################## # Utilities folder UTILITIES_FOLDER_PATH=/org/gnome/desktop/app-folders/folders/Utilities/ gsettings set org.gnome.desktop.app-folders folder-children "['Utilities']" gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} name "X-GNOME-Utilities.directory" gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} translate true gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} categories "[]" gsettings set org.gnome.desktop.app-folders.folder:${UTILITIES_FOLDER_PATH} apps "['org.gnome.FileRoller.desktop', 'org.gnome.Characters.desktop', 'ca.desrt.dconf-editor.desktop', 'org.gnome.Dictionary.desktop', 'org.gnome.baobab.desktop', 'org.gnome.DiskUtility.desktop', 'org.gnome.Evince.desktop', 'com.github.wwmm.easyeffects.desktop', 'org.gnome.Extensions.desktop', 'com.github.tchx84.Flatseal.desktop', 'org.gnome.font-viewer.desktop', 'de.haeckerfelix.Fragments.desktop', 'org.gnome.eog.desktop', 'org.gnome.Logs.desktop', 'menulibre.desktop', 'org.gnome.Screenshot.desktop', 'gnome-system-monitor.desktop', 'org.nickvision.tagger.desktop', 'org.gnome.tweaks.desktop', 'uefi-firmware-settings.desktop', 'org.gnome.Totem.desktop']" ################################################################################ # # GNOME extension setup # ################################################################################ echo "Setting up GNOME extensions..." for EXTENSION in "${GNOME_EXTENSIONS[@]}"; do busctl --user call org.gnome.Shell.Extensions /org/gnome/Shell/Extensions org.gnome.Shell.Extensions InstallRemoteExtension s ${EXTENSION} done ################################################################################ # # Personal configuration files and scripts # ################################################################################ ######################################## # Emacs echo "Setting up Emacs..." # Remove any vendor-provided Emacs configuration files rm ${HOME}/.emacs ${HOME}/.emacs.d/init.el # Download Emacs configuration EMACS_CONFIG_DIR=${HOME}/.config/emacs mkdir -p ${EMACS_CONFIG_DIR} wget https://jessieh.net/emacs -O ${EMACS_CONFIG_DIR}/init.el # Enable and start Emacs daemon systemctl --user enable emacs.service systemctl --user start emacs.service ######################################## # Fish echo "Setting up Fish..." # Download Fish configuration FISH_CONFIG_DIR=${HOME}/.config/fish mkdir -p ${FISH_CONFIG_DIR} wget https://jessieh.net/fish -O ${EMACS_CONFIG_DIR}/config.fish # Set Fish as default shell sudo chsh ${USER} -s $(which fish) ######################################## # Utility scripts echo "Installing scripts..." # Clone personalizer repo containing scripts, desktop entries, etc. to temporary folder git clone "${PERSONALIZER_REPO_URL}" "${PERSONALIZER_REPO_DIR}" if [[ "${detected_features[@]}" != *'Touchpad'* ]]; then rm ${PERSONALIZER_REPO_DIR}/scripts/gnome-toggle-touchpad-lock fi if [[ "${detected_features[@]}" != *'Windows'* ]]; then rm ${PERSONALIZER_REPO_DIR}/scripts/reboot-windows fi chmod +x ${PERSONALIZER_REPO_DIR}/scripts/* sudo cp ${PERSONALIZER_REPO_DIR}/scripts/* /usr/bin ######################################## # Desktop entries echo "Installing desktop entries..." if [[ "${detected_features[@]}" != *'Windows'* ]]; then rm ${PERSONALIZER_REPO_DIR}/desktop_entries/windows.desktop fi mkdir -p ${HOME}/.local/share/applications/ cp ${PERSONALIZER_REPO_DIR}/desktop-entries/* ${HOME}/.local/share/applications/ ################################################################################ # # Post-setup suggestions # ################################################################################ echo " Personalization complete! Recommended next steps: • Log in to Firefox Sync • Configure Nextcloud client $(if [[ "${optional_additions}" == *"EasyEffects"* ]]; then echo " • Install an audio device profile from\n https://autoeq.app\n"; fi) • Install a Wireguard VPN profile from https://mullvad.net/en/account/#/wireguard-config/?platform=linux • Configure display settings and run 'gdm-update-display-settings' • Run 'themer' to install/update unofficial libadwaita themes A copy of these recommendations has been saved to ${HOME}/next-steps" | tee ${HOME}/next-steps