Skip to content

Instantly share code, notes, and snippets.

@GoToLoop
Last active October 11, 2025 03:06
Show Gist options
  • Save GoToLoop/246a31d437aaa8c6eadb7f7186544e0f to your computer and use it in GitHub Desktop.
Save GoToLoop/246a31d437aaa8c6eadb7f7186544e0f to your computer and use it in GitHub Desktop.
Bash script to install Thonny + py5mode on Linux
#!/usr/bin/env bash
#===============================================================================
# NAME: Thonny + py5mode Linux Installer
# REQUIREMENT: Python 3.10+, pip, internet connection
# USAGE: ./thonny-installer.bash
# NOTES: Add exec flag 1st: chmod +x ./thonny-installer.bash
# DESCRIPTION: Installs both Thonny IDE + py5mode plugin + py5 full package
# GIST_REPO: https://Gist.GitHub.com/GoToLoop/246a31d437aaa8c6eadb7f7186544e0f
# SUPPORT: https://GitHub.com/py5coding/thonny-py5mode/issues
# AUTHOR: GoToLoop ( https://Discourse.Processing.org/c/28 )
# VERSION: 1.2.6
# CREATED: 2025-Sep-02
# UPDATED: 2025-Sep-10
# LICENSE: MIT
#===============================================================================
set -euo pipefail
echo
VERSION=1.2.6 # Version to echo on console for flag --version
VENV_DIR=""; PYTHON="" # Target venv & python respectively
PYTHON_DEFAULT=python # Default Python binary name
ARGS=("$@"); LEN=${#ARGS[@]} # Array of all user's passed args and its length
# Function to parse individual command-line arguments:
parse_argument() {
local key=$1; local val=$2
case $key in
# Summary of available flags:
--help|-h|'/h'|'/?')
echo -e "Usage: $0 [OPTIONS] [VENV_DIR] [PYTHON]\n"
echo "Options:"
echo " --venv, -v, /v Path to the virtual environment directory"
echo " --python, -p, /p Path or name of an existing Python binary"
echo " --version, -V, /V Show current script version and exit"
echo " --help, -h, /h, /? Show this help message and exit"
exit 0;;
--version|-V|'/V') echo $VERSION; exit 0;; # Display version and exit
--venv|-v|'/v') VENV_DIR=$val;; # Venv target installation folder
--python|-p|'/p') PYTHON=$val;; # Python binary path or name
# Positional fallback:
*)
if [ -z "$VENV_DIR" ]; then
VENV_DIR=$key
elif [ -z "$PYTHON" ]; then
PYTHON=$key
fi
esac
}
# Parse user arguments (named or positional):
for (( i=0; i<LEN; ++i )); do
key=${ARGS[i]}; val=""
if [[ $key == *=* ]]; then # Check if current arg contains `=`
val=${key#*=}; key=${key%%=*} # Handle --flag=value or -f=value
elif (( i + 1 < LEN )); then # Check if it hasn't already reached last arg
val=${ARGS[++i]} # Next arg is the value if key doesn't contain `=`
fi
parse_argument "$key" "$val"
done
# Check if pypi.org pip repo is remotely reachable:
if ! ping -4c 1 pypi.org &> /dev/null; then
echo -e "⚠️ Warning: pypi.org is unreachable! Package install may fail.\n"
fi
# Search the system for existing Python binaries if none provided by argument:
if [ -z "$PYTHON" ]; then
# Get system-wide Python commands from $PATH:
mapfile -t SYSTEM_PYTHONS < <(
compgen -c | grep -E '(^|[-_])python([0-9\.]*)?$' | sort -V | uniq
)
# Get pyenv-installed Python binaries:
mapfile -t PYENV_PYTHONS < <(
find "$HOME/.pyenv/versions" -type f -executable \
-regex '.*/bin/python[0-9\.]*$' 2>/dev/null
)
# Combine, deduplicate and validate:
PYTHON_CANDIDATES=("${SYSTEM_PYTHONS[@]}" "${PYENV_PYTHONS[@]}")
VALID_PYTHONS=() # Initialize array to store valid Python versions
for candidate in "${PYTHON_CANDIDATES[@]}"; do # Validate each candidate
command -v "$candidate" &> /dev/null && VALID_PYTHONS+=("$candidate")
done
# List available Python versions and their index:
if [ ${#VALID_PYTHONS[@]} -gt 0 ]; then # if any valid Python versions found
echo "Available Python interpreters: 🐍"
for i in "${!VALID_PYTHONS[@]}"; do
echo " [$i] ${VALID_PYTHONS[$i]}"
done
msg="\nπŸ“ You can enter a full path, a Python name,"
echo -e "$msg or just an index number from the list above.\n"
PYTHON_DEFAULT=${VALID_PYTHONS[0]} # Pick 1st valid Python version found
fi
fi
while [ -z "$PYTHON" ]; do # Prompt only if not set via user args...
# Ask for Python executable (default: index [0] python):
read -rei "$PYTHON_DEFAULT" -p "🐍 Python executable path or index: " PYTHON
done
[[ $PYTHON == ~* ]] && PYTHON=${PYTHON/#\~/$HOME} # Expand tilde ~
# If input is a valid index, convert it to the actual path:
[[ $PYTHON =~ ^[0-9]+$ ]] && ((PYTHON >= 0 &&
PYTHON < ${#VALID_PYTHONS[@]})) && PYTHON=${VALID_PYTHONS[$PYTHON]}
echo -e "βœ… Chosen Python for creating venv: $PYTHON\n"
# Validate the final Python path:
if ! (command -v "$PYTHON" &> /dev/null && "$PYTHON" -V &> /dev/null); then
echo "❌ Invalid Python path: $PYTHON"; exit 1
fi
while [ -z "$VENV_DIR" ]; do # Prompt only if not set via user args...
# Ask for venv target folder (default: ~/Apps/Thonny/):
read -rei "$HOME/Apps/Thonny" -p "πŸ“ Virtualenv target folder: " VENV_DIR
done
[[ $VENV_DIR == ~* ]] && VENV_DIR=${VENV_DIR/#\~/$HOME} # Expand tilde ~
[ "$VENV_DIR" != / ] && VENV_DIR=${VENV_DIR%/} # Remove trailing /
# Extract the parent directory of the virtual environment target:
VENV_DIR_PARENT=$(dirname "$VENV_DIR")
# Define a regex that matches either the root (/) or the user's home directory:
REGEX="^(/|${HOME})$"
# Block installation if the target path or its parent is either / or $HOME
# This prevents accidental installs into critical system or personal folders:
if [[ $VENV_DIR =~ $REGEX || $VENV_DIR_PARENT =~ $REGEX ]]; then
echo -e "\n❌ Direct home/root subfolder not allowed: $VENV_DIR"
exit 1
fi
# Check if target already exists:
if [ -e "$VENV_DIR" ]; then
echo
read -rp "⚠️ Warning: '$VENV_DIR' already exists. Overwrite? [y/N] " YES
[[ ${YES:0:1} =~ ^[YySsOo]$ ]] || { echo "❌ Aborting..."; exit 1; }
rm -rf "$VENV_DIR" # Delete target folder before creating venv there
fi
# Create the virtual environment:
echo -e "\nπŸ›  Creating virtualenv in '$VENV_DIR' using '$PYTHON'..."
"$PYTHON" -m venv --copies "$VENV_DIR"
# Upgrade venv's pip and install Thonny + py5:
echo -e "πŸ“¦ Installing thonny + thonny-py5mode...\n"
VENV_BIN="$VENV_DIR/bin"; VENV_PIP="$VENV_BIN/pip"
"$VENV_PIP" install -U pip; echo
"$VENV_PIP" install thonny thonny-py5mode[extras]; echo
# Also, add package pip-review for easily update everything inside venv.
# To use it, while venv is active, type in: pip-review -aC
"$VENV_PIP" install pip-review; echo
# Locate Thonny icon inside the virtual environment's site-packages folder,
# regardless of the specific Python version (e.g., python3.10, python3.13).
# This finds the first matching "thonny.png" file and stores its full path:
ICON_PATH=$(find "$VENV_DIR/lib" -type f \
-path "*/site-packages/thonny/res/thonny.png" | head -n 1)
# Check if previous icon search resulted in an empty string:
if [ -z "$ICON_PATH" ]; then
echo "πŸ”Ά Warning: Thonny icon not found!"
ICON_PATH="/usr/share/icons/hicolor/256x256/apps/org.thonny.Thonny.png"
[ -f $ICON_PATH ] || ICON_PATH=thonny # Try 'thonny' as last recourse.
# If not empty, locally register the found Thonny's icon:
elif command -v xdg-icon-resource &> /dev/null; then
echo -e "πŸ–ΌοΈ Registering Thonny icon as 'thonny' from: $ICON_PATH\n"
xdg-icon-resource install --size 256 --novendor "$ICON_PATH" thonny
ICON_PATH=thonny # Change icon to the locally registered name 'thonny'.
fi
# Build KDE's ".directory" icon for Thonny's env folder:
cat > "$VENV_DIR/.directory" <<EOF
[Desktop Entry]
Icon=$ICON_PATH
EOF
# Make sure "Desktop" folder exists:
DESKTOP_DIR="$HOME/Desktop"
mkdir -p "$DESKTOP_DIR"
# Build "Thonny.desktop" launcher:
THONNY_LAUNCHER="$DESKTOP_DIR/Thonny.desktop"; THONNY_EXE="$VENV_BIN/thonny"
cat > "$THONNY_LAUNCHER" <<EOF
[Desktop Entry]
Type=Application
Name=Thonny
GenericName=Python IDE
Comment=Run Thonny IDE in isolated environment
Exec=$THONNY_EXE %F
Icon=$ICON_PATH
Terminal=false
StartupWMClass=Thonny
Categories=Development;IDE
Keywords=programming;education
MimeType=text/x-python
Actions=Edit
[Desktop Action Edit]
Exec=$THONNY_EXE %F
Name=Edit with Thonny
EOF
# Make the "Thonny.desktop" launcher file executable:
chmod +x "$THONNY_LAUNCHER"
if command -v gio &> /dev/null; then
gio set "$THONNY_LAUNCHER" metadata::trusted true # Make the launcher trusted
fi
# And also make a copy of "Thonny.desktop" inside Thonny's env folder:
cp -p "$THONNY_LAUNCHER" "$VENV_DIR"
# Build "run-thonny" CLI launcher, using relative path to find Thonny's Python:
THONNY_CLI_RUN="$VENV_DIR/run-thonny"
# Quoting 'EOF' prevents variable expansion within the heredoc operator `<<`.
# Those variables will expand only when "run-thonny" script runs:
cat > "$THONNY_CLI_RUN" <<'EOF'
#!/usr/bin/env bash
# Get the absolute path to the directory containing this running script:
SCRIPT_FOLDER="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Construct the path to the Python executable inside the virtual environment:
PYTHON_EXE="$SCRIPT_FOLDER/bin/python"
# Define the folder where Thonny's log file will be stored:
LOG_FOLDER="$SCRIPT_FOLDER/.thonny"
# Define the full path to the log file:
LOG_FILE="$LOG_FOLDER/thonny.log"
# Create the log directory if it doesn't already exist:
mkdir -p "$LOG_FOLDER"
# Launch Thonny module in the background using the virtual environment's Python.
# Redirect both stdout and stderr to the log file.
# `nohup` allows the process to continue running after the terminal is closed.
# `disown` detaches the process from the shell's job control table:
nohup "$PYTHON_EXE" -m thonny > "$LOG_FILE" 2>&1 & disown
EOF
# Make the "run-thonny" CLI launcher file executable:
chmod +x "$THONNY_CLI_RUN"
# List all installed packages:
"$VENV_PIP" list; echo
# Show further details of the 3 main installed packages:
"$VENV_PIP" show thonny thonny-py5mode py5; echo
echo -e "βœ… Setup complete! You can now launch Thonny from your desktop.\n"
# Check default shell and show the corresponding instruction to activate venv:
case "$SHELL" in
*/fish)
echo "🐟 You're using Fish. Run the command below to activate venv:"
echo "source $VENV_BIN/activate.fish";;
*)
echo "πŸ’‘ Using Bash or a compatible shell. Run this to activate venv:"
echo "source $VENV_BIN/activate"
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment