#!/bin/bash # The whole script is wrapped in { ...; exit; } so bash parses all of it before # running anything. That way, replacing this file mid-run (e.g. by git pull and # the copy below) can't make the running copy read a mix of old and new text. { # Install a script atomically (copy to a temp file, then rename over the target) install_script() { local tmp tmp="$(mktemp "$2.XXXXXX")" && cp "$1" "$tmp" && chmod 755 "$tmp" && mv -f "$tmp" "$2" } # Print the logo print_logo() { cat << "EOF" ███╗ ███╗██╗███╗ ██╗██╗ ████╗ ████║██║████╗ ██║██║ ██╔████╔██║██║██╔██╗ ██║██║ ██║╚██╔╝██║██║██║╚██╗██║██║ ██║ ╚═╝ ██║██║██║ ╚████║██║ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗ ██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗ ███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝ ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗ ███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ EOF } # Clear screen and show logo clear print_logo MINISERVER="$HOME/.local/share/MiniServer" echo "Updating MiniServer..." if [[ -z "$UPDATE_REEXECED" ]] && ! git -C "$MINISERVER" pull; then echo "WARNING: could not pull the latest MiniServer; running the update hooks already on disk." export UPDATE_PULL_FAILED=1 fi mkdir -p "$HOME/.local/bin" "$MINISERVER/updates.d" install_script "$MINISERVER/update" ~/.local/bin/update install_script "$MINISERVER/refresh" ~/.local/bin/refresh install_script "$MINISERVER/sibling" ~/.local/bin/sibling install_script "$MINISERVER/version" ~/.local/bin/version install_script "$MINISERVER/configure" ~/.local/bin/configure if [[ -z "$UPDATE_REEXECED" ]]; then export UPDATE_REEXECED=1 exec ~/.local/bin/update fi # An upgrade that runs out of disk space part way through can leave the system # half-upgraded. Set UPDATE_FORCE=1 to skip this check. MIN_FREE_GIB=10 FREE_KIB="$(df -k --output=avail / | tail -n 1 | tr -d ' ')" if [[ -z "$UPDATE_FORCE" && "$FREE_KIB" =~ ^[0-9]+$ ]] && (( FREE_KIB < MIN_FREE_GIB * 1024 * 1024 )); then echo "Only $(( FREE_KIB / 1024 / 1024 )) GiB free on /; updating needs at least $MIN_FREE_GIB GiB." echo "Free up space (sudo paccache -rk1 clears old cached packages), or run: UPDATE_FORCE=1 update" exit 1 fi # Ask for the password once, then keep sudo's cached credentials fresh until # update exits, so long-running hooks don't stop for a password again. sudo -v || exit 1 while kill -0 $$ 2>/dev/null; do sudo -n -v; sleep 60; done 2>/dev/null & SUDO_KEEPALIVE_PID=$! trap 'kill "$SUDO_KEEPALIVE_PID" 2>/dev/null' EXIT FAILED=() [[ -n "$UPDATE_PULL_FAILED" ]] && FAILED+=("git pull") # Each hook runs in its own subshell so a cd, set -e, exit, or stray variable # in one hook can't leak into or abort the hooks after it. for plugin in "$MINISERVER/updates.d/"*.sh; do [ -f "$plugin" ] || continue ( unset -f is_installed run_update source "$plugin" if declare -f run_update > /dev/null; then if ! declare -f is_installed > /dev/null || is_installed; then run_update fi fi ) # Checked separately: under `( ... ) ||`, bash would ignore set -e inside the hook [ $? -eq 0 ] || FAILED+=("$(basename "$plugin")") done if (( ${#FAILED[@]} > 0 )); then echo "Done, with errors in: ${FAILED[*]}" exit 1 fi echo "Done." exit 0 }