launchdr 3
Sign up to get free protection for your applications and to get access to all the features.
- data/.manifest +7 -0
- data/README.markdown +66 -0
- data/Rakefile.rb +40 -0
- data/launchdr.gemspec +43 -0
- data/lib/launchdr.rb +36 -0
- data/lib/launchdr/launchd.rb +43 -0
- data/lib/launchdr/property_list.rb +38 -0
- data/lib/launchdr/task.rb +33 -0
- metadata +146 -0
data/.manifest
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
LaunchDoctor
|
2
|
+
============
|
3
|
+
I've got a PhD in [launchd][], and I'm not afraid to use it!
|
4
|
+
|
5
|
+
[launchd]: <http://en.wikipedia.org/wiki/Launchd> "launchd on Wikipedia"
|
6
|
+
|
7
|
+
Usage
|
8
|
+
-----
|
9
|
+
LaunchDoctor is really simple. It is essentially an easy interface to
|
10
|
+
[launchd][]'s [property list][plist] files.
|
11
|
+
|
12
|
+
There are three ways to use LaunchDoctor - the simplest being directly
|
13
|
+
creating property lists and treating them as hashes:
|
14
|
+
|
15
|
+
require 'launchdr'
|
16
|
+
plist = LaunchDr::Launchd.new "name.elliottcable.launchdr.test"
|
17
|
+
plist[:program_arguments] = ['/Applications/Calculator.app/Contents/MacOS/Calculator']
|
18
|
+
plist.dump LaunchDr::Launchd::Paths[:user_agent]
|
19
|
+
|
20
|
+
The second is to use the common block idiom, provided by the `LaunchDr()`
|
21
|
+
method:
|
22
|
+
|
23
|
+
require 'launchdr'
|
24
|
+
LaunchDr "name.elliottcable.launchdr.test" do |plist|
|
25
|
+
plist[:program_arguments] = ['/Applications/Calculator.app/Contents/MacOS/Calculator']
|
26
|
+
end
|
27
|
+
|
28
|
+
LaunchDoctor will automatically add the property list to `launchctl`, and then
|
29
|
+
start it running. Once you run the above snippet, the target will immediately
|
30
|
+
be launched for the first time.
|
31
|
+
|
32
|
+
LaunchDoctor can write (`dump`) the property lists to any place on your disk,
|
33
|
+
but the idiom method assumes you're going to want to use one of the
|
34
|
+
directories that launchd checks for property lists. These are stored in the
|
35
|
+
`Launchd::Paths` array. The default is to place it in the user-owned agents
|
36
|
+
directory at `~/Library/LaunchAgents`.
|
37
|
+
|
38
|
+
LaunchDoctor also preforms some 'prettification' on the keys provided by
|
39
|
+
launchd's property list structure. All of the keys on the
|
40
|
+
[`launchd.plist` manpage][manpage] are available, but they can also be used as
|
41
|
+
true 'Ruby-ish' symbol keys. All of the following are legal:
|
42
|
+
|
43
|
+
plist["UserName"] = "elliottcable"
|
44
|
+
plist[:UserName] = "elliottcable"
|
45
|
+
plist[:user_name] = "elliottcable"
|
46
|
+
|
47
|
+
Finally, you can use the Rake task interface to the last method. It simply
|
48
|
+
wraps the last method inside a rake task. This method is really great if you
|
49
|
+
want to provide a way to let users make your gem's binary run all the time:
|
50
|
+
|
51
|
+
require 'launchdr/task'
|
52
|
+
LaunchDr::Task.new :launchd, :bin => 'jello', :arguments => ['-D', 'shortener', 'grabup']
|
53
|
+
|
54
|
+
This isn't very flexible, but it's not very complicated either. If you need
|
55
|
+
more control over the plist, just use the second method inside a `task` block.
|
56
|
+
This method defaults to making your gem's binary `run_at_load` and be
|
57
|
+
`keep_alive` as well, so it won't die.
|
58
|
+
|
59
|
+
[plist]: <http://en.wikipedia.org/wiki/Property_list> "Property list on Wikipedia"
|
60
|
+
[manpage]: <http://developer.apple.com/DOCUMENTATION/DARWIN/Reference/ManPages/man5/launchd.plist.5.html> "Mac OS X Manual Page for launchd.plist(5)"
|
61
|
+
[rake]: <http://rake.rubyforge.org/> "Rake's RDocs"
|
62
|
+
|
63
|
+
Requirements
|
64
|
+
------------
|
65
|
+
- Plist - `gem install plist`
|
66
|
+
- Ruby Facets - `gem install facets`
|
data/Rakefile.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
($:.unshift File.expand_path(File.join( File.dirname(__FILE__), 'lib' ))).uniq!
|
2
|
+
require 'launchdr'
|
3
|
+
|
4
|
+
# =======================
|
5
|
+
# = Gem packaging tasks =
|
6
|
+
# =======================
|
7
|
+
begin
|
8
|
+
require 'echoe'
|
9
|
+
|
10
|
+
task :package => :'package:package'
|
11
|
+
task :install => :'package:install'
|
12
|
+
task :manifest => :'package:manifest'
|
13
|
+
task :clobber => :'package:clobber'
|
14
|
+
namespace :package do
|
15
|
+
Echoe.new('launchdr', LaunchDr::Version) do |g|
|
16
|
+
g.author = ['elliottcable']
|
17
|
+
g.email = ['launchdr@elliottcable.com']
|
18
|
+
g.summary = "One stop shop for launchd property list creation. The doctor is *in*!"
|
19
|
+
g.url = 'http://github.com/elliottcable/launchdr'
|
20
|
+
g.dependencies = ['plist', 'facets', 'uuid']
|
21
|
+
g.development_dependencies = ['echoe >= 3.0.2']
|
22
|
+
g.manifest_name = '.manifest' # I don't want this showing up <,<
|
23
|
+
g.retain_gemspec = true # perfect for GitHub
|
24
|
+
g.rakefile_name = 'Rakefile.rb' # It's a Ruby file, why not have .rb?
|
25
|
+
g.ignore_pattern = /^\.git\/|\.gemspec/
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'tests packaged files to ensure they are all present'
|
29
|
+
task :verify => :package do
|
30
|
+
# An error message will be displayed if files are missing
|
31
|
+
if system %(ruby -e "require 'rubygems'; require 'pkg/launchdr-#{LaunchDr::Version}/lib/launchdr'")
|
32
|
+
puts "\nThe library files are present"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue LoadError
|
38
|
+
desc 'You need the `echoe` gem to package LaunchDr'
|
39
|
+
task :package
|
40
|
+
end
|
data/launchdr.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{launchdr}
|
5
|
+
s.version = "3"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["elliottcable"]
|
9
|
+
s.date = %q{2009-02-11}
|
10
|
+
s.description = %q{One stop shop for launchd property list creation. The doctor is *in*!}
|
11
|
+
s.email = ["launchdr@elliottcable.com"]
|
12
|
+
s.extra_rdoc_files = ["lib/launchdr/launchd.rb", "lib/launchdr/property_list.rb", "lib/launchdr/task.rb", "lib/launchdr.rb", "README.markdown"]
|
13
|
+
s.files = ["lib/launchdr/launchd.rb", "lib/launchdr/property_list.rb", "lib/launchdr/task.rb", "lib/launchdr.rb", "Rakefile.rb", "README.markdown", ".manifest", "launchdr.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/elliottcable/launchdr}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Launchdr", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{launchdr}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{One stop shop for launchd property list creation. The doctor is *in*!}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<plist>, [">= 0"])
|
28
|
+
s.add_runtime_dependency(%q<facets>, [">= 0"])
|
29
|
+
s.add_runtime_dependency(%q<uuid>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<echoe>, [">= 0", "= 3.0.2"])
|
31
|
+
else
|
32
|
+
s.add_dependency(%q<plist>, [">= 0"])
|
33
|
+
s.add_dependency(%q<facets>, [">= 0"])
|
34
|
+
s.add_dependency(%q<uuid>, [">= 0"])
|
35
|
+
s.add_dependency(%q<echoe>, [">= 0", "= 3.0.2"])
|
36
|
+
end
|
37
|
+
else
|
38
|
+
s.add_dependency(%q<plist>, [">= 0"])
|
39
|
+
s.add_dependency(%q<facets>, [">= 0"])
|
40
|
+
s.add_dependency(%q<uuid>, [">= 0"])
|
41
|
+
s.add_dependency(%q<echoe>, [">= 0", "= 3.0.2"])
|
42
|
+
end
|
43
|
+
end
|
data/lib/launchdr.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'launchdr/property_list'
|
2
|
+
require 'launchdr/launchd'
|
3
|
+
|
4
|
+
|
5
|
+
def LaunchDr label, opts = {}
|
6
|
+
LaunchDr.create label, opts, &Proc.new
|
7
|
+
end
|
8
|
+
module LaunchDr
|
9
|
+
Version = 3
|
10
|
+
Options = Hash.new
|
11
|
+
|
12
|
+
def self.create label, opts = {}
|
13
|
+
plist = Launchd.new label
|
14
|
+
|
15
|
+
yield plist if block_given?
|
16
|
+
|
17
|
+
raise "You must run as a root user and allow_system_agents! if you wish to preform the dangerous act of writing to #{Launchd::Paths[:system_agent]}!" unless !(opts[:type] == :system_agent) or Options[:system_agents_allowed]
|
18
|
+
raise "You must run as a root user, allow_system_agents!, *and* allow_system_daemons! if you wish to preform the very dangerous act of writing to #{Launchd::Paths[:system_daemon]}!" unless !(opts[:type] == :system_daemon) or Options[:system_daemon_allowed]
|
19
|
+
path = Launchd::Paths[opts[:type] || :user_agent]
|
20
|
+
raise "Type error! Must be one of #{Launchd::Paths.keys.join(", ")}" unless path
|
21
|
+
|
22
|
+
plist.dump File.expand_path(path)
|
23
|
+
plist.load!
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.allow_system_agents!
|
27
|
+
Options[:system_agents_allowed] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.allow_system_daemons!
|
31
|
+
raise "You must allow System-owned agents to allow System-owned daemons!" unless Options[:system_agents_allowed]
|
32
|
+
Options[:system_daemon_allowed] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'facets/string'
|
2
|
+
|
3
|
+
module LaunchDr
|
4
|
+
|
5
|
+
class Launchd < PropertyList
|
6
|
+
Paths = {
|
7
|
+
:user_agent => "~/Library/LaunchAgents",
|
8
|
+
:agent => "/Library/LaunchAgents",
|
9
|
+
:daemon => "/Library/LaunchDaemons",
|
10
|
+
:system_agent => "/System/Library/LaunchAgents",
|
11
|
+
:system_daemon => "/System/Library/LaunchDaemons"
|
12
|
+
}
|
13
|
+
|
14
|
+
def [] key
|
15
|
+
super(key.to_s.titlecase.camelcase)
|
16
|
+
end
|
17
|
+
|
18
|
+
def []= key, value
|
19
|
+
super(key.to_s.titlecase.camelcase, value)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Adds the property list to `launchctl`'s indexes, and starts it (unless
|
23
|
+
# disabled).
|
24
|
+
def load!
|
25
|
+
system "launchctl load #{file}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Removes the propertly list from `launchctl`'s indexes.
|
29
|
+
def unload!
|
30
|
+
system "launchctl unload #{file}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Finds the plist file, if it's been created
|
34
|
+
def file
|
35
|
+
Paths.values.each do |dir|
|
36
|
+
file = File.expand_path(File.join(dir, self[:label] + '.plist'))
|
37
|
+
return file if File.file? file
|
38
|
+
end
|
39
|
+
raise "#{self[:label]} doesn't exist in any of the default locations. Did you #dump it?"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'plist'
|
2
|
+
require 'uuid'
|
3
|
+
|
4
|
+
module LaunchDr
|
5
|
+
|
6
|
+
class PropertyList
|
7
|
+
attr_reader :elements
|
8
|
+
|
9
|
+
def initialize label = nil, elements = {}
|
10
|
+
@elements = elements
|
11
|
+
@elements['Label'] = label || "rb.launchdr.#{UUID.new.generate}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def [] key
|
15
|
+
@elements[key.to_s]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []= key, value
|
19
|
+
@elements[key] = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_plist
|
23
|
+
@elements.to_plist
|
24
|
+
end
|
25
|
+
|
26
|
+
def dump path
|
27
|
+
out = File.new(File.expand_path(File.join(path, @elements['Label'] + '.plist')), 'w+')
|
28
|
+
out.puts Plist::Emit.dump @elements
|
29
|
+
out.close
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.load filename
|
33
|
+
elements = Plist::parse_xml File.new(File.expand_path filename, 'r')
|
34
|
+
new elements[:label], elements
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'launchdr'
|
2
|
+
|
3
|
+
module LaunchDr
|
4
|
+
|
5
|
+
module Task
|
6
|
+
def self.new name = :launchd, opts = {}
|
7
|
+
raise "`LaunchDr.task`'s options must include the name of the binary for your gem!" unless opts[:bin]
|
8
|
+
opts = {:desc => 'Creates a launchd property list for this gem', :arguments => []}.merge opts
|
9
|
+
|
10
|
+
desc opts[:desc] unless opts[:no_desc]
|
11
|
+
task name do
|
12
|
+
path = File.expand_path File.join('', opts[:bin])
|
13
|
+
unless File.file? path
|
14
|
+
path = File.expand_path File.join(Gem::default_bindir, opts[:bin])
|
15
|
+
unless File.file? path
|
16
|
+
path = File.expand_path %x[which "#{opts[:bin]}"].chomp
|
17
|
+
end
|
18
|
+
end
|
19
|
+
unless File.file? path
|
20
|
+
raise "** Unable to locate binary #{opts[:bin]}"
|
21
|
+
end
|
22
|
+
LaunchDr "rb.launchdr.#{opts[:bin]}" do |plist|
|
23
|
+
plist[:program_arguments] = [path, opts[:arguments]].flatten
|
24
|
+
plist[:keep_alive] = true
|
25
|
+
plist[:run_at_load] = true
|
26
|
+
end
|
27
|
+
puts "** `#{[path, opts[:arguments]].flatten.join(' ')}` will now be run on startup!"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: launchdr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: -124656629444251359
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 3
|
8
|
+
version: "3"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- elliottcable
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2009-02-11 00:00:00 -09:00
|
17
|
+
default_executable:
|
18
|
+
dependencies:
|
19
|
+
- !ruby/object:Gem::Dependency
|
20
|
+
name: plist
|
21
|
+
prerelease: false
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 134023568094289244
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: facets
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 134023568094289244
|
42
|
+
segments:
|
43
|
+
- 0
|
44
|
+
version: "0"
|
45
|
+
type: :runtime
|
46
|
+
version_requirements: *id002
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: uuid
|
49
|
+
prerelease: false
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 134023568094289244
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: echoe
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 134023568094289244
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
- - "="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 1556383981505405372
|
76
|
+
segments:
|
77
|
+
- 3
|
78
|
+
- 0
|
79
|
+
- 2
|
80
|
+
version: 3.0.2
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id004
|
83
|
+
description: One stop shop for launchd property list creation. The doctor is *in*!
|
84
|
+
email:
|
85
|
+
- launchdr@elliottcable.com
|
86
|
+
executables: []
|
87
|
+
|
88
|
+
extensions: []
|
89
|
+
|
90
|
+
extra_rdoc_files:
|
91
|
+
- lib/launchdr/launchd.rb
|
92
|
+
- lib/launchdr/property_list.rb
|
93
|
+
- lib/launchdr/task.rb
|
94
|
+
- lib/launchdr.rb
|
95
|
+
- README.markdown
|
96
|
+
files:
|
97
|
+
- lib/launchdr/launchd.rb
|
98
|
+
- lib/launchdr/property_list.rb
|
99
|
+
- lib/launchdr/task.rb
|
100
|
+
- lib/launchdr.rb
|
101
|
+
- Rakefile.rb
|
102
|
+
- README.markdown
|
103
|
+
- .manifest
|
104
|
+
- launchdr.gemspec
|
105
|
+
has_rdoc: true
|
106
|
+
homepage: http://github.com/elliottcable/launchdr
|
107
|
+
licenses: []
|
108
|
+
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options:
|
111
|
+
- --line-numbers
|
112
|
+
- --inline-source
|
113
|
+
- --title
|
114
|
+
- Launchdr
|
115
|
+
- --main
|
116
|
+
- README.markdown
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
hash: 134023568094289244
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
version: "0"
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
hash: 1922007725579590311
|
134
|
+
segments:
|
135
|
+
- 1
|
136
|
+
- 2
|
137
|
+
version: "1.2"
|
138
|
+
requirements: []
|
139
|
+
|
140
|
+
rubyforge_project: launchdr
|
141
|
+
rubygems_version: 1.3.7
|
142
|
+
signing_key:
|
143
|
+
specification_version: 2
|
144
|
+
summary: One stop shop for launchd property list creation. The doctor is *in*!
|
145
|
+
test_files: []
|
146
|
+
|