devdnsd 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+
20
+ # RubyMine
21
+ .idea/
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --exclude lib/devdnsd/patches/.+ --exclude utils/ -m markdown
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ source "http://rubygems.org"
8
+
9
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,93 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ devdnsd (1.0.0)
5
+ cowtech-extensions (~> 2.1.0)
6
+ gli (~> 1.6.0)
7
+ rainbow (~> 1.1.0)
8
+ rexec (~> 1.4.1)
9
+ rubydns (~> 0.4.0)
10
+
11
+ GEM
12
+ remote: http://rubygems.org/
13
+ specs:
14
+ actionpack (3.2.6)
15
+ activemodel (= 3.2.6)
16
+ activesupport (= 3.2.6)
17
+ builder (~> 3.0.0)
18
+ erubis (~> 2.7.0)
19
+ journey (~> 1.0.1)
20
+ rack (~> 1.4.0)
21
+ rack-cache (~> 1.2)
22
+ rack-test (~> 0.6.1)
23
+ sprockets (~> 2.1.3)
24
+ activemodel (3.2.6)
25
+ activesupport (= 3.2.6)
26
+ builder (~> 3.0.0)
27
+ activesupport (3.2.6)
28
+ i18n (~> 0.6)
29
+ multi_json (~> 1.0)
30
+ builder (3.0.0)
31
+ coderay (1.0.7)
32
+ cowtech-extensions (2.1.3)
33
+ actionpack (~> 3.0)
34
+ tzinfo (~> 0.3.33)
35
+ diff-lcs (1.1.3)
36
+ erubis (2.7.0)
37
+ eventmachine (0.12.10)
38
+ github-markup (0.7.2)
39
+ gli (1.6.0)
40
+ hike (1.2.1)
41
+ i18n (0.6.0)
42
+ journey (1.0.4)
43
+ method_source (0.8)
44
+ multi_json (1.3.6)
45
+ net-dns (0.7.1)
46
+ pry (0.9.10)
47
+ coderay (~> 1.0.5)
48
+ method_source (~> 0.8)
49
+ slop (~> 3.3.1)
50
+ rack (1.4.1)
51
+ rack-cache (1.2)
52
+ rack (>= 0.4)
53
+ rack-test (0.6.1)
54
+ rack (>= 1.0)
55
+ rainbow (1.1.4)
56
+ redcarpet (2.1.1)
57
+ rexec (1.4.1)
58
+ rspec (2.11.0)
59
+ rspec-core (~> 2.11.0)
60
+ rspec-expectations (~> 2.11.0)
61
+ rspec-mocks (~> 2.11.0)
62
+ rspec-core (2.11.0)
63
+ rspec-expectations (2.11.1)
64
+ diff-lcs (~> 1.1.3)
65
+ rspec-mocks (2.11.1)
66
+ rubydns (0.4.1)
67
+ eventmachine
68
+ rexec
69
+ simplecov (0.6.4)
70
+ multi_json (~> 1.0)
71
+ simplecov-html (~> 0.5.3)
72
+ simplecov-html (0.5.3)
73
+ slop (3.3.2)
74
+ sprockets (2.1.3)
75
+ hike (~> 1.2)
76
+ rack (~> 1.0)
77
+ tilt (~> 1.1, != 1.3.0)
78
+ tilt (1.3.3)
79
+ tzinfo (0.3.33)
80
+ yard (0.8.2.1)
81
+
82
+ PLATFORMS
83
+ ruby
84
+
85
+ DEPENDENCIES
86
+ devdnsd!
87
+ github-markup (~> 0.7.0)
88
+ net-dns (~> 0.7.0)
89
+ pry (~> 0.9.9)
90
+ redcarpet (~> 2.1.0)
91
+ rspec (~> 2.11.0)
92
+ simplecov (~> 0.6.0)
93
+ yard (~> 0.8.0)
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # devdnsd
2
+
3
+ A small DNS server to enable local .dev domain resolution.
4
+ http://github.com/ShogunPanda/devdnsd
5
+
6
+ ## Description
7
+
8
+ DevDNSd is a small DNS server which add a resolver to the system only for single TLD (by default, **.dev**). This way you can access your local application by typing every kind of url, i.e. *locallapp.dev*.
9
+
10
+ Of course, DevDNSd is inspired by [pow](https://github.com/37signals/pow), but it only provides DNS functionalities, delegating the setup of a web-server to the user.
11
+
12
+ ## Basic usage
13
+
14
+ 1. Install the gem:
15
+
16
+ `gem install devdnsd`
17
+
18
+ 2. Install the service:
19
+
20
+ `devdnsd install`
21
+
22
+ **You're done!**
23
+
24
+ ## Advanced usage
25
+
26
+ Just type `devdns help` and you'll see all available options.
27
+
28
+ ## Configuration
29
+
30
+ By defaults, DevDNSd uses a configuration file in `~/.devdnsd_config`, but you can change the path using the `--config` switch.
31
+
32
+ The file is a plain Ruby file with a single `config` object that supports the following directives.
33
+
34
+ * `foreground`: If run the application in foreground.
35
+ * `address`: The IP to bind, 0.0.0.0 by default.
36
+ * `port`: The port to bind, 7771 by default.
37
+ * `tld`: The TLD to handle.
38
+ * `log_file`: The default log file. Not used if run in foreground.
39
+ * `log_level`: The default log level. Valid values are from 0 to 5 where 0 means "all messages".
40
+ * `add_rule`: Add a rule to the server. See section *Rules* below.
41
+
42
+ ## Rules
43
+
44
+ DevDNSd has a nice rules system for matching name.
45
+ You can add rules by calling `config.add_rule(...)` into the configuration file.
46
+
47
+ The first argument of the function is the name you want to match. You can use regular expressions and this is highly recommended if you want to pass a block to the method (see below).
48
+
49
+ The second argument should be the IP associated to the name. If the first argument is a regexp with groups, you can use the standard Ruby `String#gsub` substitution syntax for the reply.
50
+ You can also use `false` to reject matching for this nameserver. In this case the next nameserver for the system will be used. You can skip this argument if you pass a block to the function.
51
+ If you return `nil` from the block, then you'll be responsible to set the response via the [`transaction`](http://rubydoc.info/gems/rubydns/RubyDNS/Transaction) variable.
52
+
53
+ The third argument can optionally be the record type for the resolv. By default is `:A`.
54
+ This argument is ignored if you pass the block, as it assumes that the second argument is the record type.
55
+
56
+ If you pass a block to the method, its return code will be taken as the result of the resolving. So if you return `false` then the resolving will be rejected. The block takes three arguments, `match`, `type` and `transaction`. If you used regular expression for the first argument, then `match` will contain all the match information for the name.
57
+
58
+ For some examples of rules, see the `config/devdnsd_config.sample` file into the repository.
59
+
60
+ ## Remarks
61
+
62
+ DevDNSd as a local resolver is tightly coupled with the OSX name resolution system, so it is only available for MacOSX.
63
+
64
+ You can, anyway, run the software as DNS server.
65
+
66
+ ## Contributing to devdns
67
+
68
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
69
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
70
+ * Fork the project
71
+ * Start a feature/bugfix branch
72
+ * Commit and push until you are happy with your contribution
73
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
74
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
75
+
76
+ ## Copyright
77
+
78
+ Copyright (C) 2012 and above Shogun <[shogun_panda@me.com](mailto:shogun_panda@me.com)>.
79
+ Licensed under the MIT license, which can be found at [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the cowtech-extensions gem. Copyright (C) 2011 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ require "bundler/gem_tasks"
8
+ require "rspec/core/rake_task"
9
+
10
+ RSpec::Core::RakeTask.new("spec")
11
+
12
+ namespace :spec do
13
+ desc "Run all specs with coverage"
14
+ task :coverage do
15
+ ENV["DEVDNSD_COVERAGE"] = "TRUE"
16
+ Rake::Task["spec"].invoke
17
+ end
18
+ end
data/bin/devdnsd ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ #
4
+ # This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
5
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
6
+ #
7
+
8
+ require "rubygems"
9
+ require "devdnsd"
10
+
11
+ # TODO: Replace GLI with another library.
12
+
13
+ include GLI
14
+ program_desc("A small DNS server to enable local domain resolution.")
15
+ version(DevDNSd::Version::STRING)
16
+
17
+ desc("Do not ask any confirmation.")
18
+ switch([:f, :force])
19
+
20
+ desc("The TLD to handle.")
21
+ default_value("dev")
22
+ arg_name("TLD")
23
+ flag([:t,:tld])
24
+
25
+ desc("The port to bind.")
26
+ default_value(7771)
27
+ arg_name("PORT")
28
+ flag([:p,:port])
29
+
30
+ desc("The default configuration file.")
31
+ default_value("~/.devdnsd_config")
32
+ arg_name("FILE")
33
+ flag([:c, :config])
34
+
35
+ desc("The default log file. Not used if run in foreground.")
36
+ default_value("/var/log/devdnsd.log")
37
+ arg_name("LOG")
38
+ flag([:l, :log])
39
+
40
+ desc("The default log level. Valid values are from 0 to 5 where 0 means \"all messages\".")
41
+ default_value(1)
42
+ arg_name("LEVEL")
43
+ flag([:L, :log_level])
44
+
45
+ desc("Starts the server.")
46
+ command [:s, :start] do |c|
47
+ c.desc("Do not daemonize.")
48
+ c.switch [:n, :foreground]
49
+
50
+ c.action do |globals, locals, args|
51
+ DevDNSd::Application.instance(globals, locals, args).action_start()
52
+ end
53
+ end
54
+
55
+ desc("Stops the server.")
56
+ command [:t, :stop] do |c|
57
+ c.action do |globals, locals, args|
58
+ DevDNSd::Application.instance(globals, locals, args).action_stop()
59
+ end
60
+ end
61
+
62
+ desc("Install the server.")
63
+ command [:i, :install] do |c|
64
+ c.action do |globals, locals, args|
65
+ DevDNSd::Application.instance(globals, locals, args).action_install()
66
+ end
67
+ end
68
+
69
+ desc("Uninstall the server.")
70
+ command [:u, :uninstall] do |c|
71
+ c.action do |globals, locals, args|
72
+ DevDNSd::Application.instance(globals, locals, args).action_uninstall()
73
+ end
74
+ end
75
+
76
+ ARGV << "help" if ARGV.length == 0
77
+ exit(GLI.run(ARGV))
@@ -0,0 +1,24 @@
1
+ config.add_rule("match_1.dev", "10.0.1.1")
2
+
3
+ config.add_rule("match_2.dev", "10.0.2.1", :MX)
4
+
5
+ config.add_rule("match_3.dev") do "10.0.3.1" end
6
+
7
+ config.add_rule("match_4.dev", :CNAME) do "10.0.4.1" end
8
+
9
+
10
+ config.add_rule(/match_5_(\d+)\.dev/, "10.0.5.$1")
11
+
12
+ config.add_rule(/match_6_(\d+).dev/, "10.0.6.$1", :PTR)
13
+
14
+ config.add_rule(/match_7_(\d+).dev/) do "10.0.7.$1" end
15
+
16
+ config.add_rule(/match_8_(\d+).dev/, :PTR) do "10.0.8.$1" end
17
+
18
+
19
+ config.add_rule("match_9.dev") do false end
20
+
21
+ config.add_rule("match_10.dev", [:A, :MX]) do |match, type, transaction| type == :A ? "10.0.10.1" : "10.0.10.2" end
22
+
23
+
24
+ config.add_rule("quit.dev") do DevDNSd::Application.quit end
data/devdnsd.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the devdnsd gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ require "./lib/devdnsd/version"
8
+
9
+ Gem::Specification.new do |gem|
10
+ gem.name = "devdnsd"
11
+ gem.version = DevDNSd::Version::STRING
12
+ gem.authors = ["Shogun"]
13
+ gem.email = ["shogun_panda@me.com"]
14
+ gem.homepage = "http://github.com/ShogunPanda/devdnsd"
15
+ gem.summary = %q{A small DNS server to enable local domain resolution.}
16
+ gem.description = %q{A small DNS server to enable local domain resolution.}
17
+
18
+ gem.rubyforge_project = "devdnsd"
19
+ gem.files = `git ls-files`.split("\n")
20
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ gem.require_paths = ["lib"]
23
+ #gem.platform = Gem::Platform::CURRENT
24
+
25
+ gem.add_dependency("rubydns", "~> 0.4.0")
26
+ gem.add_dependency("cowtech-extensions", "~> 2.1.0")
27
+ gem.add_dependency("gli", "~> 1.6.0")
28
+ gem.add_dependency("rexec", "~> 1.4.1")
29
+ gem.add_dependency("rainbow", "~> 1.1.0")
30
+
31
+ gem.add_development_dependency("rspec", "~> 2.11.0")
32
+ gem.add_development_dependency("simplecov", "~> 0.6.0")
33
+ gem.add_development_dependency("pry", "~> 0.9.9")
34
+ gem.add_development_dependency("net-dns", "~> 0.7.0")
35
+ gem.add_development_dependency("yard", "~> 0.8.0")
36
+ gem.add_development_dependency("redcarpet", "~> 2.1.0")
37
+ gem.add_development_dependency("github-markup", "~> 0.7.0")
38
+ end
39
+
40
+
data/lib/devdnsd.rb ADDED
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the devdns gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ require "rubygems"
8
+ require "rubydns"
9
+ require "gli"
10
+ require "rexec/daemon"
11
+ require "pathname"
12
+ require "rainbow"
13
+ require "cowtech-extensions"
14
+ require "rbconfig"
15
+
16
+ Cowtech::Extensions.load!
17
+
18
+ require "devdnsd/application"
19
+ require "devdnsd/configuration"
20
+ require "devdnsd/errors"
21
+ require "devdnsd/logger"
22
+ require "devdnsd/rule"
23
+ require "devdnsd/version" if !defined?(DevDNSd::Version)
@@ -0,0 +1,366 @@
1
+ # encoding: utf-8
2
+ #
3
+ # This file is part of the devdns gem. Copyright (C) 2012 and above Shogun <shogun_panda@me.com>.
4
+ # Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
5
+ #
6
+
7
+ # A small DNS server to enable local .dev domain resolution.
8
+ module DevDNSd
9
+ # The main DevDNSd application.
10
+ class Application < RExec::Daemon::Base
11
+ # Class for ANY DNS request.
12
+ ANY_REQUEST = Resolv::DNS::Resource::IN::ANY
13
+
14
+ # List of classes handled in case of DNS request with resource class ANY.
15
+ ANY_CLASSES = [Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME, Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS, Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT]
16
+
17
+ # The {Configuration Configuration} of this application.
18
+ attr_reader :config
19
+
20
+ # The arguments passed via command-line.
21
+ attr_reader :args
22
+
23
+ # The {Logger Logger} for this application.
24
+ attr_accessor :logger
25
+
26
+ # Creates a new application.
27
+ #
28
+ # @param globals [Hash] Global options.
29
+ # @param locals [Hash] Local command options.
30
+ # @param args [Array] Extra arguments.
31
+ def initialize(globals = {}, locals = {}, args = [])
32
+ @args = {
33
+ :global => globals,
34
+ :local => locals,
35
+ :args => args
36
+ }
37
+
38
+ # Setup logger
39
+ DevDNSd::Logger.start_time = Time.now
40
+ @logger = DevDNSd::Logger.create(DevDNSd::Logger.get_real_file(@args[:global][:log_file]) || DevDNSd::Logger.default_file, Logger::INFO)
41
+
42
+ # Open configuration
43
+ begin
44
+ @config = DevDNSd::Configuration.new(@args[:global][:config], self, {
45
+ :foreground => @args[:local][:foreground],
46
+ :log_file => @args[:global][:log_file],
47
+ :log_level => @args[:global][:log_level],
48
+ :tld => @args[:global][:tld],
49
+ :port => @args[:global][:port]
50
+ })
51
+ @logger = nil
52
+ @logger = self.get_logger
53
+
54
+ rescue DevDNSd::Errors::InvalidConfiguration, DevDNSd::Errors::InvalidRule => e
55
+ @logger.fatal(e.message)
56
+ raise SystemExit
57
+ end
58
+
59
+ self
60
+ end
61
+
62
+ # Check if we are running on MacOS X.
63
+ # System services are only available on that platform.
64
+ #
65
+ # @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
66
+ def is_osx?
67
+ Config::CONFIG['host_os'] =~ /^darwin/
68
+ end
69
+
70
+ # Gets the current logger of the application.
71
+ #
72
+ # @return [Logger] The current logger of the application.
73
+ def get_logger
74
+ @logger ||= DevDNSd::Logger.create(@config.foreground ? DevDNSd::Logger.default_file : @config.log_file, @config.log_level, @log_formatter)
75
+ end
76
+
77
+ # Gets the path for the resolver file.
78
+ #
79
+ # @param tld [String] The TLD to manage.
80
+ # @return [String] The path for the resolver file.
81
+ def resolver_path(tld = nil)
82
+ tld ||= @config.tld
83
+ "/etc/resolver/#{tld}"
84
+ end
85
+
86
+ # Gets the path for the launch agent file.
87
+ #
88
+ # @param name [String] The base name for the agent.
89
+ # @return [String] The path for the launch agent file.
90
+ def launch_agent_path(name = "it.cowtech.devdnsd")
91
+ ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
92
+ end
93
+
94
+ # Executes a shell command.
95
+ #
96
+ # @param command [String] The command to execute.
97
+ # @return [Boolean] `true` if command succeeded, `false` otherwise.
98
+ def execute_command(command)
99
+ system(command)
100
+ end
101
+
102
+ # Updates DNS cache.
103
+ #
104
+ # @return [Boolean] `true` if command succeeded, `false` otherwise.
105
+ def dns_update
106
+ @logger.info("Flushing DNS cache and resolvers ...")
107
+ self.execute_command("dscacheutil -flushcache")
108
+ end
109
+
110
+ # Starts the DNS server.
111
+ #
112
+ # @return [Object] The result of stop callbacks.
113
+ def perform_server
114
+ RubyDNS::run_server(:listen => [[:udp, @config.address, @config.port.to_integer]]) do
115
+ self.logger = Application.instance.logger
116
+
117
+ match(/.+/, Application::ANY_REQUEST) do |match_data, transaction|
118
+ transaction.append_question!
119
+
120
+ Application.instance.config.rules.each do |rule|
121
+ begin
122
+ # Get the subset of handled class that is valid for the rule
123
+ resource_classes = Application::ANY_CLASSES & rule.resource_class.ensure_array
124
+
125
+ if resource_classes.present? then
126
+ resource_classes.each do |resource_class| # Now for every class
127
+ matches = rule.match_host(match_data[0])
128
+ Application.instance.process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
129
+ end
130
+ end
131
+ rescue Exception => e
132
+ raise e
133
+ end
134
+ end
135
+ end
136
+
137
+ # Default DNS handler
138
+ otherwise do |transaction|
139
+ transaction.failure!(:NXDomain)
140
+ end
141
+
142
+ # Attach event handlers
143
+ self.on(:start) do
144
+ Application.instance.on_start
145
+ end
146
+
147
+ self.on(:stop) do
148
+ Application.instance.on_stop
149
+ end
150
+ end
151
+ end
152
+
153
+ # Processes a DNS rule.
154
+ #
155
+ # @param rule [Rule] The rule to process.
156
+ # @param type [Class] The type of request.
157
+ # @param match_or_transaction [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
158
+ # @param transaction [Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
159
+ # @return A reply for the request if matched, otherwise `false` or `nil`.
160
+ def process_rule(rule, type, match_data, transaction)
161
+ is_regex = rule.match.is_a?(Regexp)
162
+ type = DevDNSd::Rule.resource_class_to_symbol(type)
163
+
164
+ Application.instance.logger.debug("Found match on #{rule.match} with type #{type}.")
165
+
166
+ if !rule.block.nil? then
167
+ reply = rule.block.call(match_data, type, transaction)
168
+ else
169
+ reply = rule.reply
170
+ end
171
+
172
+ if is_regex && reply && match_data[0] then
173
+ reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\"))
174
+ end
175
+
176
+ Application.instance.logger.debug(reply ? "Reply is #{reply} with type #{type}." : "No reply found.")
177
+
178
+ if reply then
179
+ options = rule.options
180
+
181
+ final_reply = []
182
+
183
+ case type
184
+ when :MX
185
+ preference = options.delete(:preference)
186
+ preference = 10 if !preference.is_integer?
187
+ final_reply << preference
188
+ end
189
+
190
+ if [:A, :AAAA].include?(type) then
191
+ final_reply << reply
192
+ else
193
+ final_reply << Resolv::DNS::Name.create(reply)
194
+ end
195
+
196
+ final_reply << options.merge({:resource_class => DevDNSd::Rule.symbol_to_resource_class(type)})
197
+ transaction.respond!(*final_reply)
198
+ elsif reply == false then
199
+ false
200
+ else
201
+ reply
202
+ end
203
+ end
204
+
205
+ # Starts the server in background.
206
+ #
207
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
208
+ def action_start
209
+ logger = self.get_logger
210
+
211
+ logger.info("Starting DevDNSd ...")
212
+
213
+ if @config.foreground then
214
+ self.perform_server
215
+ else
216
+ RExec::Daemon::Controller.start(self.class)
217
+ end
218
+
219
+ true
220
+ end
221
+
222
+ # Stops the server in background.
223
+ #
224
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
225
+ def action_stop
226
+ RExec::Daemon::Controller.stop(self.class)
227
+
228
+ true
229
+ end
230
+
231
+ # Installs the server into the system.
232
+ #
233
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
234
+ def action_install
235
+ logger = get_logger
236
+
237
+ if !self.is_osx? then
238
+ logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
239
+ return false
240
+ end
241
+
242
+ resolver_file = self.resolver_path
243
+ launch_agent = self.launch_agent_path
244
+
245
+ # Installs the resolver
246
+ begin
247
+ logger.info("Installing the resolver in #{resolver_file} ...")
248
+
249
+ open(resolver_file, "w") {|f|
250
+ f.write("nameserver 127.0.0.1\n")
251
+ f.write("port #{@config.port}")
252
+ f.flush
253
+ }
254
+ rescue => e
255
+ logger.error("Cannot create the resolver file.")
256
+ return false
257
+ end
258
+
259
+ begin
260
+ logger.info("Creating the launch agent in #{launch_agent} ...")
261
+
262
+ args = $ARGV ? $ARGV[0, $ARGV.length - 1] : ["A"]
263
+
264
+ plist = {"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => (Pathname.new(Dir.pwd) + $0).to_s, "ProgramArguments" => args, "RunAtLoad" => true}
265
+ File.open(launch_agent, "w") {|f|
266
+ f.write(plist.to_json)
267
+ f.flush
268
+ }
269
+ self.execute_command("plutil -convert binary1 \"#{launch_agent}\"")
270
+ rescue => e
271
+ logger.error("Cannot create the launch agent.")
272
+ return false
273
+ end
274
+
275
+ begin
276
+ logger.info("Loading the launch agent ...")
277
+ self.execute_command("launchctl load -w \"#{launch_agent}\" > /dev/null 2>&1")
278
+ rescue => e
279
+ logger.error("Cannot load the launch agent.")
280
+ return false
281
+ end
282
+
283
+ self.dns_update
284
+
285
+ true
286
+ end
287
+
288
+ # Uninstalls the server from the system.
289
+ #
290
+ # @return [Boolean] `true` if action succedeed, `false` otherwise.
291
+ def action_uninstall
292
+ logger = self.get_logger
293
+
294
+ if !self.is_osx? then
295
+ logger.fatal("Install DevDNSd as a local resolver is only available on MacOSX.")
296
+ return false
297
+ end
298
+
299
+ resolver_file = self.resolver_path
300
+ launch_agent = self.launch_agent_path
301
+
302
+ # Remove the resolver
303
+ begin
304
+ logger.info("Deleting the resolver #{resolver_file} ...")
305
+ File.delete(resolver_file)
306
+ rescue => e
307
+ logger.warn("Cannot delete the resolver file.")
308
+ return false
309
+ end
310
+
311
+ # Unload the launch agent.
312
+ begin
313
+ self.execute_command("launchctl unload -w \"#{launch_agent}\" > /dev/null 2>&1")
314
+ rescue => e
315
+ logger.warn("Cannot unload the launch agent.")
316
+ end
317
+
318
+ # Delete the launch agent.
319
+ begin
320
+ logger.info("Deleting the launch agent #{launch_agent} ...")
321
+ File.delete(launch_agent)
322
+ rescue => e
323
+ logger.warn("Cannot delete the launch agent.")
324
+ return false
325
+ end
326
+
327
+ self.dns_update
328
+
329
+ true
330
+ end
331
+
332
+ # This method is called when the server starts. By default is a no-op.
333
+ #
334
+ # @return [NilClass] `nil`.
335
+ def on_start
336
+ end
337
+
338
+ # This method is called when the server stop.
339
+ #
340
+ # @return [NilClass] `nil`.
341
+ def on_stop
342
+ end
343
+
344
+ # Returns a unique (singleton) instance of the application.
345
+ # @param globals [Hash] Global options.
346
+ # @param locals [Hash] Local command options.
347
+ # @param args [Array] Extra arguments.
348
+ # @return [Application] The unique (singleton) instance of the application.
349
+ def self.instance(globals = {}, locals = {}, args = [], force = false)
350
+ @@instance = nil if force
351
+ @@instance ||= Application.new(globals, locals, args)
352
+ end
353
+
354
+ # Runs the application in foreground.
355
+ #
356
+ # @see #perform_server
357
+ def self.run
358
+ self.instance.perform_server
359
+ end
360
+
361
+ # Stops the application.
362
+ def self.quit
363
+ EventMachine.stop
364
+ end
365
+ end
366
+ end