Per-User Alternative Selection with update-alternatives: A Debian/Linux Guide


2 views

Debian's update-alternatives system traditionally operates at the system level, affecting all users. This becomes problematic when:

  • Developers need different versions of tools (e.g., Python 2.7 vs 3.x)
  • System administrators want to restrict changes to root
  • Team environments require personalized toolchains

The alternatives mechanism maintains symbolic links in /etc/alternatives that point to actual executables. A typical system-wide change looks like:

sudo update-alternatives --config python

Method 1: PATH Manipulation

Create a ~/.local/alternatives directory and prepend it to your PATH:

mkdir -p ~/.local/alternatives
echo 'export PATH="$HOME/.local/alternatives:$PATH"' >> ~/.bashrc

Then create manual symlinks:

ln -s /usr/bin/python3.8 ~/.local/alternatives/python

Method 2: Environment Modules

Install and configure environment-modules:

sudo apt install environment-modules
mkdir -p ~/privatemodules
echo "prepend-path PATH /path/to/your/preferred/version" > ~/privatemodules/python

Method 3: Virtual Environments

For language-specific alternatives, virtual environments work well:

python -m venv ~/venvs/myproject
source ~/venvs/myproject/bin/activate

Create user-specific wrapper scripts in ~/bin:

#!/bin/bash
# ~/bin/python
exec /usr/bin/python3.9 "$@"

Make executable:

chmod +x ~/bin/python

When implementing user-specific alternatives:

  • Avoid giving users write access to system directories
  • Consider SELinux/AppArmor policies if applicable
  • Document custom paths for team environments

Check which version is actually being executed:

which python
type -a python
readlink -f $(which python)

The standard update-alternatives tool in Debian-based systems operates at a system-wide level. When you run commands like:

sudo update-alternatives --config editor

This change affects all users on the system. In multi-user environments or development workstations, this can cause conflicts when different users prefer different tools.

While Debian doesn't provide a built-in per-user alternative system, we can implement a workaround using environment variables and wrapper scripts:

  1. Create a directory for user alternatives:
  2. mkdir -p ~/.alternatives/bin
  3. Add it to your PATH in ~/.bashrc:
  4. export PATH="$HOME/.alternatives/bin:$PATH"
  5. Create wrapper scripts for each alternative:
  6. 
    #!/bin/bash
    # ~/.alternatives/bin/editor
    if [ -f ~/.alternatives/editor ]; then
        exec $(cat ~/.alternatives/editor) "$@"
    else
        exec /usr/bin/nano "$@"
    fi
    

Create a user-level alternative manager script:


#!/bin/bash
# ~/bin/user-alternatives
ALTERNATIVE_NAME=$1
ALTERNATIVE_PATH=$2

mkdir -p ~/.alternatives
echo "$ALTERNATIVE_PATH" > ~/.alternatives/$ALTERNATIVE_NAME
chmod +x ~/.alternatives/bin/$ALTERNATIVE_NAME

Usage example:


user-alternatives editor /usr/bin/vim
user-alternatives x-terminal-emulator /usr/bin/kitty

For better compatibility, we can make our user alternatives fall back to system alternatives:


#!/bin/bash
# ~/.alternatives/bin/x-terminal-emulator
if [ -f ~/.alternatives/x-terminal-emulator ]; then
    exec $(cat ~/.alternatives/x-terminal-emulator) "$@"
else
    exec $(update-alternatives --query x-terminal-emulator | grep '^Best:' | cut -d' ' -f2-) "$@"
fi

Create a configuration file for easier management (~/.alternatives.conf):


# User alternatives configuration
editor=/usr/bin/vim
x-terminal-emulator=/usr/bin/kitty
browser=/usr/bin/firefox

Then modify the user-alternatives script to read from this config:


#!/bin/bash
while read -r line; do
    [[ "$line" =~ ^# ]] && continue
    name=$(echo "$line" | cut -d'=' -f1)
    path=$(echo "$line" | cut -d'=' -f2)
    echo "$path" > ~/.alternatives/$name
done < ~/.alternatives.conf