dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

updatemaster (4463B)


      1 #!/usr/bin/env ruby
      2 # frozen_string_literal: true
      3 
      4 def check_deps(deps)
      5   deps.each do |dep|
      6     unless system("command -v #{dep} >/dev/null 2>&1")
      7       warn "error: command #{dep} not found"
      8       exit 1
      9     end
     10   end
     11 end
     12 
     13 # The Homebrew package manager for macOS
     14 class Homebrew
     15   def initialize
     16     check_deps ['brew']
     17     puts '-- HOMEBREW --'
     18     2.times { system 'brew update' }
     19     @outdated_casks = `brew outdated --cask --greedy --verbose`.lines.map(&:chomp)
     20     @cask_names = @outdated_casks.map { |c| c.split.first }
     21     @outdated_formulae = `brew outdated --formula`
     22   end
     23 
     24   def start_sudo_cache
     25     puts 'Please enter the administrator password to install cask upgrades.'
     26     system 'sudo -v'
     27     @sudo_cache_pid = spawn 'while :; do sudo -n true; sleep 60; done 2>/dev/null'
     28   end
     29 
     30   def prompt_user
     31     return if @outdated_casks.empty?
     32 
     33     puts "\nOutdated casks:"
     34     @outdated_casks.each_with_index { |c, i| puts "#{i} #{c}" }
     35     print 'Numbers of casks to upgrade (space-separated): '
     36     begin
     37       @to_upgrade = $stdin.gets.chomp.split.map { |casknum| @cask_names[Integer(casknum)] }
     38     rescue ArgumentError
     39       warn 'Only numbers allowed'
     40       exit 1
     41     end
     42     start_sudo_cache unless @to_upgrade.empty?
     43   end
     44 
     45   def do_upgrade
     46     if @outdated_formulae.empty?
     47       puts "\nNo formulae to upgrade."
     48     else
     49       puts "\nUpgrading all formulae.\nTo upgrade:"
     50       puts @outdated_formulae
     51       system 'brew upgrade --formula'
     52     end
     53 
     54     return if @outdated_casks.empty?
     55 
     56     if @to_upgrade.empty?
     57       puts 'Not upgrading any casks.'
     58     else
     59       puts "\nUpgrading casks:"
     60       puts @to_upgrade
     61 
     62       @to_upgrade.each { |cask| system "brew upgrade --cask --greedy --verbose #{cask}" }
     63       puts 'Upgrade complete.'
     64     end
     65   end
     66 
     67   def cleanup
     68     Process.kill('TERM', @sudo_cache_pid) if @sudo_cache_pid
     69     system 'brew cleanup -s --prune-prefix'
     70     system 'brew cleanup'
     71     system 'brew doctor'
     72     system 'brew missing'
     73 
     74     puts "Homebrew cache disk usage: #{`du -skh "$(brew --cache)" | cut -f1`}"
     75     puts "Remove with:\trm -rf $(brew --cache)"
     76     system 'brew bundle dump --force --file=~/.dotfiles/Brewfile;'
     77   end
     78 end
     79 
     80 # Pipx for Python
     81 class Pipx
     82   def initialize
     83     check_deps ['pipx']
     84     puts '-- PIPX --'
     85   end
     86 
     87   def do_upgrade
     88     system 'pipx upgrade-all'
     89   end
     90 end
     91 
     92 # Gems for Ruby
     93 class RubyGems
     94   def initialize
     95     check_deps ['gem']
     96     puts '-- GEM --'
     97     puts 'To upgrade:'
     98     system 'gem outdated'
     99   end
    100 
    101   def do_upgrade
    102     system 'gem update'
    103   end
    104 
    105   def cleanup
    106     system 'gem cleanup'
    107   end
    108 end
    109 
    110 # NPM for JS
    111 class NPM
    112   def initialize
    113     check_deps ['npm']
    114     puts '-- NPM --'
    115     puts 'To upgrade:'
    116     system 'npm -g outdated'
    117   end
    118 
    119   def do_upgrade
    120     outdated = `npm outdated -g --parseable | sed 's!:.*!!g; s!.*/!!'`.split
    121     outdated.each { |pkg| system "npm -g install #{pkg}@latest" }
    122     system 'npm -g upgrade'
    123   end
    124 end
    125 
    126 def do_prompts(systems)
    127   system_instances = systems.map(&:new)
    128   system_instances.each(&:prompt_user) if (['-n', '--dry-run'] & ARGV).empty?
    129   system_instances
    130 end
    131 
    132 def split_systems_by_prompting(all_systems)
    133   systems_with_prompt = all_systems.select { |s| s.method_defined? 'prompt_user' }
    134   { prompting: systems_with_prompt,
    135     no_prompting: all_systems - systems_with_prompt }
    136 end
    137 
    138 def upgrade_cleanup(system_instance)
    139   system_instance.do_upgrade if (['-n', '--dry-run'] & ARGV).empty?
    140   system_instance.cleanup if system_instance.class.method_defined? 'cleanup'
    141 end
    142 
    143 def main
    144   package_systems = [Homebrew, Pipx, RubyGems, NPM]
    145 
    146   split_systems = split_systems_by_prompting package_systems
    147 
    148   # Do any prompts to get interactivity out of the way, then upgrade those first
    149   do_prompts(split_systems[:prompting]).each { |s| upgrade_cleanup s } unless split_systems[:prompting].empty?
    150 
    151   # Finish up by upgrading systems that don't need user input
    152   split_systems[:no_prompting].each do |nonprompting_sys|
    153     pksys = nonprompting_sys.new
    154     upgrade_cleanup pksys
    155   end
    156 end
    157 
    158 def handle_help
    159   return if (['-h', '--help'] & ARGV).empty?
    160 
    161   puts <<~END_HELPTEXT
    162     Usage:  updatemaster [options]
    163 
    164     Options:
    165     -h, --help    display this helptext
    166     -n, --dry-run   don't upgrade anything, only update & cleanup
    167   END_HELPTEXT
    168   exit 0
    169 end
    170 
    171 handle_help
    172 begin
    173   caffeinate = spawn 'caffeinate -d'
    174   main
    175 rescue SystemExit, Interrupt
    176   puts "\nInterrupted, exiting..."
    177 end
    178 Process.kill('SIGTERM', caffeinate)
    179 Process.detach(caffeinate)