#!/usr/bin/env bash running=0 _check_running() { if [ $running -ne 0 ]; then _unlock exit $running fi } _onkill() { running=1 } _lock() { if [ -f ~/.config/dotfiles/.data/update_lock ]; then return 1 fi touch ~/.config/dotfiles/.data/update_lock } _lock_or_error_message() { _lock local value=$? if [ $value -ne 0 ]; then echo -e "\033[31;1merror:\033[0m update seems to be already running" echo -e "\033[1minfo:\033[0m if you're sure it's not running, run \`update force-unlock\`" return $value fi } _unlock() { rm -rf ~/.config/dotfiles/.data/update_lock } _check_date_if_format() { local format=`_date_format` if [ "$format" != "" ]; then _check_date $format $1 fi } _date_format() { if [ "$UPDATE_CHECK_TYPE" = "sliding" ]; then echo "+%s" else case $UPDATE_CHECK in "daily") echo +%d/%m/%Y;; "weekly") echo +%V/%Y;; "monthly") echo +%m/%Y;; esac fi } _print_last_update() { if [ "$UPDATE_CHECK_TYPE" = "sliding" ]; then if [ -s ~/.config/dotfiles/.data/update_date ]; then date --date=@$(cat ~/.config/dotfiles/.data/update_date) +%x else echo never fi else echo -e >&2 "\033[31;1merror:\033[0m last-update only available for sliding update check" fi } _check_date_file() { mkdir -p ~/.config/dotfiles/.data touch ~/.config/dotfiles/.data/update_date } _sentence() { local subject0=("Your" "The" "This") local subject1=("system" "machine" "pc" "computer") local adjective=("awesome" "incredible" "amazing" "brave" "hard working" "loyal" "nice" "polite" "powerful" "pro-active" "reliable" "fabulous" "fantastic" "incredible" "outstanding" "remarkable" "spectacular" "splendid" "super" "happy" "cheerful") local verb=("is" "seems" "looks" "appears to be") local no_verb=("is not" "doesn't seem" "doesn't look" "appears not to be") local adverb=("up-to-date" "ready" "updated") local dot=("." "!" "...") local n_subject0=`shuf -i0-"$((${#subject0[@]}-1))" -n1` local n_subject1=`shuf -i0-"$((${#subject1[@]}-1))" -n1` local n_adjective=`shuf -i0-"$((${#adjective[@]}-1))" -n1` local n_adverb=`shuf -i0-"$((${#adverb[@]}-1))" -n1` local n_dot=`shuf -i0-"$((${#dot[@]}-1))" -n1` if [ $1 = "updated" ]; then local color="32" local n_verb=`shuf -i0-"$((${#verb[@]}-1))" -n1` local s_verb=${verb[$n_verb]} local end="" elif [ $1 = "not_updated" ]; then local color="31" local n_verb=`shuf -i0-"$((${#no_verb[@]}-1))" -n1` local s_verb=${no_verb[$n_verb]} local end="Run \`update\` to update your system." fi echo -e "\033[$color;1m${subject0[$n_subject0]} ${adjective[$n_adjective]} ${subject1[$n_subject1]} $s_verb ${adverb[$n_adverb]}${dot[$n_dot]} ${end}\033[0m" } _check_date() { _check_date_file local old_date=`cat ~/.config/dotfiles/.data/update_date` local new_date=`date $1` local updated=0 if [ -s ~/.config/dotfiles/.data/update_date ] && [ "$UPDATE_CHECK_TYPE" = "sliding" ]; then local diff=$(($new_date - $old_date)) case $UPDATE_CHECK in "daily") [ $diff -lt 86400 ] && updated=1;; "weekly") [ $diff -lt 604800 ] && updated=1;; "monthly") [ $diff -lt 18748800 ] && updated=1;; esac else if [ "$new_date" = "$old_date" ] ; then updated=1 fi fi if [ $updated -eq 0 ] && [ ! -f ~/.config/dotfiles/.data/update_lock ]; then _sentence not_updated elif [ "$UPDATE_CHECK_ALWAYS" = "true" ] || [ "$2" = "force" ]; then _sentence updated fi } # Returns 0 if $1 > $2 _semver_greater() { local major_1=$(echo $1 | cut -d '.' -f 1) local minor_1=$(echo $1 | cut -d '.' -f 2) local patch_1=$(echo $1 | cut -d '.' -f 3) local major_2=$(echo $2 | cut -d '.' -f 1) local minor_2=$(echo $2 | cut -d '.' -f 2) local patch_2=$(echo $2 | cut -d '.' -f 3) if [ $major_1 -gt $major_2 ]; then return 0 elif [ $major_1 -eq $major_2 ] && [ $minor_1 -gt $minor_2 ]; then return 0 elif [ $major_1 -eq $major_2 ] && [ $minor_1 -eq $minor_2 ] && [ $patch_1 -gt $patch_2 ]; then return 0 else return 1 fi } update-system() { if [ -f ~/.config/dotfiles/.data/noroot ]; then # If this file exists, it means root is not available: skip the system update echo -e "\033[33;1m=== You don't have root, skipping system update ===\033[0m" return fi if [ -z "$password" ]; then echo -e "\033[32;1m=== Starting system update, please enter your password ===\033[0m" sudo true else echo $password | sudo -S true fi if [ $? -ne 0 ]; then echo "Could not get sudo..." return 1 fi echo -e "\033[32;1m=== Updating system ===\033[0m" local start=`date +%s` # Debian based systems command -v apt > /dev/null 2>&1 if [ $? -eq 0 ]; then if [ -z "$password" ]; then sudo apt update -y else echo $password | sudo -S apt update -y fi if [ $? -eq 0 ]; then if [ -z "$password" ]; then sudo apt upgrade -y else echo $password | sudo -S apt upgrade -y fi if [ $? -eq 0 ]; then if [ -z "$password" ]; then sudo apt autoremove -y else echo $password | sudo -S apt autoremove -y fi fi fi fi # Archlinux based systems command -v yay > /dev/null 2>&1 if [ $? -eq 0 ]; then if [ ! -z "$password" ]; then echo $password | sudo -S true fi yay -Syu --noconfirm yay -Syua --noconfirm else command -v pacman > /dev/null 2>&1 if [ $? -eq 0 ]; then if [ -z "$password" ]; then sudo pacman -Syu --noconfirm else echo $password | sudo -S pacman -Syu --noconfirm fi fi fi # Fedora based systems command -v dnf > /dev/null 2>&1 if [ $? -eq 0 ]; then if [ -z "$password" ]; then sudo dnf upgrade else echo $password | sudo -S dnf upgrade fi fi local seconds=$((`date +%s` - $start)) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== System updated in $formatted ===\033[0m" } update-rust() { # Update rust if installed command -v rustup > /dev/null 2>&1 if [ $? -ne 0 ]; then return fi local start=`date +%s` echo -e "\033[32;1m=== Updating rustup ===\033[0m" rustup self update echo -e "\033[32;1m=== Updating rust ===\033[0m" rustup update cargo install-update --help > /dev/null 2>&1 # Program to update cargo programs is not installed if [ $? -ne 0 ]; then echo -e "\033[33;1m=== To allow updating rust packages, run \`cargo install cargo-update\`===\033[0m" fi # If the user has no root, and if he doesn't have openssl, we won't install cargo update. # Before running cargo update, we check again that it is installed. cargo install-update --help > /dev/null 2>&1 if [ $? -eq 0 ]; then echo -e "\033[32;1m=== Updating rust packages ===\033[0m" cargo install-update -ag fi local seconds=$((`date +%s` - $start)) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== Rust updated in $formatted ===\033[0m" } update-wasm() { command -v wasmer > /dev/null 2>&1 if [ $? -ne 0 ]; then return fi local start=`date +%s` echo -e "\033[32;1m=== Updating wasmer ===\033[0m" local old_path=$PWD # Protect dotfiles from being modified by wasmer self-update cd ~/.config/dotfiles git stash > /dev/null 2>&1 wasmer self-update git checkout zshrc > /dev/null 2>&1 git stash pop > /dev/null 2>&1 cd $old_path command -v wapm > /dev/null 2>&1 if [ $? -ne 0 ]; then return fi echo -e "\033[32;1m=== Updating wapm packages ===\033[0m" local output=$(wapm list -g | grep '^ _/' | cut -d '|' -f 1 | cut -d '/' -f 2 | while read package; do local package_trimmed=$(echo $package | tr -d '[:space:]') local current_version=$(wapm list -g | grep "^ _/$package_trimmed " | cut -d '|' -f 2 | tr -d '[:space:]') local last_version=$(wapm search $package_trimmed | grep $package_trimmed | cut -d '|' -f 4 | tr -d '[:space:']) local needs_update="No" if _semver_greater $last_version $current_version; then needs_update="Yes" fi echo $package_trimmed@$current_version@$last_version@$needs_update done | column -t -s "@" -N "Package,Installed,Latest,Needs update") if [ "$output" != "" ]; then echo -e "$output\n" else echo "No packages need updating." fi local update_packages=0 for package in $(echo "$output" | tail -n +2 | grep 'Yes' | cut -d ' ' -f 1); do echo Updating $package wapm install -g $package update_packages=$(($update_packages + 1)) done if [ $update_packages -gt 1 ]; then echo "1 package updated." elif [ $update_packages -gt 0 ]; then echo "$update_packages packages updated." fi local seconds=$((`date +%s` - $start)) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== Wapm packages updated in $formatted ===\033[0m" } update-npm() { # Update node packages if installed command -v npm > /dev/null 2>&1 if [ $? -ne 0 ]; then return fi if [ ! -d ~/.npmbin ]; then return fi local start=`date +%s` echo -e "\033[32;1m=== Updating node packages ===\033[0m" npm update -g local seconds=$((`date +%s` - $start)) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== Node packages updated in $formatted ===\033[0m" } update-dotfiles() { local start=`date +%s` local current_dir=$PWD echo -e "\033[32;1m=== Updating dotfiles ===\033[0m" cd ~/.config/dotfiles && git pull if [ -d ~/.config/ohmyzsh ]; then echo -e "\033[32;1m=== Updating ohmyzsh ===\033[0m" cd ~/.config/ohmyzsh && git pull fi if [ -d ~/.config/awesome/.git ]; then echo -e "\033[32;1m=== Updating awesome config ===\033[0m" cd ~/.config/awesome/ && git pull fi cd $current_dir local seconds=$((`date +%s` - $start)) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== Dotfiles updated in $formatted ===\033[0m" } update-neovim() { command -v nvim > /dev/null 2>&1 if [ $? -ne 0 ]; then return fi local start=`date +%s` echo -e "\033[32;1m=== Updating neovim packages ===\033[0m" curl -fLo ~/.local/share/nvim/site/autoload/plug.vim --create-dirs \ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim 2>/dev/null nvim +PlugUpdate +qall local seconds=$((`date +%s` - $start)) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== Neovim updated in $formatted ===\033[0m" } update-postpone() { local format=`_date_format` if [ $? -eq 0 ]; then _check_date_file date $format > ~/.config/dotfiles/.data/update_date fi } _print_help() { echo -e "\033[32mupdate\033[0m" echo -e "Thomas Forgione " echo -e "A script that automatically updates your system" echo _print_usage } _print_usage() { echo -e "\033[33mUSAGE:\033[0m" echo -e " \033[32mupdate\033[0m Auto-detect what to update and perform th update" echo -e " \033[32mupdate \033[0m Calls the update subcommand" echo echo -e "\033[33mSUBCOMMANDS:\033[0m" echo -e " \033[32msystem\033[0m Updates the system (Debian, Archlinux, Fedora)" echo -e " \033[32mrust\033[0m Updates rust and rust packages (requires rustup)" echo -e " \033[32mwasm\033[0m Updates wasmer and wapm packages" echo -e " \033[32mnpm\033[0m Updates npm packages (in ~/.config/npmbin)" echo -e " \033[32mdotfiles\033[0m Updates the dotfiles" echo -e " \033[32mneovim\033[0m Updates the neovim packages" echo -e " \033[32mcheck\033[0m Checks whether the system has been recently updated" echo -e " \033[32mstartup\033[0m Same as \`update check\` but is silent in case of success" echo -e " \033[32mlast-update\033[0m Print the last update date" echo -e " \033[32mpostpone\033[0m Skip the current update (disable check warnings)" echo -e " \033[32mforce-unlock\033[0m Deletes the lock file" } partial-test() { case $1 in "system") return 0;; "rust") return 0;; "wasm") return 0;; "npm") return 0;; "dotfiles") return 0;; "neovim") return 0;; "check") return 0;; "last-update") return 0;; "startup") return 0;; "postpone") return 0;; "force-unlock") return 0;; *) return 1;; esac } partial-requires-lock() { case $1 in "system") return 0;; "rust") return 0;; "wasm") return 0;; "npm") return 0;; "dotfiles") return 0;; "neovim") return 0;; "check") return 1;; "last-update") return 1;; "startup") return 1;; "postpone") return 1;; "force-unlock") return 1;; *) return 1;; esac } partial-update() { case $1 in "system") update-system;; "rust") update-rust;; "wasm") update-wasm;; "npm") update-npm;; "dotfiles") update-dotfiles;; "neovim") update-neovim;; "check") _check_date_if_format force;; "last-update") _print_last_update;; "startup") _check_date_if_format;; "postpone") update-postpone;; "force-unlock") _unlock;; *) return 1 esac } main() { if [ "$1" == "--pass" ]; then shift password=$1 shift fi if [ $# -eq 0 ]; then _lock_or_error_message local error_code=$? if [ $error_code -ne 0 ]; then exit $error_code fi local start=`date +%s` echo -e "\033[32;1m=== Starting full update ===\033[0m" # Update the system _check_running update-system # Update rust and rust packages _check_running update-rust # Update wasmer _check_running update-wasm # Update npm and npm packages _check_running update-npm # Update the dotfiles _check_running update-dotfiles # Update the neovim packages _check_running update-neovim _check_running update-postpone _check_running local seconds=$((`date +%s` - $start )) local formatted=`date -ud "@$seconds" +'%H hours %M minutes %S seconds'` echo -e "\033[32;1m=== Update finished in $formatted ===\033[0m" _check_running _check_date_if_format force _check_running _unlock else local lock_required=1 for part in $@; do if [ $part == "-h" ] || [ $part == "--help" ]; then _print_help exit 0 fi partial-test $part if [ $? -ne 0 ]; then echo -e "\033[1;31merror:\033[0m unrocognized update command \"$part\"" _print_usage return 1 fi partial-requires-lock $part lock_required=$(($lock_required * $?)) done if [ $lock_required -eq 0 ]; then _lock_or_error_message local error_code=$? if [ $error_code -ne 0 ]; then exit $error_code fi fi for part in $@; do _check_running partial-update $part done if [ $lock_required -eq 0 ]; then _unlock fi fi } trap _onkill 2 3 main $@