Created
March 7, 2013 11:56
-
-
Save mitsuhiko/5107546 to your computer and use it in GitHub Desktop.
This file contains 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 | |
require 'optparse' | |
require 'fileutils' | |
class Lunchy | |
VERSION = '0.7.0' | |
def start(params) | |
raise ArgumentError, "start [-wF] [name]" if params.empty? | |
with_match params[0] do |name, path| | |
execute("launchctl load #{force}#{write}#{path.inspect}") | |
puts "started #{name}" | |
end | |
end | |
def stop(params) | |
raise ArgumentError, "stop [-w] [name]" if params.empty? | |
with_match params[0] do |name, path| | |
execute("launchctl unload #{write}#{path.inspect}") | |
puts "stopped #{name}" | |
end | |
end | |
def restart(params) | |
stop(params.dup) | |
start(params.dup) | |
end | |
def status(params) | |
pattern = params[0] | |
cmd = "launchctl list" | |
unless verbose? | |
agents = plists.keys.map { |k| "-e \"#{k}\"" }.join(" ") | |
cmd << " | grep -i #{agents}" | |
end | |
cmd.gsub!('.','\.') | |
cmd << " | grep -i \"#{pattern}\"" if pattern | |
execute(cmd) | |
end | |
def ls(params) | |
agents = plists.keys | |
agents = agents.grep(/#{params[0]}/) if !params.empty? | |
if long | |
puts agents.map { |agent| plists[agent] }.sort.join("\n") | |
else | |
puts agents.sort.join("\n") | |
end | |
end | |
alias_method :list, :ls | |
def install(params) | |
raise ArgumentError, "install [file]" if params.empty? | |
filename = params[0] | |
%w(~/Library/LaunchAgents /Library/LaunchAgents).each do |dir| | |
if File.exist?(File.expand_path(dir)) | |
FileUtils.cp filename, File.join(File.expand_path(dir), File.basename(filename)) | |
return puts "#{filename} installed to #{dir}" | |
end | |
end | |
end | |
def show(params) | |
raise ArgumentError, "show [name]" if params.empty? | |
with_match params[0] do |_, path| | |
puts IO.read(path) | |
end | |
end | |
def edit(params) | |
raise ArgumentError, "edit [name]" if params.empty? | |
with_match params[0] do |_, path| | |
editor = ENV['EDITOR'] | |
if editor.nil? | |
raise 'EDITOR environment variable is not set' | |
else | |
execute("#{editor} #{path.inspect} > `tty`") | |
end | |
end | |
end | |
private | |
def force | |
CONFIG[:force] and '-F ' | |
end | |
def write | |
CONFIG[:write] and '-w ' | |
end | |
def long | |
CONFIG[:long] | |
end | |
def with_match name | |
files = plists.select {|k,_| k =~ /#{name}/i } | |
files = Hash[files] if files.is_a?(Array) # ruby 1.8 | |
if files.size > 1 | |
puts "Multiple daemons found matching '#{name}'. You need to be more specific. Matches found are:\n#{files.keys.join("\n")}" | |
elsif files.empty? | |
puts "No daemon found matching '#{name}'" unless name | |
else | |
yield(*files.to_a.first) | |
end | |
end | |
def execute(cmd) | |
puts "Executing: #{cmd}" if verbose? | |
emitted = `#{cmd}` | |
puts emitted unless emitted.empty? | |
end | |
def plists | |
@plists ||= begin | |
plists = {} | |
dirs.each do |dir| | |
Dir["#{File.expand_path(dir)}/*.plist"].inject(plists) do |memo, filename| | |
memo[File.basename(filename, ".plist")] = filename; memo | |
end | |
end | |
plists | |
end | |
end | |
def dirs | |
result = %w(/Library/LaunchAgents ~/Library/LaunchAgents) | |
result.push('/Library/LaunchDaemons', '/System/Library/LaunchDaemons') if root? | |
result | |
end | |
def root? | |
Process.euid == 0 | |
end | |
def verbose? | |
CONFIG[:verbose] | |
end | |
end | |
CONFIG = {} | |
OPERATIONS = %w(start stop restart ls list status install show edit) | |
option_parser = OptionParser.new do |opts| | |
opts.banner = "Lunchy #{Lunchy::VERSION}, the friendly launchctl wrapper\n" \ | |
"Usage: #{File.basename(__FILE__)} [#{OPERATIONS.join('|')}] [options]" | |
opts.on("-F", "--force", "Force start (disabled) agents") do |verbose| | |
CONFIG[:force] = true | |
end | |
opts.on("-v", "--verbose", "Show command executions") do |verbose| | |
CONFIG[:verbose] = true | |
end | |
opts.on("-w", "--write", "Persist command") do |verbose| | |
CONFIG[:write] = true | |
end | |
opts.on("-l", "--long", "Display absolute paths when listing agents") do | |
CONFIG[:long] = true | |
end | |
opts.separator <<-EOS | |
Supported commands: | |
ls [-l] [pattern] Show the list of installed agents, with optional [pattern] filter | |
list [-l] [pattern] Alias for 'ls' | |
start [-wF] [pattern] Start the first agent matching [pattern] | |
stop [-w] [pattern] Stop the first agent matching [pattern] | |
restart [pattern] Stop and start the first agent matching [pattern] | |
status [pattern] Show the PID and label for all agents, with optional [pattern] filter | |
install [file] Install [file] to ~/Library/LaunchAgents or /Library/LaunchAgents (whichever it finds first) | |
show [pattern] Show the contents of the launchctl daemon file | |
edit [pattern] Open the launchctl daemon file in the default editor (EDITOR environment variable) | |
-w will persist the start/stop command so the agent will load on startup or never load, respectively. | |
-l will display absolute paths of the launchctl daemon files when showing list of installed agents. | |
Example: | |
lunchy ls | |
lunchy ls -l nginx | |
lunchy start -w redis | |
lunchy stop mongo | |
lunchy status mysql | |
lunchy install /usr/local/Cellar/redis/2.2.2/io.redis.redis-server.plist | |
lunchy show redis | |
lunchy edit mongo | |
Note: if you run lunchy as root, you can manage daemons in /Library/LaunchDaemons also. | |
EOS | |
end | |
option_parser.parse! | |
op = ARGV.shift | |
if OPERATIONS.include?(op) | |
begin | |
Lunchy.new.send(op.to_sym, ARGV) | |
rescue ArgumentError => ex | |
puts ex.message | |
rescue Exception => e | |
puts "Uh oh, I didn't expect this:" | |
puts e.message | |
puts e.backtrace.join("\n") | |
end | |
else | |
puts option_parser.help | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment