Skip to content

Instantly share code, notes, and snippets.

@agwells
Last active November 10, 2018 22:19
Show Gist options
  • Save agwells/a9bed57c59430ef35e6328d3f93933fe to your computer and use it in GitHub Desktop.
Save agwells/a9bed57c59430ef35e6328d3f93933fe to your computer and use it in GitHub Desktop.
Multi-package-management-system updater
#!/bin/bash
####
# A script to run updates or check for updates, in many different package management systems
# on Ubuntu.
#
# When I was installing everything I use through apt alone, it was easy to keep all my
# packages up to date with "apt-get update && apt-get upgrade", or the GUI update manager.
# But now every programming language has its own separate package management system, and
# I often use these to install packages at a "global" level on my computer. It's a pain
# to keep all of those up to date, because they all require different commands to update
# them.
#
# So, I made this script, and I periodically run it manually.
#
# SECURITY: Most package management systems allow a package to execute arbitrary code as part
# of the installation and upgrade process. This is inherently insecure, whether you're running
# the upgrade with this script or without it. Most package management systems allow you to
# reduce that risk by installing "global" packages in a directory owned by your own user account,
# rather than root. This script is built to support that, and only uses root permissions (via sudo)
# to update things that are installed as root. Even so, you can still get harmed by a malicious
# package.
#
# REQUIREMENTS: This script is hand-tailored for my workstation. I've tried to remove any
# specific usernames or paths, so it's somewhat generic. But it does not check for the
# existence of any of these programs before running them, so it probably will not work
# perfectly unless you have all the same things installed that I do.
#
# COPYRIGHT: Aaron Wells, 2018
#
# LICENSE:
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details:
# <https://www.gnu.org/licenses/>.
#######################
# Don't run this as root. :(
if [[ $(whoami) == "root" ]]
then
echo
echo "******"
echo "ERROR! Don't run this script as root! When it needs root privileges, it asks for them."
echo "******"
exit 1
fi
echo
echo "**********************"
echo "SUDO: Update apt packages..."
echo "**********************"
sudo apt update -qq
sudo apt upgrade -q -y
sudo apt autoremove -q -y
echo
echo "*************************"
echo "SUDO: Update Rubygems (Ruby)..."
echo "*************************"
sudo gem update
echo
echo "*********************************"
echo "SUDO: Update system-level R packages..."
echo "*********************************"
# Go to my home directory first, so that we'll have the normal user libs,
# not project-specific packrat libs.
pushd ~ > /dev/null
# Nearly the same as the script for user-level R packages, except it looks for
# libraries owned by root. Since we're not doing "sudo -H", it'll use my home
# directory, and hence have the same set of library directories as the user-level,
# command, so I don't need to install magrittr and remotes as root packages.
#
# Note that these are generally installed via apt packages, so hopefully apt
# will already have updated them! But it's possible that the CRAN version is
# ahead of the debian repo.
echo 'Checking for outdated packages...'
CMD=$(cat <<EOM
user <- 'root';
rootLibPaths <- .libPaths() %>%
file.info() %>%
use_series("uname") %>%
equals(user) %>%
extract(.libPaths(), .);
.libPaths(rootLibPaths);
rootPkgs <- installed.packages() %>%
rownames();
pkgsToUpdate <- remotes::package_deps(rootPkgs);
if (any(pkgsToUpdate\$diff < 0)) {
print(pkgsToUpdate[pkgsToUpdate\$diff < 0, ]);
remotes::update_packages(rootPkgs);
} else {
cat('No packages to update for user ', user, '\\n')
}
EOM
)
CMD=$(echo $CMD | cat)
sudo -- Rscript --default-packages="utils,magrittr,remotes" -e "${CMD}"
popd > /dev/null
echo
echo "******************************"
echo "SUDO: Update system-installed NPM..."
echo "******************************"
source ~/.nvm/nvm.sh
currentNodeVersion=$(node --version)
echo "Currently on Node $currentNodeVersion. Switching via nvm to system node..."
if nvm use system
then
sudo npm install -g npm@latest
fi
if [[ $(ls /usr/lib/node_modules/ | wc -l) -gt 1 ]]
then
echo "PROBLEM: You've got some global NPM packages installed in the "
echo "(root-owned system directory! You should probably delete those.)"
ls /usr/lib/node_modules | grep -v '^npm$' | sort
fi
echo "Done! Switching back to Node $currentNodeVersion."
nvm use --silent $currentNodeVersion
nvm unload
echo
echo "********************"
echo "Relinquishing SUDO privileges..."
echo "********************"
sudo -k
echo
echo "****************************************"
echo "Update composer (PHP) global packages..."
echo "****************************************"
composer global update
composer selfupdate
echo
echo "*******************************"
echo "Update pip (Python) packages..."
echo "*******************************"
# Go to home directory to avoid project-specific python venv stuff
pushd ~ > /dev/null
if which pip
then
pip list --user --outdated --format=columns | \
tail -n "+3" | \
cut -s -d " " -f 1 | \
sed -e 's/^\|$/"/g' | \
xargs -r pip install --user -U
pip check
else
echo "No pip packages on this system! :)"
fi
popd > /dev/null
echo
echo "*******************************"
echo "Update user-level R packages..."
echo "*******************************"
# Go to my home directory first, so that we'll have the normal user libs,
# not project-specific packrat libs.
pushd ~ > /dev/null
# It gets the current set of libraries, then it finds out which Unix user
# owns each one. It extracts only those libraries owned by me. Then it finds
# all the installed packages in those libraries. Then it checks if any of those
# need to be updated.
echo 'Checking for outdated packages...'
CMD=$(cat <<EOM
user <- '$(whoami)';
pkgs <- .libPaths() %>%
file.info() %>%
use_series("uname") %>%
equals(user) %>%
extract(.libPaths(), .) %>%
installed.packages() %>%
rownames();
pkgsToUpdate <- remotes::package_deps(pkgs);
if (any(pkgsToUpdate\$diff < 0)) {
print(pkgsToUpdate[pkgsToUpdate\$diff < 0, ]);
remotes::update_packages(pkgs);
} else {
cat('No packages to update for user ', user, '\\n')
}
EOM
)
CMD=$(echo $CMD | cat)
Rscript --default-packages="utils,magrittr,remotes" -e "${CMD}"
popd > /dev/null
echo
echo "*******************************************"
echo "Update Node and NPM (JS) global packages..."
echo "*******************************************"
source ~/.nvm/nvm.sh
currentNodeVersion=$(node --version)
echo "Currently on Node $currentNodeVersion. Switching via nvm..."
for nodeversion in $(nvm ls | grep -vF N/A | grep -oP 'v[0-9]+\.[0-9]+' | grep -oP '[0-9]+\.[0-9]+' | cut -d '.' -f 1 | sort | uniq)
do
localversion=$(nvm ls $nodeversion | tail -n 1 | grep -oP 'v[0-9]+\.[0-9]+\.[0-9]+')
remoteversion=$(nvm ls-remote $nodeversion | tail -n 1 | grep -oP 'v[0-9]+\.[0-9]+\.[0-9]+')
if [[ "$localversion" == "$remoteversion" ]]
then
echo " - Node $nodeversion: up-to-date at $localversion"
else
echo " - Node $nodeversion: upgrade from $localversion to $remoteversion"
nvm install $nodeversion --reinstall-packages-from=$localversion
nvm uninstall $localversion
if [[ "$localversion" == "$currentNodeVersion" ]]
then
# Can't switch back to our "current" version anymore because we've uninstalled it!
# So switch back to the upgraded version at the end, instead.
currentNodeVersion=$remoteversion
fi
fi
nvm use --silent $nodeversion
if [[ $(npm -g outdated --parseable npm | wc -c ) -eq 0 ]]
then
echo " -- and npm up-to-date at $(npm --version)"
else
echo " -- and updating npm..."
nvm install-latest-npm
fi
echo " -- Checking global packages for Node $nodeversion..."
# You can't do "npm upgrade" or "npm install" to check for updates to global-installed
# npm packages, because those rely on having a project package.json file with version
# specifiers.
#
# So instead I use the "npm-check-updates" utility, which can check that each global
# package is at its latest version in the npm repo.
#
# But first, npm-check-updates must *itself* be installed as a global package, in the
# current node version.
if [[ -x $(npm -g bin)/npm-check-updates ]];
then
echo " --- Installing npm-check-updates..."
npm -g install npm-check-updates
fi
# Now, check for updates to global packages.
for package in $($(npm -g bin)/npm-check-updates -g 2> /dev/null | grep -oP '^ [^ ]+ ')
do
echo " --- Upgrading package: $package"
npm install -g $package
done
done
echo "Done! Switching back to Node $currentNodeVersion."
nvm use --silent $currentNodeVersion
nvm unload
#echo
#echo "***************************"
#echo "Update Android SDK packages"
#echo "***************************"
#sdkmanager --update
echo
echo "***********************************"
echo "Check for outdated vagrant boxes..."
echo "***********************************"
vagrant box outdated --global
echo
echo "***********************************"
echo "Check whether reboot is required..."
echo "***********************************"
if [ -f /var/run/reboot-required ]; then
echo "REBOOT REQUIRED! :)"
else
echo "No reboot required today."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment