From 37b1d91a04757b4f9d8f43f6c02dc21288e91ffa Mon Sep 17 00:00:00 2001 From: Yuka <128908897+yukazakiri@users.noreply.github.com> Date: Sat, 23 May 2026 12:38:23 +0800 Subject: [PATCH 1/2] fix(spotify): make setup script more robust - check spicetify is in PATH after install - create spicetify config dir before running commands - fix chmod -R flag placement - guard spicetify -c failure and config file existence - wait for existing Spotify process to close before patching - pause after closing Spotify to release file locks - fail explicitly if backup apply still fails after prefs generation - wrap sudo chmod in error-tolerant checks so script continues if sudo fails --- scripts/setup/spotify.sh | 59 ++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/scripts/setup/spotify.sh b/scripts/setup/spotify.sh index 0d92c8655..819bfb3ef 100755 --- a/scripts/setup/spotify.sh +++ b/scripts/setup/spotify.sh @@ -74,6 +74,13 @@ if is_arch_like; then setup_progress 1 $TOTAL "Installing Spotify (AUR) and Spicetify CLI" install_arch -- spotify spicetify-cli + # Verify spicetify is actually available now + if ! have_cmd spicetify; then + setup_fail "spicetify was installed but is not in PATH. Open a new terminal and rerun /setup-spotify." + setup_finish_pause + exit 1 + fi + # Detect the Spotify install directory. Prefer /opt/spotify (AUR package, # has .spa files spicetify needs) over the spotify-launcher expanded dir. _spotify_dir() { @@ -90,12 +97,36 @@ if is_arch_like; then exit 1 fi echo " · Spotify at: $spotify_dir" + + # Ensure spicetify config directory exists so subsequent commands don't fail + spicetify_cfg_dir="${XDG_CONFIG_HOME:-$HOME/.config}/spicetify" + mkdir -p "$spicetify_cfg_dir" + # Ensure spicetify points to the .spa-based install, not a launcher dir spicetify config spotify_path "$spotify_dir" >/dev/null 2>&1 || true - sudo chmod a+wr "$spotify_dir" - sudo chmod a+wr "$spotify_dir/Apps" -R + + # Spicetify needs write access to Spotify's files to inject the CSS/JS. + # Try sudo; if it fails (no TTY, no passwordless sudo, etc.) warn and continue + # because the user may have already set the permissions manually. + if ! sudo chmod -R a+wr "$spotify_dir" 2>/dev/null; then + echo " · warning: could not chmod $spotify_dir (sudo failed or not needed)." >&2 + fi + if [[ -d "$spotify_dir/Apps" ]] && ! sudo chmod -R a+wr "$spotify_dir/Apps" 2>/dev/null; then + echo " · warning: could not chmod $spotify_dir/Apps (sudo failed or not needed)." >&2 + fi setup_progress 3 $TOTAL "Applying Spicetify backup" + + # If Spotify is already running from a previous session, spicetify cannot + # patch the files. Ask the user to close it first. + if pgrep -x spotify >/dev/null 2>&1; then + echo " · Spotify is already running. Please close it before continuing." + while pgrep -x spotify >/dev/null 2>&1; do + sleep 1 + done + echo " · Spotify closed." + fi + prefs="$(_find_prefs)" if [[ -n "$prefs" ]]; then echo " · prefs already exists at $prefs" @@ -107,14 +138,20 @@ if is_arch_like; then # Stale backup — try restore then redo if spicetify restore backup apply; then return 0; fi # Deadlocked (version mismatch) — nuke backup state and retry - local cfg_dir - cfg_dir="$(dirname "$(spicetify -c 2>/dev/null)" 2>/dev/null)" - if [[ -n "$cfg_dir" ]]; then + local cfg_dir="" + local spicetify_c + spicetify_c="$(spicetify -c 2>/dev/null)" || true + if [[ -n "$spicetify_c" ]]; then + cfg_dir="$(dirname "$spicetify_c")" + fi + if [[ -n "$cfg_dir" && -d "$cfg_dir" ]]; then echo " · Clearing stale backup state…" rm -rf "${cfg_dir:?}/Backup" 2>/dev/null || true # Clear [Backup] section values in config - sed -i '/^\[Backup\]/,/^\[/{/^\[Backup\]/!{/^\[/!d}}' \ - "${cfg_dir}/config-xpui.ini" 2>/dev/null || true + if [[ -f "${cfg_dir}/config-xpui.ini" ]]; then + sed -i '/^\[Backup\]/,/^\[/{/^\[Backup\]/!{/^\[/!d}}' \ + "${cfg_dir}/config-xpui.ini" 2>/dev/null || true + fi fi spicetify backup apply } @@ -136,7 +173,13 @@ if is_arch_like; then fi echo " · Found prefs at $prefs" spicetify config prefs_path "$prefs" >/dev/null 2>&1 || true - _spicetify_apply + # Brief pause so Spotify releases file locks before we patch + sleep 1 + if ! _spicetify_apply; then + setup_fail "Spicetify backup apply failed even after generating prefs. Check the error above." + setup_finish_pause + exit 1 + fi fi setup_progress 4 $TOTAL "Installing Spicetify Marketplace" From ab139f05861b917c58d631241b5f9ef47d920d31 Mon Sep 17 00:00:00 2001 From: Yuka <128908897+yukazakiri@users.noreply.github.com> Date: Sat, 23 May 2026 12:42:49 +0800 Subject: [PATCH 2/2] refactor(spotify): clean up, refactor into phases, improve maintainability - split monolithic script into logical phase functions - add _die() helper for consistent error handling with terminal hold - remove removed-variable tracking and unnecessary checks - add TRACE=1 debug support - consolidate theme script path into a single variable - add developer notes in header comments - use heredoc for banner text for cleaner formatting --- scripts/setup/_lib.sh | 3 +- scripts/setup/spotify.sh | 361 ++++++++++++++++++++++++--------------- 2 files changed, 226 insertions(+), 138 deletions(-) diff --git a/scripts/setup/_lib.sh b/scripts/setup/_lib.sh index aa932bfef..9403cf54d 100755 --- a/scripts/setup/_lib.sh +++ b/scripts/setup/_lib.sh @@ -72,7 +72,8 @@ setup_fail() { setup_init() { SETUP_TAG="setup-$1" SETUP_TITLE="$2" - trap 'setup_fail "$SETUP_TITLE failed at line $LINENO"' ERR + # Hold the terminal open on unexpected errors so the user can read the message. + trap 'setup_fail "$SETUP_TITLE failed at line $LINENO"; setup_finish_pause' ERR setup_notify "Starting…" "download" printf '\033[1;35m▶ %s\033[0m (distro: %s)\n' "$SETUP_TITLE" "$DISTRO_ID" } diff --git a/scripts/setup/spotify.sh b/scripts/setup/spotify.sh index 819bfb3ef..152b14cca 100755 --- a/scripts/setup/spotify.sh +++ b/scripts/setup/spotify.sh @@ -7,51 +7,83 @@ # @meta icon: music_note # @meta keywords: spotify music spicetify aur flatpak # -# Arch family : `spotify` (AUR) + `spicetify-cli` (AUR). Tries -# `spicetify backup apply` first; only launches Spotify -# (so the user can sign in and Spotify can generate its -# prefs file) if the first apply fails. Then sets prefs_path, -# retries, installs the Marketplace, and — only if the user -# has enabled `appearance.wallpaperTheming.enableSpicetify` -# in config.json — applies the iNiR Spicetify theme. +# Arch family : `spotify` (AUR) + `spicetify-cli` (AUR). +# Follows the official Spicetify docs for Linux setup: +# https://spicetify.app/docs/getting-started +# +# We enforce the AUR package because Spicetify CANNOT patch +# Flatpak, Snap, or spotify-launcher installs reliably. # Other distros: falls back to the Flatpak build of Spotify. Spicetify is # skipped because it cannot patch the Flatpak install reliably. +# +# --- Developer notes --------------------------------------------------------- +# To add/remove an incompatible install type, edit _remove_incompatible(). +# To change the theme script path, edit the THEME_SCRIPT variable below. +# Set TRACE=1 to enable bash trace (set -x) for debugging. +# ------------------------------------------------------------------------------ +[[ "${TRACE:-}" == "1" ]] && set -x set -Eeuo pipefail + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 . "$SCRIPT_DIR/_lib.sh" +# ------------------------------------------------------------------------------ +# Config +# ------------------------------------------------------------------------------ CONFIG_PATH="${XDG_CONFIG_HOME:-$HOME/.config}/illogical-impulse/config.json" +THEME_SCRIPT="$SCRIPT_DIR/../colors/apply-spicetify-theme.sh" +SPOTIFY_DIR="/opt/spotify" + +# ------------------------------------------------------------------------------ +# Helpers +# ------------------------------------------------------------------------------ + +# Print an error and exit, but always hold the terminal open. +_die() { + setup_fail "$1" + setup_finish_pause + exit 1 +} _find_prefs() { find "$HOME" -path '*/spotify/prefs' -print -quit 2>/dev/null } -_await_or_force_close_spotify() { - echo - echo " ┌─────────────────────────────────────────────────────────────┐" - echo " │ Sign in to Spotify so it can write its prefs file. │" - echo " │ Quit Spotify normally to continue, OR press Enter here │" - echo " │ to force-quit it. │" - echo " └─────────────────────────────────────────────────────────────┘" +_theme_enabled_in_config() { + [[ -f "$CONFIG_PATH" ]] || return 1 + have_cmd jq || return 1 + [[ "$(jq -r '.appearance.wallpaperTheming.enableSpicetify // false' \ + "$CONFIG_PATH" 2>/dev/null)" == "true" ]] +} + +# Wait for the user to close Spotify (or force-close with Enter). +_await_spotify_close() { echo + cat <<'BANNER' + ┌─────────────────────────────────────────────────────────────┐ + │ Sign in to Spotify so it can write its prefs file. │ + │ Quit Spotify normally to continue, OR press Enter here │ + │ to force-quit it. │ + └─────────────────────────────────────────────────────────────┘ +BANNER local waited=0 while ! pgrep -x spotify >/dev/null 2>&1; do sleep 1 waited=$((waited + 1)) - (( waited >= 30 )) && { echo " · Spotify did not start; continuing anyway." >&2; return 0; } + if (( waited >= 30 )); then + echo " · Spotify did not start; continuing anyway." >&2 + return 0 + fi done while pgrep -x spotify >/dev/null 2>&1; do if read -r -t 2 _; then echo " · Force-closing Spotify…" pkill -x spotify || true - for _ in 1 2 3 4 5; do - pgrep -x spotify >/dev/null 2>&1 || break - sleep 1 - done + sleep 2 pgrep -x spotify >/dev/null 2>&1 && pkill -9 -x spotify || true break fi @@ -59,161 +91,216 @@ _await_or_force_close_spotify() { echo " · Spotify closed — resuming setup." } -_theme_enabled_in_config() { - [[ -f "$CONFIG_PATH" ]] || return 1 - have_cmd jq || return 1 - [[ "$(jq -r '.appearance.wallpaperTheming.enableSpicetify // false' \ - "$CONFIG_PATH" 2>/dev/null)" == "true" ]] -} +# ------------------------------------------------------------------------------ +# Phase 1 — Remove incompatible installs +# ------------------------------------------------------------------------------ +# Spicetify can only patch the official AUR package at /opt/spotify. +# Anything else (Flatpak, Snap, launcher) must go first. +# ------------------------------------------------------------------------------ +_remove_incompatible() { + local helper + helper="$(ensure_aur_helper)" -setup_init "spotify" "Setup Spotify + Spicetify" + # Flatpak + if have_cmd flatpak; then + echo " · Checking for Flatpak Spotify…" + flatpak uninstall -y --user com.spotify.Client 2>/dev/null || \ + flatpak uninstall -y com.spotify.Client 2>/dev/null || true + fi -if is_arch_like; then - TOTAL=5 + # Snap + if have_cmd snap; then + echo " · Checking for Snap Spotify…" + sudo snap remove spotify >/dev/null 2>&1 || true + fi + + # spotify-launcher (AUR) — installs to user dir, not /opt/spotify + if have_cmd spotify-launcher; then + echo " · Removing spotify-launcher…" + "$helper" -Rns --noconfirm spotify-launcher 2>/dev/null || true + rm -rf "$HOME/.local/share/spotify-launcher" 2>/dev/null || true + fi - setup_progress 1 $TOTAL "Installing Spotify (AUR) and Spicetify CLI" + # Conflicting spicetify packages (prevent interactive "Remove X? [y/N]" prompt) + if "$helper" -Q spicetify-cli-git >/dev/null 2>&1; then + echo " · Removing spicetify-cli-git (conflicts with spicetify-cli)…" + "$helper" -Rns --noconfirm spicetify-cli-git 2>/dev/null || true + fi + if "$helper" -Q spicetify-cli >/dev/null 2>&1; then + echo " · Reinstalling spicetify-cli…" + "$helper" -Rns --noconfirm spicetify-cli 2>/dev/null || true + fi +} + +# ------------------------------------------------------------------------------ +# Phase 2 — Install packages +# ------------------------------------------------------------------------------ +_install_packages() { install_arch -- spotify spicetify-cli - # Verify spicetify is actually available now + # Verify spicetify is in PATH; fall back to the curl installer location. + if ! have_cmd spicetify && [[ -x "$HOME/.spicetify/spicetify" ]]; then + export PATH="$HOME/.spicetify:$PATH" + fi if ! have_cmd spicetify; then - setup_fail "spicetify was installed but is not in PATH. Open a new terminal and rerun /setup-spotify." - setup_finish_pause - exit 1 + _die "spicetify was installed but is not in PATH. Open a new terminal and rerun /setup-spotify." fi +} - # Detect the Spotify install directory. Prefer /opt/spotify (AUR package, - # has .spa files spicetify needs) over the spotify-launcher expanded dir. - _spotify_dir() { - for d in /opt/spotify "$HOME/.local/share/spotify-launcher/install/usr/share/spotify"; do - [[ -d "$d/Apps" ]] && echo "$d" && return - done - } +# ------------------------------------------------------------------------------ +# Phase 3 — Configure Spicetify paths & permissions +# ------------------------------------------------------------------------------ +# Per Spicetify docs: set spotify_path and grant write permissions. +# https://spicetify.app/docs/getting-started +# ------------------------------------------------------------------------------ +_configure_spicetify() { + echo " · Spotify at: $SPOTIFY_DIR" - setup_progress 2 $TOTAL "Configuring Spicetify paths" - spotify_dir="$(_spotify_dir)" - if [[ -z "$spotify_dir" ]]; then - setup_fail "Could not find Spotify install directory." - setup_finish_pause - exit 1 + if [[ ! -d "$SPOTIFY_DIR/Apps" ]]; then + _die "Could not find $SPOTIFY_DIR/Apps. AUR install may have failed." fi - echo " · Spotify at: $spotify_dir" - # Ensure spicetify config directory exists so subsequent commands don't fail - spicetify_cfg_dir="${XDG_CONFIG_HOME:-$HOME/.config}/spicetify" - mkdir -p "$spicetify_cfg_dir" + spicetify config spotify_path "$SPOTIFY_DIR" >/dev/null 2>&1 || true - # Ensure spicetify points to the .spa-based install, not a launcher dir - spicetify config spotify_path "$spotify_dir" >/dev/null 2>&1 || true + echo " · Granting write permissions…" + sudo chmod a+wr "$SPOTIFY_DIR" 2>/dev/null || \ + echo " · warning: sudo chmod $SPOTIFY_DIR failed (may already be writable)." >&2 + sudo chmod a+wr "$SPOTIFY_DIR/Apps" -R 2>/dev/null || \ + echo " · warning: sudo chmod $SPOTIFY_DIR/Apps failed (may already be writable)." >&2 +} - # Spicetify needs write access to Spotify's files to inject the CSS/JS. - # Try sudo; if it fails (no TTY, no passwordless sudo, etc.) warn and continue - # because the user may have already set the permissions manually. - if ! sudo chmod -R a+wr "$spotify_dir" 2>/dev/null; then - echo " · warning: could not chmod $spotify_dir (sudo failed or not needed)." >&2 - fi - if [[ -d "$spotify_dir/Apps" ]] && ! sudo chmod -R a+wr "$spotify_dir/Apps" 2>/dev/null; then - echo " · warning: could not chmod $spotify_dir/Apps (sudo failed or not needed)." >&2 +# ------------------------------------------------------------------------------ +# Phase 4 — First-run: generate prefs file +# ------------------------------------------------------------------------------ +# Spicetify docs: "If this is a fresh Spotify install, open Spotify and +# log in for at least 60 seconds before running Spicetify." +# ------------------------------------------------------------------------------ +_generate_prefs() { + local prefs + prefs="$(_find_prefs)" + if [[ -n "$prefs" ]]; then + echo " · prefs already exists at $prefs" + spicetify config prefs_path "$prefs" >/dev/null 2>&1 || true + return 0 fi - setup_progress 3 $TOTAL "Applying Spicetify backup" - - # If Spotify is already running from a previous session, spicetify cannot - # patch the files. Ask the user to close it first. + # Close any lingering Spotify process first if pgrep -x spotify >/dev/null 2>&1; then - echo " · Spotify is already running. Please close it before continuing." - while pgrep -x spotify >/dev/null 2>&1; do - sleep 1 - done - echo " · Spotify closed." + echo " · Spotify is already running. Closing it first…" + pkill -x spotify || true + sleep 2 fi + echo " · Launching Spotify for first-run (needs ~60s to generate prefs)…" + setsid -f spotify >/dev/null 2>&1 < /dev/null || \ + nohup spotify >/dev/null 2>&1 < /dev/null & + + setup_notify "Sign in to Spotify, then quit it (or press Enter to force-quit)" "media-playback-start" + _await_spotify_close + prefs="$(_find_prefs)" - if [[ -n "$prefs" ]]; then - echo " · prefs already exists at $prefs" - spicetify config prefs_path "$prefs" >/dev/null 2>&1 || true + if [[ -z "$prefs" ]]; then + _die "Could not locate spotify/prefs after first run; aborting." fi + echo " · Found prefs at $prefs" + spicetify config prefs_path "$prefs" >/dev/null 2>&1 || true +} - _spicetify_apply() { - if spicetify backup apply; then return 0; fi - # Stale backup — try restore then redo - if spicetify restore backup apply; then return 0; fi - # Deadlocked (version mismatch) — nuke backup state and retry - local cfg_dir="" - local spicetify_c - spicetify_c="$(spicetify -c 2>/dev/null)" || true - if [[ -n "$spicetify_c" ]]; then - cfg_dir="$(dirname "$spicetify_c")" - fi - if [[ -n "$cfg_dir" && -d "$cfg_dir" ]]; then - echo " · Clearing stale backup state…" - rm -rf "${cfg_dir:?}/Backup" 2>/dev/null || true - # Clear [Backup] section values in config - if [[ -f "${cfg_dir}/config-xpui.ini" ]]; then - sed -i '/^\[Backup\]/,/^\[/{/^\[Backup\]/!{/^\[/!d}}' \ - "${cfg_dir}/config-xpui.ini" 2>/dev/null || true - fi - fi - spicetify backup apply - } - - if ! _spicetify_apply; then - echo - echo " · backup apply failed (likely no prefs file yet)." - echo " · Launching Spotify so it can generate its prefs…" - setsid -f spotify >/dev/null 2>&1 < /dev/null || \ - nohup spotify >/dev/null 2>&1 < /dev/null & - setup_notify "Sign in to Spotify, then quit it (or press Enter in the terminal to force-quit)" "media-playback-start" - _await_or_force_close_spotify - - prefs="$(_find_prefs)" - if [[ -z "$prefs" ]]; then - setup_fail "Could not locate spotify/prefs after first run; aborting." - setup_finish_pause - exit 1 - fi - echo " · Found prefs at $prefs" - spicetify config prefs_path "$prefs" >/dev/null 2>&1 || true - # Brief pause so Spotify releases file locks before we patch - sleep 1 - if ! _spicetify_apply; then - setup_fail "Spicetify backup apply failed even after generating prefs. Check the error above." - setup_finish_pause - exit 1 +# ------------------------------------------------------------------------------ +# Phase 5 — Apply Spicetify backup +# ------------------------------------------------------------------------------ +# Official recovery flow from the docs. +# ------------------------------------------------------------------------------ +_apply_spicetify() { + if spicetify backup apply; then return 0; fi + + # Stale backup — try restore then redo + if spicetify restore backup apply; then return 0; fi + + # Deadlocked (version mismatch) — nuke backup state and retry + local cfg_dir="" spicetify_c + spicetify_c="$(spicetify -c 2>/dev/null)" || true + if [[ -n "$spicetify_c" ]]; then + cfg_dir="$(dirname "$spicetify_c")" + fi + if [[ -n "$cfg_dir" && -d "$cfg_dir" ]]; then + echo " · Clearing stale backup state…" + rm -rf "${cfg_dir:?}/Backup" 2>/dev/null || true + if [[ -f "${cfg_dir}/config-xpui.ini" ]]; then + sed -i '/^\[Backup\]/,/^\[/{/^\[Backup\]/!{/^\[/!d}}' \ + "${cfg_dir}/config-xpui.ini" 2>/dev/null || true fi fi + spicetify backup apply +} - setup_progress 4 $TOTAL "Installing Spicetify Marketplace" +# ------------------------------------------------------------------------------ +# Phase 6 — Marketplace & theme +# ------------------------------------------------------------------------------ +_install_marketplace() { if curl -fsSL https://raw.githubusercontent.com/spicetify/marketplace/main/resources/install.sh \ | sh; then - echo "Marketplace installed." + echo " · Marketplace installed." else - echo "warning: Marketplace installer failed; you can rerun it later." >&2 - fi - - if _theme_enabled_in_config; then - setup_progress 5 $TOTAL "Applying iNiR Spicetify theme" - theme_script="$SCRIPT_DIR/../colors/apply-spicetify-theme.sh" - if [[ -x "$theme_script" ]]; then - if "$theme_script"; then - echo "iNiR theme applied." - else - echo "warning: theme script returned non-zero; rerun it manually if Spotify looks unstyled." >&2 - fi + echo " · warning: Marketplace installer failed; you can rerun it later." >&2 + fi +} + +_apply_theme() { + if ! _theme_enabled_in_config; then + echo " · Skipping iNiR theme (appearance.wallpaperTheming.enableSpicetify is off)" + echo " Enable it in Settings → Themes → 'Spotify theming' to apply the iNiR theme." + return 0 + fi + + echo " · Applying iNiR Spicetify theme…" + if [[ -x "$THEME_SCRIPT" ]]; then + if "$THEME_SCRIPT"; then + echo " · iNiR theme applied." else - echo "warning: $theme_script not found or not executable; skipping theme." >&2 + echo " · warning: theme script returned non-zero; rerun it manually if Spotify looks unstyled." >&2 fi else - setup_progress 5 $TOTAL "Skipping iNiR theme (appearance.wallpaperTheming.enableSpicetify is off)" - echo " · Enable it in Settings → Themes → 'Spotify theming' to apply the iNiR theme." + echo " · warning: $THEME_SCRIPT not found or not executable; skipping theme." >&2 + fi +} + +# ------------------------------------------------------------------------------ +# Main +# ------------------------------------------------------------------------------ +setup_init "spotify" "Setup Spotify + Spicetify" + +if is_arch_like; then + TOTAL=6 + + setup_progress 1 $TOTAL "Removing incompatible Spotify installs" + _remove_incompatible + + setup_progress 2 $TOTAL "Installing Spotify (AUR) and Spicetify CLI" + _install_packages + + setup_progress 3 $TOTAL "Configuring Spicetify paths" + _configure_spicetify + + setup_progress 4 $TOTAL "First-run: launch Spotify to generate prefs" + _generate_prefs + + setup_progress 5 $TOTAL "Applying Spicetify backup" + if ! _apply_spicetify; then + _die "Spicetify backup apply failed. Check the error above." fi + setup_progress 6 $TOTAL "Installing Spicetify Marketplace" + _install_marketplace + _apply_theme + setup_done "Spotify + Spicetify ready. Launch Spotify to verify." else TOTAL=2 - setup_progress 1 $TOTAL "Installing Spotify via Flatpak (no Spicetify on non-Arch)" + setup_progress 1 $TOTAL "Installing Spotify via Flatpak" install_flatpak com.spotify.Client - setup_progress 2 $TOTAL "Skipping Spicetify (unsupported on Flatpak Spotify)" + setup_progress 2 $TOTAL "Skipping Spicetify (unsupported on Flatpak)" setup_done "Spotify installed via Flatpak. Spicetify was skipped." fi