launchd_tools 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +108 -0
- data/Rakefile +1 -0
- data/TODO +3 -0
- data/bin/cmd2launchd +5 -0
- data/bin/launchd2cmd +5 -0
- data/launchd_tools.gemspec +24 -0
- data/lib/launchd_tools/cmd2launchd_cli.rb +38 -0
- data/lib/launchd_tools/environment_parser.rb +31 -0
- data/lib/launchd_tools/environment_variables.rb +17 -0
- data/lib/launchd_tools/launchd2cmd_cli.rb +82 -0
- data/lib/launchd_tools/launchd_job.rb +45 -0
- data/lib/launchd_tools/launchd_plist.rb +44 -0
- data/lib/launchd_tools/path.rb +47 -0
- data/lib/launchd_tools/path_content.rb +24 -0
- data/lib/launchd_tools/path_parser.rb +34 -0
- data/lib/launchd_tools/program_args_parser.rb +21 -0
- data/lib/launchd_tools/version.rb +3 -0
- data/lib/launchd_tools.rb +5 -0
- data/spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist +21 -0
- data/spec/fixtures/LaunchAgents/com.apple.storeagent.plist +50 -0
- data/spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist +0 -0
- data/spec/fixtures/LaunchDaemons/inaccessible.plist +41 -0
- data/spec/fixtures/LaunchDaemons/org.apache.httpd.plist +25 -0
- data/spec/integration/cmd2launchd_spec.rb +25 -0
- data/spec/integration/launchd2cmd_spec.rb +82 -0
- data/spec/spec_helper.rb +17 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7a74ee78c83f4de133e4d2ae13f21ead304210ef
|
4
|
+
data.tar.gz: ca34827f49bf559be77e8c250471cfa7aa6c2eca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 03f429b7f92e27e6aa3db2ab370ae418d1a1d0a8d53f18edbb15d926e42b2e4e36a4f2088c919b8733e86eb89abd8808c089b6bc80b034a532000bbc6398edea
|
7
|
+
data.tar.gz: 65c19da4950484a251c349c244e57cad2b76f115e5650f65eb747de9441c6bcf5545c14645cdbf29001f477c0b93ef9feb907fa86aab2f86f3e6d7ea3c770630
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kyle Crawford
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Launchd Tools
|
2
|
+
|
3
|
+
Convert from command to launchd plist or from launchd plist to command
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
$ gem install launchd_tools
|
8
|
+
|
9
|
+
## Command Usage
|
10
|
+
|
11
|
+
Output the command and arguments from a launchd plist:
|
12
|
+
|
13
|
+
$ launchd2cmd /path/to/launchd.plist [another/launchd.plist]
|
14
|
+
|
15
|
+
Create a launchd plist from a command and arguments:
|
16
|
+
|
17
|
+
$ cmd2launchd /path/to/daemon [arg] [-option]
|
18
|
+
|
19
|
+
## Examples
|
20
|
+
|
21
|
+
####launchd2cmd with multiple directories:
|
22
|
+
|
23
|
+
$ launchd2cmd /System/Library/LaunchAgents /System/Library/LaunchDaemons
|
24
|
+
...
|
25
|
+
# /System/Library/LaunchDaemons/tftp.plist
|
26
|
+
/usr/libexec/tftpd -i /private/tftpboot
|
27
|
+
|
28
|
+
####launchd2cmd wildcards/globbing:
|
29
|
+
|
30
|
+
$ launchd2cmd /Library/Launch*/com.googlecode.munki.*
|
31
|
+
...
|
32
|
+
# /Library/LaunchDaemons/com.googlecode.munki.managedsoftwareupdate-manualcheck.plist
|
33
|
+
/usr/local/munki/supervisor --timeout 43200 -- /usr/local/munki/managedsoftwareupdate --manualcheck
|
34
|
+
|
35
|
+
####cmd2launchd creating a launchd plist skeleton:
|
36
|
+
|
37
|
+
$ cmd2launchd /usr/local/bin/daemond -d --mode foreground
|
38
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
39
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
40
|
+
<plist version='1.0'>
|
41
|
+
<dict>
|
42
|
+
<key>ProgramArguments</key>
|
43
|
+
<array>
|
44
|
+
<string>/usr/local/bin/daemond</string>
|
45
|
+
<string>-d</string>
|
46
|
+
<string>--mode</string>
|
47
|
+
<string>foreground</string>
|
48
|
+
</array>
|
49
|
+
</dict>
|
50
|
+
</plist>
|
51
|
+
|
52
|
+
## Usage from Ruby
|
53
|
+
|
54
|
+
### Parsing a launchd plist
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
>> require 'launchd_tools/path'
|
58
|
+
=> false
|
59
|
+
>> sshd_plist_path =
|
60
|
+
LaunchdTools::Path.new("/System/Library/LaunchDaemons/ssh.plist")
|
61
|
+
=> #<LaunchdTools::Path:0x007f9a7b1a5f50
|
62
|
+
@path="/System/Library/LaunchDaemons/ssh.plist">
|
63
|
+
>> sshd_job = sshd_plist_path.parse
|
64
|
+
=> #<LaunchdTools::LaunchdJob:0x007f9a7aa6cc70
|
65
|
+
@attributes={"EnvironmentVariables"=>[],
|
66
|
+
"ProgramArguments"=>["/usr/sbin/sshd", "-i"]}>
|
67
|
+
>> sshd_job.program_arguments
|
68
|
+
=> ["/usr/sbin/sshd", "-i"]
|
69
|
+
```
|
70
|
+
|
71
|
+
### Creating a launchd plist
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
>> require 'launchd_tools/launchd_plist'
|
75
|
+
=> true
|
76
|
+
>> plist = LaunchdTools::LaunchdPlist.new
|
77
|
+
=> #<LaunchdTools::LaunchdPlist:0x007fde1c434c78 @doc=<UNDEFINED> ...
|
78
|
+
</>, @program_args_array_element=<array/>>
|
79
|
+
>> plist.add_program_args(["/usr/local/bin/daemon", "-f", "-o",
|
80
|
+
"/var/log/daemon.log"])
|
81
|
+
=> ["/usr/local/bin/daemon", "-f", "-o", "/var/log/daemon.log"]
|
82
|
+
>> puts plist.to_s
|
83
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
84
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
85
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
86
|
+
<plist version='1.0'>
|
87
|
+
<dict>
|
88
|
+
<key>ProgramArguments</key>
|
89
|
+
<array>
|
90
|
+
<string>/usr/local/bin/daemon</string>
|
91
|
+
<string>-f</string>
|
92
|
+
<string>-o</string>
|
93
|
+
<string>/var/log/daemon.log</string>
|
94
|
+
</array>
|
95
|
+
</dict>
|
96
|
+
</plist>
|
97
|
+
```
|
98
|
+
|
99
|
+
|
100
|
+
## Contributing
|
101
|
+
|
102
|
+
1. Fork it
|
103
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
104
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
105
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
106
|
+
5. Create new Pull Request
|
107
|
+
|
108
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO
ADDED
data/bin/cmd2launchd
ADDED
data/bin/launchd2cmd
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'launchd_tools/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "launchd_tools"
|
8
|
+
spec.version = LaunchdTools::VERSION
|
9
|
+
spec.authors = ["Kyle Crawford"]
|
10
|
+
spec.email = ["kcrwfrd@gmail.com"]
|
11
|
+
spec.description = %q{Provides tools for converting from command line arguments to a formatted launchd plist and vice versa}
|
12
|
+
spec.summary = %q{launchd tools convert from command to launchd and launchd to command}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'launchd_tools/launchd_plist'
|
2
|
+
|
3
|
+
module LaunchdTools
|
4
|
+
class Cmd2LaunchdCli
|
5
|
+
|
6
|
+
attr_reader :args
|
7
|
+
def initialize(args)
|
8
|
+
if args.empty?
|
9
|
+
show_usage
|
10
|
+
exit 0
|
11
|
+
elsif args.length == 1
|
12
|
+
case args.first
|
13
|
+
when "--help"
|
14
|
+
show_usage
|
15
|
+
exit 0
|
16
|
+
when "-h"
|
17
|
+
show_usage
|
18
|
+
exit 0
|
19
|
+
when "--version"
|
20
|
+
puts LaunchdTools::VERSION
|
21
|
+
exit 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@args = args
|
25
|
+
end
|
26
|
+
|
27
|
+
def show_usage
|
28
|
+
puts "Usage: #{$0} command [arg1] [arg2]"
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
plist = LaunchdPlist.new
|
33
|
+
plist.add_program_args(args)
|
34
|
+
puts plist.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
include REXML
|
3
|
+
module LaunchdTools
|
4
|
+
class EnvironmentParser
|
5
|
+
attr_reader :xml_doc, :element
|
6
|
+
def initialize(xml_doc)
|
7
|
+
@xml_doc = xml_doc
|
8
|
+
end
|
9
|
+
|
10
|
+
def element
|
11
|
+
@element ||= REXML::XPath.first(xml_doc, "plist/dict/key[text()='EnvironmentVariables']/following-sibling::dict")
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_env
|
15
|
+
env = {}
|
16
|
+
REXML::XPath.match(element, 'key').each do |environment_key|
|
17
|
+
env[environment_key.text] = environment_key.next_sibling.next_sibling.text
|
18
|
+
end
|
19
|
+
env
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse
|
23
|
+
if element
|
24
|
+
extract_env
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module LaunchdTools
|
2
|
+
class EnvironmentVariables
|
3
|
+
attr_reader :variables
|
4
|
+
def initialize(variables = {})
|
5
|
+
@variables = variables
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_a
|
9
|
+
env_items = []
|
10
|
+
variables.each do |key,value|
|
11
|
+
env_items << "#{key}=#{value}"
|
12
|
+
end
|
13
|
+
env_items
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'launchd_tools/path'
|
2
|
+
require 'optparse'
|
3
|
+
module LaunchdTools
|
4
|
+
class Launchd2CmdCli
|
5
|
+
attr_reader :paths, :args
|
6
|
+
def initialize(args)
|
7
|
+
showing_help = false
|
8
|
+
opt_parser = OptionParser.new do |opt|
|
9
|
+
opt.banner = "Usage: #{$0} path/to/launchd.plist"
|
10
|
+
opt.separator ""
|
11
|
+
opt.separator "Options"
|
12
|
+
opt.on("--help", "-h", "Displays this help message") do
|
13
|
+
showing_help = true
|
14
|
+
end
|
15
|
+
opt.on("--version", "outputs version information for this tool") do
|
16
|
+
puts LaunchdTools::VERSION
|
17
|
+
end
|
18
|
+
opt.separator ""
|
19
|
+
end
|
20
|
+
|
21
|
+
opt_parser.parse!(args)
|
22
|
+
|
23
|
+
if args.empty? || showing_help
|
24
|
+
puts opt_parser
|
25
|
+
else
|
26
|
+
@args = args
|
27
|
+
@paths = extract_paths(args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_paths(path_args)
|
32
|
+
path_args = path_args.map do |path_arg|
|
33
|
+
if File.directory?(path_arg)
|
34
|
+
path_arg += "/" if path_arg[-1] != "/"
|
35
|
+
path_arg += "*"
|
36
|
+
end
|
37
|
+
path_arg
|
38
|
+
end
|
39
|
+
Dir.glob(path_args)
|
40
|
+
end
|
41
|
+
|
42
|
+
def run
|
43
|
+
errors = 0
|
44
|
+
if paths
|
45
|
+
if paths.length > 0
|
46
|
+
errors = process_each_path
|
47
|
+
else
|
48
|
+
args.each {|arg| puts "No launchd job found at '#{arg}'" }
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
exit 2 if errors > 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_path(path_string)
|
56
|
+
error_count = 0
|
57
|
+
begin
|
58
|
+
path = Path.new(path_string).validate
|
59
|
+
puts "# #{path.expanded}"
|
60
|
+
puts path.parse.to_s
|
61
|
+
rescue LaunchdTools::Path::UnparsablePlist
|
62
|
+
puts "Error: unable to parse launchd job\n"
|
63
|
+
error_count = 1
|
64
|
+
rescue LaunchdTools::Path::PermissionsError
|
65
|
+
require 'etc'
|
66
|
+
username = Etc.getpwuid(Process.euid).name
|
67
|
+
puts "Error: user #{username} does not have access to read launchd job\n"
|
68
|
+
error_count = 1
|
69
|
+
end
|
70
|
+
return error_count
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_each_path
|
74
|
+
error_count = 0
|
75
|
+
puts
|
76
|
+
paths.each do |path_string|
|
77
|
+
error_count += process_path(path_string)
|
78
|
+
end
|
79
|
+
return error_count
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module LaunchdTools
|
2
|
+
class LaunchdJob
|
3
|
+
attr_reader :attributes
|
4
|
+
def initialize(attributes = {})
|
5
|
+
@attributes = attributes
|
6
|
+
end
|
7
|
+
|
8
|
+
def environment_variables
|
9
|
+
attributes.fetch('EnvironmentVariables', {})
|
10
|
+
end
|
11
|
+
|
12
|
+
def program_arguments
|
13
|
+
attributes.fetch('ProgramArguments', [])
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
escaped_args = program_arguments.map {|arg| shellescape(arg) }
|
18
|
+
puts (environment_variables + escaped_args).join(" ")
|
19
|
+
end
|
20
|
+
|
21
|
+
# from ruby's shellescape
|
22
|
+
# included here for ruby 1.8 compatibility
|
23
|
+
def shellescape(str)
|
24
|
+
str = str.to_s
|
25
|
+
|
26
|
+
# An empty argument will be skipped, so return empty quotes.
|
27
|
+
return "''" if str.empty?
|
28
|
+
|
29
|
+
str = str.dup
|
30
|
+
|
31
|
+
# Treat multibyte characters as is. It is caller's responsibility
|
32
|
+
# to encode the string in the right encoding for the shell
|
33
|
+
# environment.
|
34
|
+
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")
|
35
|
+
|
36
|
+
# A LF cannot be escaped with a backslash because a backslash + LF
|
37
|
+
# combo is regarded as line continuation and simply ignored.
|
38
|
+
str.gsub!(/\n/, "'\n'")
|
39
|
+
|
40
|
+
return str
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
include REXML
|
3
|
+
module LaunchdTools
|
4
|
+
class LaunchdPlist
|
5
|
+
attr_reader :doc, :program_args_array_element
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
base = %q[<?xml version='1.0' encoding='UTF-8'?>
|
9
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">]
|
10
|
+
@doc = Document.new(base)
|
11
|
+
plist_element = Element.new('plist')
|
12
|
+
doc.add_element(plist_element, {"version" => "1.0"})
|
13
|
+
base_dictionary = Element.new('dict')
|
14
|
+
plist_element.add_element(base_dictionary)
|
15
|
+
program_args_key_element = Element.new('key')
|
16
|
+
program_args_key_element.text = 'ProgramArguments'
|
17
|
+
base_dictionary.add_element(program_args_key_element)
|
18
|
+
@program_args_array_element = Element.new "array"
|
19
|
+
base_dictionary.add_element(program_args_array_element)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def add_program_arg(arg)
|
24
|
+
string_element = Element.new "string"
|
25
|
+
string_element.text = arg
|
26
|
+
program_args_array_element << string_element
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_program_args(args)
|
30
|
+
args.each do |arg|
|
31
|
+
add_program_arg(arg)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
formatter = REXML::Formatters::Pretty.new # (2)
|
37
|
+
formatter.compact = true
|
38
|
+
xml_string = String.new
|
39
|
+
formatter.write(doc, xml_string)
|
40
|
+
xml_string
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'launchd_tools/path_parser'
|
2
|
+
require 'launchd_tools/path_content'
|
3
|
+
|
4
|
+
module LaunchdTools
|
5
|
+
class Path
|
6
|
+
|
7
|
+
class UnparsablePlist < Exception
|
8
|
+
end
|
9
|
+
|
10
|
+
class PermissionsError < Exception
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :path
|
14
|
+
|
15
|
+
def initialize(path)
|
16
|
+
@path = path
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
raise PathMissingError unless File.exist?(path)
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def content
|
25
|
+
PathContent.new(path).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse
|
29
|
+
begin
|
30
|
+
path_parser.parse
|
31
|
+
rescue Errno::EACCES
|
32
|
+
raise PermissionsError.new
|
33
|
+
rescue
|
34
|
+
raise UnparsablePlist.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def path_parser
|
39
|
+
PathParser.new(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def expanded
|
43
|
+
File.expand_path(path)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module LaunchdTools
|
2
|
+
class PathContent
|
3
|
+
attr_reader :path
|
4
|
+
def initialize(path)
|
5
|
+
@path = path
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
if binary?
|
10
|
+
`plutil -convert xml1 -o /dev/stdout '#{path}'`
|
11
|
+
else
|
12
|
+
File.read(path)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def binary?
|
17
|
+
f = File.open(path, "r")
|
18
|
+
f.read(6) == "bplist"
|
19
|
+
ensure
|
20
|
+
f.close if f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'launchd_tools/launchd_job'
|
3
|
+
require 'launchd_tools/environment_parser'
|
4
|
+
require 'launchd_tools/environment_variables'
|
5
|
+
require 'launchd_tools/program_args_parser'
|
6
|
+
|
7
|
+
include REXML
|
8
|
+
module LaunchdTools
|
9
|
+
class PathParser
|
10
|
+
attr_reader :path, :xml_doc
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
# returns a parsed launchd job
|
16
|
+
def parse
|
17
|
+
LaunchdJob.new({ 'EnvironmentVariables' => parse_env, 'ProgramArguments' => parse_program_args })
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_env
|
21
|
+
variables = EnvironmentParser.new(xml_doc).parse
|
22
|
+
EnvironmentVariables.new(variables).to_a
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_program_args
|
26
|
+
ProgramArgsParser.new(xml_doc).parse
|
27
|
+
end
|
28
|
+
|
29
|
+
def xml_doc
|
30
|
+
@xml_doc ||= Document.new(path.content)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
include REXML
|
3
|
+
module LaunchdTools
|
4
|
+
class ProgramArgsParser
|
5
|
+
attr_reader :xml_doc, :element
|
6
|
+
def initialize(xml_doc)
|
7
|
+
@xml_doc = xml_doc
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse
|
11
|
+
element = REXML::XPath.first(xml_doc, "plist/dict/key[text()='ProgramArguments']/following-sibling::array")
|
12
|
+
if element
|
13
|
+
args_strings = XPath.match(element, 'string')
|
14
|
+
args_strings.map {|e| e.text }
|
15
|
+
else
|
16
|
+
program_string_element = REXML::XPath.first(xml_doc, "plist/dict/key[text()='Program']/following-sibling::string")
|
17
|
+
[program_string_element.text]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string>com.apple.dt.CommandLineTools.installondemand</string>
|
7
|
+
<key>MachServices</key>
|
8
|
+
<dict>
|
9
|
+
<key>com.apple.dt.CommandLineTools.installondemand</key>
|
10
|
+
<true/>
|
11
|
+
</dict>
|
12
|
+
<key>OnDemand</key>
|
13
|
+
<true/>
|
14
|
+
<key>EnableTransactions</key>
|
15
|
+
<true/>
|
16
|
+
<key>Program</key>
|
17
|
+
<string>/System/Library/CoreServices/Install Command Line Developer Tools.app/Contents/MacOS/Install Command Line Developer Tools</string>
|
18
|
+
<key>LimitLoadToSessionType</key>
|
19
|
+
<string>Aqua</string>
|
20
|
+
</dict>
|
21
|
+
</plist>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string>com.apple.storeagent</string>
|
7
|
+
<key>MachServices</key>
|
8
|
+
<dict>
|
9
|
+
<key>com.apple.storeagent</key>
|
10
|
+
<true/>
|
11
|
+
<key>com.apple.storeagent-xpc</key>
|
12
|
+
<true/>
|
13
|
+
<key>com.apple.storeagent.storekit</key>
|
14
|
+
<string></string>
|
15
|
+
</dict>
|
16
|
+
<key>OnDemand</key>
|
17
|
+
<true/>
|
18
|
+
<key>Program</key>
|
19
|
+
<string>/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeagent</string>
|
20
|
+
<key>EnableTransactions</key>
|
21
|
+
<true/>
|
22
|
+
<key>LimitLoadToSessionType</key>
|
23
|
+
<array>
|
24
|
+
<string>LoginWindow</string>
|
25
|
+
<string>Aqua</string>
|
26
|
+
</array>
|
27
|
+
<key>LaunchEvents</key>
|
28
|
+
<dict>
|
29
|
+
<key>com.apple.usernotificationcenter.matching</key>
|
30
|
+
<dict>
|
31
|
+
<key>store</key>
|
32
|
+
<dict>
|
33
|
+
<key>bundleid</key>
|
34
|
+
<string>com.apple.appstore</string>
|
35
|
+
<key>delay registration</key>
|
36
|
+
<true/>
|
37
|
+
<key>events</key>
|
38
|
+
<array>
|
39
|
+
<string>didDismissAlert</string>
|
40
|
+
<string>didActivateNotification</string>
|
41
|
+
<string>didDeliverNotification</string>
|
42
|
+
<string>didSnoozeAlert</string>
|
43
|
+
<string>didRemoveDeliveredNotifications</string>
|
44
|
+
<string>didExpireNotifications</string>
|
45
|
+
</array>
|
46
|
+
</dict>
|
47
|
+
</dict>
|
48
|
+
</dict>
|
49
|
+
</dict>
|
50
|
+
</plist>
|
Binary file
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Disabled</key>
|
6
|
+
<true/>
|
7
|
+
<key>Label</key>
|
8
|
+
<string>com.openssh.sshd</string>
|
9
|
+
<key>Program</key>
|
10
|
+
<string>/usr/libexec/sshd-keygen-wrapper</string>
|
11
|
+
<key>ProgramArguments</key>
|
12
|
+
<array>
|
13
|
+
<string>/usr/sbin/sshd</string>
|
14
|
+
<string>-i</string>
|
15
|
+
</array>
|
16
|
+
<key>Sockets</key>
|
17
|
+
<dict>
|
18
|
+
<key>Listeners</key>
|
19
|
+
<dict>
|
20
|
+
<key>SockServiceName</key>
|
21
|
+
<string>ssh</string>
|
22
|
+
<key>Bonjour</key>
|
23
|
+
<array>
|
24
|
+
<string>ssh</string>
|
25
|
+
<string>sftp-ssh</string>
|
26
|
+
</array>
|
27
|
+
</dict>
|
28
|
+
</dict>
|
29
|
+
<key>inetdCompatibility</key>
|
30
|
+
<dict>
|
31
|
+
<key>Wait</key>
|
32
|
+
<false/>
|
33
|
+
</dict>
|
34
|
+
<key>StandardErrorPath</key>
|
35
|
+
<string>/dev/null</string>
|
36
|
+
<key>SHAuthorizationRight</key>
|
37
|
+
<string>system.preferences</string>
|
38
|
+
<key>POSIXSpawnType</key>
|
39
|
+
<string>Interactive</string>
|
40
|
+
</dict>
|
41
|
+
</plist>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Disabled</key>
|
6
|
+
<true/>
|
7
|
+
<key>EnvironmentVariables</key>
|
8
|
+
<dict>
|
9
|
+
<key>XPC_SERVICES_UNAVAILABLE</key>
|
10
|
+
<string>1</string>
|
11
|
+
</dict>
|
12
|
+
<key>Label</key>
|
13
|
+
<string>org.apache.httpd</string>
|
14
|
+
<key>OnDemand</key>
|
15
|
+
<false/>
|
16
|
+
<key>ProgramArguments</key>
|
17
|
+
<array>
|
18
|
+
<string>/usr/sbin/httpd</string>
|
19
|
+
<string>-D</string>
|
20
|
+
<string>FOREGROUND</string>
|
21
|
+
</array>
|
22
|
+
<key>SHAuthorizationRight</key>
|
23
|
+
<string>system.preferences</string>
|
24
|
+
</dict>
|
25
|
+
</plist>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
describe 'running cmd2launchd' do
|
3
|
+
it "converts right back to original commmand using launchd2cmd" do
|
4
|
+
t = Tempfile.new(File.basename(__FILE__))
|
5
|
+
temp_file_path = t.path
|
6
|
+
t.close
|
7
|
+
output = `bundle exec bin/cmd2launchd ls -la /tmp > #{temp_file_path} && bundle exec bin/launchd2cmd #{temp_file_path}`
|
8
|
+
expect(output.chomp).to include("ls -la /tmp")
|
9
|
+
File.unlink(temp_file_path)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "no arguments" do
|
13
|
+
it "outputs usage info" do
|
14
|
+
output = `bundle exec bin/cmd2launchd`
|
15
|
+
expect(output.chomp).to include("Usage: ")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "help option" do
|
20
|
+
it "outputs usage info" do
|
21
|
+
output = `bundle exec bin/cmd2launchd -h`
|
22
|
+
expect(output.chomp).to include("Usage: ")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
describe 'running launchd2cmd' do
|
2
|
+
|
3
|
+
context 'launch agent' do
|
4
|
+
it 'outputs the command' do
|
5
|
+
output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents/com.apple.storeagent.plist`
|
6
|
+
expect(output.chomp).to include("/System/Library/PrivateFrameworks/CommerceKit.framework/Versions/A/Resources/storeagent")
|
7
|
+
end
|
8
|
+
it "outputs a comment line with path to the plist" do
|
9
|
+
output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents/com.apple.storeagent.plist`
|
10
|
+
expect(output.chomp).to include("# #{File.expand_path("spec/fixtures/LaunchAgents/com.apple.storeagent.plist")}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "no arguments" do
|
15
|
+
it "outputs usage info" do
|
16
|
+
output = `bundle exec bin/launchd2cmd`
|
17
|
+
expect(output.chomp).to include("Usage: ")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "help option" do
|
22
|
+
it "outputs usage info" do
|
23
|
+
output = `bundle exec bin/launchd2cmd -h`
|
24
|
+
expect(output.chomp).to include("Usage: ")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "inaccessible file" do
|
29
|
+
it "outputs relevant error message" do
|
30
|
+
inaccessible_path = 'spec/fixtures/LaunchDaemons/inaccessible.plist'
|
31
|
+
File.chmod(0000, inaccessible_path)
|
32
|
+
output = `bundle exec bin/launchd2cmd #{inaccessible_path}`
|
33
|
+
File.chmod(0644, inaccessible_path)
|
34
|
+
require 'etc'
|
35
|
+
username = Etc.getpwuid(Process.euid).name
|
36
|
+
expect(output).to include("Error: user #{username} does not have access to read launchd job")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "unparsable file" do
|
41
|
+
it "outputs relevant error message" do
|
42
|
+
output = `bundle exec bin/launchd2cmd #{__FILE__}`
|
43
|
+
expect(output).to include("Error: unable to parse launchd job")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'bad file paths' do
|
48
|
+
it "outputs relevant error message" do
|
49
|
+
output = `bundle exec bin/launchd2cmd not/a/real/path and/another/bad/path`
|
50
|
+
expect(output).to include("No launchd job found at 'not/a/real/path'")
|
51
|
+
expect(output).to include("No launchd job found at 'and/another/bad/path'")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
context 'launch daemon' do
|
55
|
+
context 'with environment variables' do
|
56
|
+
it 'includes argument and environment variables in output' do
|
57
|
+
output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchDaemons/org.apache.httpd.plist`
|
58
|
+
expect(output.chomp).to include("XPC_SERVICES_UNAVAILABLE=1 /usr/sbin/httpd -D FOREGROUND")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
context "binary plist" do
|
63
|
+
it "handles it without error" do
|
64
|
+
output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist`
|
65
|
+
expect(output.chomp).to include("__CFPREFERENCES_AVOID_DAEMON=1 __CF_USER_TEXT_ENCODING=0x0:0:0 /usr/libexec/opendirectoryd")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "args are directories" do
|
70
|
+
it "lists contents of directory" do
|
71
|
+
command = "bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents > /dev/null"
|
72
|
+
expect(system(command)).to be_true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "program arguments contain spaces" do
|
77
|
+
it "escapes them for use in the shell" do
|
78
|
+
output = `bundle exec bin/launchd2cmd spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist`
|
79
|
+
expect(output.chomp).to include("/System/Library/CoreServices/Install\\ Command\\ Line\\ Developer\\ Tools.app/Contents/MacOS/Install\\ Command\\ Line\\ Developer\\ Tools")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: launchd_tools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kyle Crawford
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Provides tools for converting from command line arguments to a formatted
|
56
|
+
launchd plist and vice versa
|
57
|
+
email:
|
58
|
+
- kcrwfrd@gmail.com
|
59
|
+
executables:
|
60
|
+
- cmd2launchd
|
61
|
+
- launchd2cmd
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- ".gitignore"
|
66
|
+
- ".rspec"
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- TODO
|
72
|
+
- bin/cmd2launchd
|
73
|
+
- bin/launchd2cmd
|
74
|
+
- launchd_tools.gemspec
|
75
|
+
- lib/launchd_tools.rb
|
76
|
+
- lib/launchd_tools/cmd2launchd_cli.rb
|
77
|
+
- lib/launchd_tools/environment_parser.rb
|
78
|
+
- lib/launchd_tools/environment_variables.rb
|
79
|
+
- lib/launchd_tools/launchd2cmd_cli.rb
|
80
|
+
- lib/launchd_tools/launchd_job.rb
|
81
|
+
- lib/launchd_tools/launchd_plist.rb
|
82
|
+
- lib/launchd_tools/path.rb
|
83
|
+
- lib/launchd_tools/path_content.rb
|
84
|
+
- lib/launchd_tools/path_parser.rb
|
85
|
+
- lib/launchd_tools/program_args_parser.rb
|
86
|
+
- lib/launchd_tools/version.rb
|
87
|
+
- spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist
|
88
|
+
- spec/fixtures/LaunchAgents/com.apple.storeagent.plist
|
89
|
+
- spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist
|
90
|
+
- spec/fixtures/LaunchDaemons/inaccessible.plist
|
91
|
+
- spec/fixtures/LaunchDaemons/org.apache.httpd.plist
|
92
|
+
- spec/integration/cmd2launchd_spec.rb
|
93
|
+
- spec/integration/launchd2cmd_spec.rb
|
94
|
+
- spec/spec_helper.rb
|
95
|
+
homepage: ''
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.2.1
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: launchd tools convert from command to launchd and launchd to command
|
119
|
+
test_files:
|
120
|
+
- spec/fixtures/LaunchAgents/com.apple.dt.CommandLineTools.installondemand.plist
|
121
|
+
- spec/fixtures/LaunchAgents/com.apple.storeagent.plist
|
122
|
+
- spec/fixtures/LaunchDaemons/com.apple.opendirectoryd.plist
|
123
|
+
- spec/fixtures/LaunchDaemons/inaccessible.plist
|
124
|
+
- spec/fixtures/LaunchDaemons/org.apache.httpd.plist
|
125
|
+
- spec/integration/cmd2launchd_spec.rb
|
126
|
+
- spec/integration/launchd2cmd_spec.rb
|
127
|
+
- spec/spec_helper.rb
|