Skip to content

Commit

Permalink
install.sh: rework for better error reporting and fix some bugs.
Browse files Browse the repository at this point in the history
Fix a bug where auto_venv.sh was being created in a non-existent directory.

Trap exit codes for some commands and add some help text + GitHUb url at
the end of the install process.

Try to comment what some sections do, and insert linebreaks so they are
more logically broken up in the installer output.

Try to be more consistent with colours.

Try to be more friendly with colours- remove full red warning text in
favour of a prefix so the errors/warnings are easier to read.

Return a failure exit code if bits of the script have failed.

Try to re-order output so it's more logical.

Re-word venv creation message.
  • Loading branch information
Gadgetoid committed Nov 23, 2023
1 parent 9d2b4c8 commit 8134ffa
Showing 1 changed file with 123 additions and 42 deletions.
165 changes: 123 additions & 42 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
#!/bin/bash
LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'`
CONFIG=/boot/config.txt
CONFIG_FILE=config.txt
CONFIG_DIR="/boot/firmware"
DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"`
CONFIG_BACKUP=false
APT_HAS_UPDATED=false
RESOURCES_TOP_DIR=$HOME/Pimoroni
VENV_BASH_SNIPPET=$RESOURCES_DIR/auto_venv.sh
VENV_DIR=$HOME/.virtualenvs/pimoroni
RESOURCES_TOP_DIR="$HOME/Pimoroni"
VENV_BASH_SNIPPET="$RESOURCES_TOP_DIR/auto_venv.sh"
VENV_DIR="$HOME/.virtualenvs/pimoroni"
WD=`pwd`
USAGE="./install.sh (--unstable)"
POSITIONAL_ARGS=()
FORCE=false
UNSTABLE=false
PYTHON="python"
CMD_ERRORS=false


user_check() {
if [ $(id -u) -eq 0 ]; then
printf "Script should not be run as root. Try './install.sh'\n"
exit 1
fatal "Script should not be run as root. Try './install.sh'\n"
fi
}

Expand Down Expand Up @@ -53,13 +54,36 @@ inform() {
}

warning() {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
echo -e "$(tput setaf 1)⚠ WARNING:$(tput sgr0) $1"
}

fatal() {
echo -e "$(tput setaf 1)⚠ FATAL:$(tput sgr0) $1"
exit 1
}

find_config() {
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then
CONFIG_DIR="/boot"
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then
fatal "Could not find $CONFIG_FILE!"
fi
else
if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then
warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE"
warning "You might want to fix this!"
fi
fi
inform "Using $CONFIG_FILE in $CONFIG_DIR"
}

venv_bash_snippet() {
inform "Checking for $VENV_BASH_SNIPPET\n"
if [ ! -f $VENV_BASH_SNIPPET ]; then
inform "Creating $VENV_BASH_SNIPPET\n"
mkdir -p $RESOURCES_TOP_DIR
cat << EOF > $VENV_BASH_SNIPPET
# Add `source $RESOURCES_DIR/auto_venv.sh` to your ~/.bashrc to activate
# Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate
# the Pimoroni virtual environment automagically!
VENV_DIR="$VENV_DIR"
if [ ! -f \$VENV_DIR/bin/activate ]; then
Expand All @@ -77,42 +101,52 @@ venv_check() {
PYTHON_BIN=`which $PYTHON`
if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then
printf "This script should be run in a virtual Python environment.\n"
if confirm "Would you like us to create one for you?"; then
if confirm "Would you like us to create and/or use a default one?"; then
printf "\n"
if [ ! -f $VENV_DIR/bin/activate ]; then
inform "Creating virtual Python environment in $VENV_DIR, please wait...\n"
inform "Creating a new virtual Python environment in $VENV_DIR, please wait...\n"
mkdir -p $VENV_DIR
/usr/bin/python3 -m venv $VENV_DIR --system-site-packages
venv_bash_snippet
source $VENV_DIR/bin/activate
else
inform "Found existing virtual Python environment in $VENV_DIR\n"
inform "Activating existing virtual Python environment in $VENV_DIR\n"
printf "source $VENV_DIR/bin/activate\n"
source $VENV_DIR/bin/activate
fi
inform "Activating virtual Python environment in $VENV_DIR..."
inform "source $VENV_DIR/bin/activate\n"
source $VENV_DIR/bin/activate

else
exit 1
printf "\n"
fatal "Please create and/or activate a virtual Python environment and try again!\n"
fi
fi
printf "\n"
}

check_for_error() {
if [ $? -ne 0 ]; then
CMD_ERRORS=true
warning "^^^ 😬"
fi
}

function do_config_backup {
if [ ! $CONFIG_BACKUP == true ]; then
CONFIG_BACKUP=true
FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt"
inform "Backing up $CONFIG to /boot/$FILENAME\n"
sudo cp $CONFIG /boot/$FILENAME
inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n"
sudo cp $CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME
mkdir -p $RESOURCES_TOP_DIR/config-backups/
cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME
cp $CONFIG_DIR/$CONFIG_FILE $RESOURCES_TOP_DIR/config-backups/$FILENAME
if [ -f "$UNINSTALLER" ]; then
echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER
echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> $UNINSTALLER
fi
fi
}

function apt_pkg_install {
PACKAGES=()
PACKAGES_IN=("$@")
# Check the list of packages and only run update/install if we need to
for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do
PACKAGE="${PACKAGES_IN[$i]}"
if [ "$PACKAGE" == "" ]; then continue; fi
Expand All @@ -124,20 +158,24 @@ function apt_pkg_install {
done
PACKAGES="${PACKAGES[@]}"
if ! [ "$PACKAGES" == "" ]; then
echo "Installing missing packages: $PACKAGES"
printf "\n"
inform "Installing missing packages: $PACKAGES"
if [ ! $APT_HAS_UPDATED ]; then
sudo apt update
APT_HAS_UPDATED=true
fi
sudo apt install -y $PACKAGES
check_for_error
if [ -f "$UNINSTALLER" ]; then
echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER
fi
fi
}

function pip_pkg_install {
# A null Keyring prevents pip stalling in the background
PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@"
check_for_error
}

while [[ $# -gt 0 ]]; do
Expand Down Expand Up @@ -167,30 +205,33 @@ while [[ $# -gt 0 ]]; do
esac
done

printf "Installing $LIBRARY_NAME...\n\n"

user_check
venv_check

if [ ! -f `which $PYTHON` ]; then
printf "Python path $PYTHON not found!\n"
exit 1
fatal "Python path $PYTHON not found!\n"
fi

PYTHON_VER=`$PYTHON --version`

printf "$LIBRARY_NAME Python Library: Installer\n\n"

inform "Checking Dependencies. Please wait..."

# Install toml and try to read pyproject.toml into bash variables

pip_pkg_install toml

CONFIG_VARS=`$PYTHON - <<EOF
import toml
config = toml.load("pyproject.toml")
github_url = config['project']['urls']['GitHub']
p = dict(config['tool']['pimoroni'])
# Convert list config entries into bash arrays
for k, v in p.items():
v = "'\n\t'".join(v)
p[k] = f"('{v}')"
print(f'GITHUB_URL="{github_url}"')
print("""
APT_PACKAGES={apt_packages}
SETUP_CMDS={commands}
Expand All @@ -199,8 +240,8 @@ CONFIG_TXT={configtxt}
EOF`
if [ $? -ne 0 ]; then
warning "Error parsing configuration...\n"
exit 1
# This is bad, this should not happen in production!
fatal "Error parsing configuration...\n"
fi
eval $CONFIG_VARS
Expand All @@ -210,13 +251,17 @@ UNINSTALLER=$RESOURCES_DIR/uninstall.sh
RES_DIR_OWNER=`stat -c "%U" $RESOURCES_TOP_DIR`
# Previous install.sh scripts were run as root with sudo, which caused
# the ~/Pimoroni dir to be created with root ownership. Try and fix it.
if [[ "$RES_DIR_OWNER" == "root" ]]; then
warning "\n\nFixing $RESOURCES_TOP_DIR permissions!\n\n"
sudo chown -R $USER:$USER $RESOURCES_TOP_DIR
fi
mkdir -p $RESOURCES_DIR
# Create a stub uninstaller file, we'll try to add the inverse of every
# install command run to here, though it's not complete.
cat << EOF > $UNINSTALLER
printf "It's recommended you run these steps manually.\n"
printf "If you want to run the full script, open it in\n"
Expand All @@ -225,47 +270,64 @@ exit 1
source $VIRTUAL_ENV/bin/activate
EOF
if $UNSTABLE; then
warning "Installing unstable library from source.\n\n"
else
printf "Installing stable library from pypi.\n\n"
fi
printf "\n"
inform "Installing for $PYTHON_VER...\n"
# Install apt packages from pyproject.toml / tool.pimoroni.apt_packages
apt_pkg_install "${APT_PACKAGES[@]}"
printf "\n"
if $UNSTABLE; then
warning "Installing unstable library from source.\n"
pip_pkg_install .
else
inform "Installing stable library from pypi.\n"
pip_pkg_install $LIBRARY_NAME
fi
if [ $? -eq 0 ]; then
success "Done!\n"
echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER
fi
cd $WD
find_config
# Run the setup commands from pyproject.toml / tool.pimoroni.commands
for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do
CMD="${SETUP_CMDS[$i]}"
# Attempt to catch anything that touches /boot/config.txt and trigger a backup
if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then
# Attempt to catch anything that touches config.txt and trigger a backup
if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then
do_config_backup
fi
eval $CMD
check_for_error
done
printf "\n"
# Add the config.txt entries from pyproject.toml / tool.pimoroni.configtxt
for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do
CONFIG_LINE="${CONFIG_TXT[$i]}"
if ! [ "$CONFIG_LINE" == "" ]; then
do_config_backup
inform "Adding $CONFIG_LINE to $CONFIG\n"
sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG
if ! grep -q "^$CONFIG_LINE" $CONFIG; then
printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG
inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE"
sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE
if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then
printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE
fi
fi
done
printf "\n"
# Just a straight copy of the examples/ dir into ~/Pimoroni/board/examples
if [ -d "examples" ]; then
if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then
inform "Copying examples to $RESOURCES_DIR"
Expand All @@ -277,9 +339,12 @@ fi
printf "\n"
# Use pdoc to generate basic documentation from the installed module
if confirm "Would you like to generate documentation?"; then
inform "Installing pdoc. Please wait..."
pip_pkg_install pdoc
printf "Generating documentation.\n"
inform "Generating documentation.\n"
$PYTHON -m pdoc $LIBRARY_NAME -o $RESOURCES_DIR/docs > /dev/null
if [ $? -eq 0 ]; then
inform "Documentation saved to $RESOURCES_DIR/docs"
Expand All @@ -289,6 +354,22 @@ if confirm "Would you like to generate documentation?"; then
fi
fi
success "\nAll done!"
inform "If this is your first time installing you should reboot for hardware changes to take effect.\n"
inform "Find uninstall steps in $UNINSTALLER\n"
printf "\n"
if [ "$CMD_ERRORS" = true ]; then
warning "One or more setup commands appear to have failed."
printf "This might prevent things from working properly.\n"
printf "Make sure your OS is up to date and try re-running this installer.\n"
printf "If things still don't work, report this or find help at $GITHUB_URL.\n\n"
else
success "\nAll done!"
fi
printf "If this is your first time installing you should reboot for hardware changes to take effect.\n"
printf "Find uninstall steps in $UNINSTALLER\n\n"
if [ "$CMD_ERRORS" = true ]; then
exit 1
else
exit 0
fi

0 comments on commit 8134ffa

Please sign in to comment.