Skip to content

Instantly share code, notes, and snippets.

@redperadot
Last active January 4, 2016 01:19
Show Gist options
  • Select an option

  • Save redperadot/8547413 to your computer and use it in GitHub Desktop.

Select an option

Save redperadot/8547413 to your computer and use it in GitHub Desktop.
Change the OS X login window message to a fortune.
#!/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