Last active
January 4, 2016 01:19
-
-
Save redperadot/8547413 to your computer and use it in GitHub Desktop.
Change the OS X login window message to a fortune.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # ___ | |
| # /\ \ | |
| # \ \ \ loginfortune | |
| # \ \ \ Created by redperadot@darkgem.net | |
| # _____\_\ \ ___________ | |
| # /\ _____ \/\ _____ \ | |
| # \ \ \___/\ \ \ \___/\ \ If you're running OS X and are tired of your boring | |
| # \ \ \__\_\ \ \ \__\_\ \ lock screen message this is the script for you! | |
| # \ \__________\ \________ \ Using the fortune command this script will change | |
| # \/__________/\/_______/\ \ the lock screen message every so often. | |
| # ___ \ \ \ | |
| # /\ \___\_\ \ | |
| # \ \___________\ | |
| # \/___________/ | |
| ## Script Tools | |
| module Color extend self | |
| def red; "\033[31m"; end | |
| def green; "\033[32m"; end | |
| def yellow; "\033[33m"; end | |
| def blue; "\033[34m"; end | |
| def magenta; "\033[35m"; end | |
| def cyan; "\033[36m"; end | |
| def white; "\033[37m"; end | |
| def reset; "\033[0m" ; end | |
| end | |
| def parse_version(ver) | |
| ver = ver.split('.') | |
| ver = { major: ver[0].to_i, minor: ver[1].to_i, patch: ver[2].to_i } | |
| return ver | |
| end | |
| def acquire_gems(*gems) | |
| gems.each do |_gem| | |
| begin require _gem | |
| rescue LoadError | |
| dep = _gem.split('/')[0] | |
| puts "[Info] Installing the dependency '#{dep}'." | |
| system("gem install #{dep} > /dev/null 2>&1") | |
| require _gem | |
| end | |
| end | |
| end | |
| ## Check The System | |
| def sys_check | |
| os = parse_version(`sw_vers -productVersion`) | |
| failed = Array.new | |
| failed.push("You need OS X.") unless (/darwin/ =~ RUBY_PLATFORM) != nil && (os[:major] == 10 && os[:minor] >= 8) | |
| failed.push("Homebrew is not installed.\n ~> #{Color.yellow}Instructions at http://brew.sh") unless File.exists?('/usr/local/bin/brew') | |
| failed.push("The required Ruby version 2.0 was not meet.\n ~> #{Color.yellow}Instructions at http://rvm.io") unless RUBY_VERSION.to_f >= 2.0 | |
| failed.push("Fortune is required.\n ~> #{Color.yellow}brew install fortune") unless File.exists?('/usr/local/bin/fortune') | |
| failed.push("Root privileges are required.\n ~> #{Color.yellow}sudo #{$0}") unless ENV["USER"] == "root" || ENV["USER"].nil? | |
| unless failed.empty? | |
| puts "The script could not run because of the fallowing:" | |
| failed.each { |error| puts " #{Color.red}#{error}#{Color.reset}" } | |
| exit 1 | |
| end | |
| end | |
| ## Require Stuff & Clean Exit | |
| acquire_gems('optparse', 'fileutils', 'io/console') | |
| Signal.trap("INT") { |signo| puts; puts "See you next time!"; exit 0 } | |
| ## Set Configuration | |
| $version = 9.8 | |
| $config = { | |
| :update_path => "https://gist.github.com/redperadot/8547413/raw/loginfortune.rb", | |
| :script_path => "/usr/local/bin/loginfortune", | |
| :daemon_path => "/Library/LaunchDaemons/com.darkgem.loginfortune.plist", | |
| :logfile_path => "/Library/Logs/loginfortune.log", | |
| :current_path => File.expand_path(File.symlink?($0) ? File.readlink($0) : $0), | |
| :tell => false, | |
| :debug => false, | |
| :length => 60, | |
| :interval => 1800, | |
| :log_limit => 3000, | |
| :daemon_ver => 2, | |
| } | |
| ## Define Core Functions | |
| def fortune(reset = false) | |
| # Use the fortune command to get a string within the specified length. | |
| $fortune = `/usr/local/bin/fortune -e -s -n #{$config[:length]}`. | |
| # Encode the string in UTF-8 and remove any invalid or undefined characters. | |
| encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => ''). | |
| # Strip out any extra space. | |
| gsub(/\s+/, " ").strip if $fortune.nil? || reset | |
| # Now return our fortune even if we didn't reset it. | |
| return $fortune | |
| end | |
| def efires_rehash | |
| return false unless `fdesetup status`.include? 'On' | |
| fork do | |
| log(:b, "Rehashing FileVaults login localizations.") | |
| efires = '/System/Library/Caches/com.apple.corestorage/EFILoginLocalizations/*.efires' | |
| FileUtils.rm_f Dir.glob(efires) | |
| (log(:b, "Waiting for system to rehash."); sleep(5)) while Dir.glob(efires).empty? | |
| log(:b, "Rehash of FileVaults login localizations complete.") | |
| end | |
| end | |
| def log(level, message = fortune) | |
| # Shortcuts for common levels. | |
| case level | |
| when :e; level = 'error' ; | |
| when :i; level = 'install'; | |
| when :u; level = 'update' ; | |
| when :r; level = 'remove' ; | |
| when :a; level = 'user' ; | |
| when :d; level = 'daemon' ; | |
| when :b; return false unless $config[:debug]; level = 'debug'; | |
| end | |
| # Set the time and then append to the log file. | |
| time = Time.now.strftime('%y:%m:%d:%H:%M:%S') | |
| open($config[:logfile_path], 'a') { |f| f.puts time + " [#{level.capitalize}] " + message } | |
| end | |
| def user | |
| File.chmod(0664, $config[:logfile_path]) unless File.stat($config[:logfile_path]).mode.to_s(8)[3..5].to_i == 664 | |
| # Fancy loading message to build anticipation. | |
| print " Looking into my crystal ball \r" | |
| sleep 0.5 | |
| print " Looking into my crystal ball. \r" | |
| sleep 0.5 | |
| print " Looking into my crystal ball.. \r" | |
| sleep 0.5 | |
| puts "Looking into my crystal ball... " | |
| # Set our new fortune and log it. | |
| system '/usr/bin/defaults', 'write', '/Library/Preferences/com.apple.loginwindow', 'LoginwindowText', '-string', fortune | |
| log(:a) | |
| efires_rehash | |
| # Tell us our new fortune if we want to know. | |
| puts "#{Color.cyan} ~> #{fortune}#{Color.reset}" if $config[:tell] | |
| puts "Your login fortune has been set." | |
| exit | |
| end | |
| def daemon | |
| # Let's make sure launchd has the right daemon plist. | |
| unless `defaults read #{$config[:daemon_path]} DaemonVersion 2> /dev/null`.to_i == $config[:daemon_ver] | |
| log(:e, "Daemon caller version miss match. Reinstall the script.") | |
| exit | |
| end | |
| # Log that the daemon started. | |
| log(:d, "Starting the login fortune daemon with a refresh of #{$config[:interval]} seconds.") | |
| # Our daemon is actually a loop, because launchd isn't reliable. | |
| loop do | |
| # Delete the log file if it gets too large. | |
| lines = `wc -l #{$config[:logfile_path]}`.split[0].to_i | |
| File.delete($config[:logfile_path]) if lines > $config[:log_limit] | |
| # Set the new login fortune and log the change. | |
| system '/usr/bin/defaults', 'write', '/Library/Preferences/com.apple.loginwindow', 'LoginwindowText', '-string', fortune(true) | |
| log(:d) | |
| efires_rehash | |
| # Now we sleep untill we're ready to run again. | |
| sleep $config[:interval] | |
| end | |
| end | |
| def install | |
| if $config[:current_path] == $config[:script_path] | |
| puts 'Login Fortune has already been installed.' | |
| log(:e, 'Login Fortune has already been installed.') | |
| exit | |
| end | |
| print "You are about to install Login Fortune on your system. This will make this script available as a system wide command and setup a daemon." | |
| print "#{Color.magenta} [Press enter to continue]#{Color.reset}" | |
| $stdin.noecho(&:gets) | |
| puts | |
| print " Installing\r" | |
| sleep 0.5 | |
| FileUtils.rm_f $config[:script_path] | |
| FileUtils.rm_f $config[:daemon_path] | |
| FileUtils.cp $config[:current_path], $config[:script_path] | |
| print " Installing.\r" | |
| sleep 0.5 | |
| file = <<EOF | |
| <?xml version="1.0" encoding="UTF-8"?> | |
| <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
| <plist version="1.0"> | |
| <dict> | |
| <key>DaemonVersion</key> | |
| <integer>2</integer> | |
| <key>Label</key> | |
| <string>com.darkgem.loginfortune</string> | |
| <key>ProgramArguments</key> | |
| <array> | |
| <string>/usr/bin/env</string> | |
| <string>ruby</string> | |
| <string>#{$config[:script_path]}</string> | |
| <string>--daemon</string> | |
| </array> | |
| <key>StandardOutPath</key> | |
| <string>#{$config[:logfile_path]}</string> | |
| <key>StandardErrorPath</key> | |
| <string>#{$config[:logfile_path]}</string> | |
| <key>KeepAlive</key> | |
| <true/> | |
| </dict> | |
| </plist> | |
| EOF | |
| File.open($config[:daemon_path], 'w') { |f| f.puts file } | |
| `plutil -convert binary1 #{$config[:daemon_path]}` | |
| unless File.exists?($config[:script_path]) or File.exists?($config[:daemon_path]) | |
| puts "#{Color.red}[Error]#{Color.reset} The install failed! Could not write to:" | |
| puts " #{$config[:script_path]}" unless File.exists?($config[:script_path]) | |
| puts " #{$config[:daemon_path]}" unless File.exists?($config[:daemon_path]) | |
| exit 1 | |
| end | |
| log(:i, 'Script and daemon have been installed.') | |
| print " Installing..\r" | |
| sleep 0.5 | |
| File.chmod(0644, $config[:daemon_path]) | |
| File.chown(0, 0, $config[:daemon_path]) | |
| File.chmod(0664, $config[:logfile_path]) | |
| print " Installing...\r" | |
| sleep 0.5 | |
| `launchctl load -w '#{$config[:daemon_path]}' > /dev/null 2>&1` | |
| `launchctl start com.darkgem.loginfortune > /dev/null 2>&1` | |
| log(:i,'Starting daemon.') | |
| puts "Done! " | |
| exit | |
| end | |
| def update | |
| acquire_gems('uri', 'open-uri') | |
| print "You are about to update Login Fortune." | |
| print "#{Color.magenta} [Press enter to continue]#{Color.reset}" | |
| $stdin.noecho(&:gets) | |
| puts | |
| ## Ping Server | |
| print " Checking connection...\r" | |
| sleep 0.7 | |
| host = URI.parse($config[:update_path]).host | |
| abort("#{Color.red}[Error]#{Color.reset} Could not connect to '#{host}'.") unless system("ping -q -c 1 #{host} > /dev/null 2>&1") | |
| print " \r" | |
| ## Download File | |
| print " Downloading file...\r" | |
| sleep 0.5 | |
| log(:u, 'Downloading update file.') | |
| file = open($config[:update_path]).read | |
| print " \r" | |
| abort("#{Color.red}[Error]#{Color.reset} Could not download '#{$config[:update_path]}'.") if file.empty? || file.nil? | |
| ## Compare Versions | |
| print " Checking version...\r" | |
| sleep 0.7 | |
| log(:u, 'Checking version of new file.') | |
| remote_version = file.lines.grep(/^\$version = /) | |
| remote_version = "0.0" if remote_version.empty? | |
| remote_version = remote_version[0].strip.split('= ')[-1].to_f | |
| print " \r" | |
| if remote_version <= $version | |
| log(:u, 'Login Fortune is already up to date.') | |
| puts "Login Fortune is already up to date." | |
| exit | |
| end | |
| ## Overwrite Old Files | |
| print " Overwriting files...\r" | |
| sleep 0.7 | |
| log(:u, 'Installing new update.') | |
| open($config[:script_path], 'w') { |f| f.puts file } | |
| print " \r" | |
| puts "Login fortune has been updated." | |
| log(:u, "Login Fortune has been updated from version #{$version} to #{remote_version} successfully.") | |
| exit | |
| end | |
| def remove | |
| print "You are about to remove Login Fortune from your system." | |
| print "#{Color.magenta} [Press enter to continue]#{Color.reset}" | |
| $stdin.noecho(&:gets) | |
| log(:r, 'Starting to remove self from system.') | |
| puts | |
| print " Removing\r" | |
| sleep 0.5 | |
| log(:r, 'Stopping daemon.') | |
| `launchctl unload '#{$config[:daemon_path]}'` | |
| print " Removing.\r" | |
| sleep 0.5 | |
| log(:r, 'Deleting script and daemon.') | |
| FileUtils.rm_f $config[:script_path] | |
| FileUtils.rm_f $config[:daemon_path] | |
| print " Removing..\r" | |
| sleep 0.5 | |
| log(:r, 'Removing login window text.') | |
| `/usr/bin/defaults delete /Library/Preferences/com.apple.loginwindow LoginwindowText 2> /dev/null` | |
| print " Removing...\r" | |
| sleep 0.5 | |
| if File.exists?($config[:script_path]) or File.exists?($config[:daemon_path]) | |
| log(:e, 'Failed to remove self from system.') | |
| puts "#{Color.red}[Error]#{Color.reset} The uninstall failed! Could not remove:" | |
| puts " #{$config[:script_path]}" if File.exists?($config[:script_path]) | |
| puts " #{$config[:daemon_path]}" if File.exists?($config[:daemon_path]) | |
| exit 1 | |
| end | |
| log(:r, 'Successfully removed self from system.') | |
| puts "All done! Login Fortune has been removed from your system, but you can always grab another copy from 'https://gist.github.com/redperadot/8547413'. Thanks for trying it -- redperadot" | |
| exit | |
| end | |
| ## Options | |
| OptionParser.new do |opts| | |
| opts.banner = "Usage: #{$0} [FLAG]" | |
| opts.on("-h", "--help", "Show this message") do | |
| puts opts | |
| exit | |
| end | |
| opts.on("-v", "--version", "Print the version and exit.") do | |
| puts $version | |
| exit | |
| end | |
| opts.on("-c", "--current", "Print the current login message.") do | |
| current = `/usr/bin/defaults read /Library/Preferences/com.apple.loginwindow LoginwindowText 2> /dev/null` | |
| puts current if $?.exitstatus == 0 | |
| exit | |
| end | |
| opts.on("-t", "--tell", "Print the new fortune when running from the command line.") do | |
| $config[:tell] = true | |
| end | |
| opts.on("-i", "--install", "Install script as a command.") do | |
| sys_check | |
| install | |
| end | |
| opts.on("-u", "--update", "Update this script from github.") do | |
| sys_check | |
| update | |
| end | |
| opts.on("-r", "--remove", "Remove the script from your system.") do | |
| sys_check | |
| remove | |
| end | |
| opts.on("-d", "--daemon", "Start the daemon. For use by launchd.") do | |
| sys_check | |
| daemon | |
| end | |
| end.parse! | |
| sys_check | |
| user |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment