devdnsd 3.1.2 → 4.0.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 +4 -4
- data/.gitignore +2 -2
- data/.rubocop.yml +82 -0
- data/.travis-gemfile +4 -10
- data/.travis.yml +11 -5
- data/CHANGELOG.md +11 -0
- data/Gemfile +9 -8
- data/README.md +4 -5
- data/Rakefile +22 -6
- data/bin/devdnsd +42 -36
- data/config/devdnsd_config.sample +11 -11
- data/devdnsd.gemspec +7 -6
- data/doc/DevDNSd.html +6 -6
- data/doc/DevDNSd/{ApplicationMethods/Aliases.html → Aliases.html} +96 -100
- data/doc/DevDNSd/Application.html +2170 -1084
- data/doc/DevDNSd/Configuration.html +63 -33
- data/doc/DevDNSd/Errors.html +3 -3
- data/doc/DevDNSd/Errors/InvalidRule.html +3 -3
- data/doc/DevDNSd/{ApplicationMethods/System.html → OSX.html} +116 -489
- data/doc/DevDNSd/Rule.html +448 -749
- data/doc/DevDNSd/{ApplicationMethods/Server.html → Server.html} +77 -73
- data/doc/DevDNSd/System.html +895 -0
- data/doc/DevDNSd/Version.html +6 -6
- data/doc/_index.html +28 -27
- data/doc/class_list.html +6 -2
- data/doc/file.README.html +8 -9
- data/doc/file_list.html +5 -1
- data/doc/frames.html +1 -1
- data/doc/index.html +8 -9
- data/doc/js/full_list.js +4 -1
- data/doc/method_list.html +106 -84
- data/doc/top-level-namespace.html +3 -3
- data/lib/devdnsd.rb +10 -8
- data/lib/devdnsd/aliases.rb +171 -0
- data/lib/devdnsd/application.rb +93 -704
- data/lib/devdnsd/configuration.rb +20 -11
- data/lib/devdnsd/errors.rb +1 -1
- data/lib/devdnsd/osx.rb +217 -0
- data/lib/devdnsd/rule.rb +65 -94
- data/lib/devdnsd/server.rb +118 -0
- data/lib/devdnsd/system.rb +102 -0
- data/lib/devdnsd/version.rb +3 -3
- data/locales/en.yml +17 -16
- data/locales/it.yml +17 -16
- data/spec/devdnsd/application_spec.rb +188 -184
- data/spec/devdnsd/configuration_spec.rb +2 -2
- data/spec/devdnsd/rule_spec.rb +33 -34
- data/spec/resolver_helper.rb +10 -27
- data/spec/spec_helper.rb +21 -7
- metadata +36 -21
- data/doc/DevDNSd/ApplicationMethods.html +0 -125
- data/doc/DevDNSd/ApplicationMethods/System/ClassMethods.html +0 -538
- data/spec/coverage_helper.rb +0 -20
@@ -6,7 +6,7 @@
|
|
6
6
|
<title>
|
7
7
|
Top Level Namespace
|
8
8
|
|
9
|
-
— Documentation by YARD 0.8.7.
|
9
|
+
— Documentation by YARD 0.8.7.6
|
10
10
|
|
11
11
|
</title>
|
12
12
|
|
@@ -103,9 +103,9 @@
|
|
103
103
|
</div>
|
104
104
|
|
105
105
|
<div id="footer">
|
106
|
-
Generated on
|
106
|
+
Generated on Wed Mar 30 19:23:43 2016 by
|
107
107
|
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
108
|
-
0.8.7.
|
108
|
+
0.8.7.6 (ruby-2.3.0).
|
109
109
|
</div>
|
110
110
|
|
111
111
|
</body>
|
data/lib/devdnsd.rb
CHANGED
@@ -4,23 +4,25 @@
|
|
4
4
|
# Licensed under the MIT license, which can be found at http://www.opensource.org/licenses/mit-license.php.
|
5
5
|
#
|
6
6
|
|
7
|
-
require "rubygems"
|
8
|
-
require "bovem"
|
9
7
|
require "rubydns"
|
10
|
-
require "
|
8
|
+
require "process/daemon"
|
11
9
|
require "mustache"
|
12
10
|
require "ipaddr"
|
13
|
-
require "fiber"
|
14
11
|
require "plist"
|
15
12
|
require "tempfile"
|
13
|
+
require "bovem"
|
16
14
|
|
17
|
-
|
18
|
-
|
15
|
+
require "devdnsd/version" unless defined?(DevDNSd::Version)
|
16
|
+
require "devdnsd/aliases"
|
17
|
+
require "devdnsd/server"
|
18
|
+
require "devdnsd/system"
|
19
|
+
require "devdnsd/osx"
|
19
20
|
require "devdnsd/application"
|
20
21
|
require "devdnsd/configuration"
|
21
22
|
require "devdnsd/errors"
|
22
23
|
require "devdnsd/rule"
|
23
|
-
|
24
|
+
|
25
|
+
Celluloid.logger = nil
|
24
26
|
|
25
27
|
# DevDNSd is not supported on JRuby
|
26
|
-
DevDNSd::Application.check_ruby_implementation
|
28
|
+
DevDNSd::Application.check_ruby_implementation
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# This file is part of the devdnsd gem. Copyright (C) 2013 and above Shogun <shogun@cowtech.it>.
|
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
|
+
# Methods to handle interfaces aliases.
|
10
|
+
module Aliases
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
# Manages aliases.
|
14
|
+
#
|
15
|
+
# @param operation [Symbol] The type of operation. Can be `:add` or `:remove`.
|
16
|
+
# @param message [String] The message to show if no addresses are found.
|
17
|
+
# @param options [Hash] The options provided by the user.
|
18
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
19
|
+
def manage_aliases(operation, message, options)
|
20
|
+
config = self.config
|
21
|
+
options.each { |k, v| config.send("#{k}=", v) if config.respond_to?("#{k}=") }
|
22
|
+
|
23
|
+
addresses = compute_addresses
|
24
|
+
|
25
|
+
if addresses.present?
|
26
|
+
# Now, for every address, call the command
|
27
|
+
addresses.all? { |address| manage_address(operation, address, options[:dry_run]) }
|
28
|
+
else
|
29
|
+
@logger.error(message)
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Adds or removes an alias from the interface.
|
35
|
+
#
|
36
|
+
# @param type [Symbol] The operation to execute. Can be `:add` or `:remove`.
|
37
|
+
# @param address [String] The address to manage.
|
38
|
+
# @param dry_run [Boolean] If only show which modifications will be done.
|
39
|
+
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
40
|
+
def manage_address(type, address, dry_run = false)
|
41
|
+
rv, command, prefix = setup_management(type, address)
|
42
|
+
|
43
|
+
# Now execute
|
44
|
+
if rv
|
45
|
+
if !dry_run
|
46
|
+
execute_manage(command, prefix, type, address, config)
|
47
|
+
else
|
48
|
+
log_management(:dry_run, prefix, type, i18n.remove, i18n.add, address, config)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
rv
|
53
|
+
end
|
54
|
+
|
55
|
+
# Computes the list of address to manage.
|
56
|
+
#
|
57
|
+
# @param type [Symbol] The type of addresses to consider. Valid values are `:ipv4`, `:ipv6`, otherwise all addresses are considered.
|
58
|
+
# @return [Array] The list of addresses to add or remove from the interface.
|
59
|
+
def compute_addresses(type = :all)
|
60
|
+
config = self.config
|
61
|
+
config.addresses.present? ? filter_addresses(config, type) : generate_addresses(config, type)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks if an address is a valid IPv4 address.
|
65
|
+
#
|
66
|
+
# @param address [String] The address to check.
|
67
|
+
# @return [Boolean] `true` if the address is a valid IPv4 address, `false` otherwise.
|
68
|
+
def ipv4?(address)
|
69
|
+
address = address.ensure_string
|
70
|
+
|
71
|
+
mo = /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/.match(address)
|
72
|
+
(mo && mo.captures.all? { |i| i.to_i < 256 }) ? true : false
|
73
|
+
end
|
74
|
+
alias_method :is_ipv4?, :ipv4?
|
75
|
+
|
76
|
+
# Checks if an address is a valid IPv6 address.
|
77
|
+
#
|
78
|
+
# @param address [String] The address to check.
|
79
|
+
# @return [Boolean] `true` if the address is a valid IPv6 address, `false` otherwise.
|
80
|
+
def ipv6?(address)
|
81
|
+
address = address.ensure_string
|
82
|
+
|
83
|
+
catch(:valid) do
|
84
|
+
# IPv6 (normal)
|
85
|
+
check_normal_ipv6(address)
|
86
|
+
# IPv6 (IPv4 compat)
|
87
|
+
check_compat_ipv6(address)
|
88
|
+
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# :nodoc:
|
96
|
+
def setup_management(type, address)
|
97
|
+
@addresses ||= compute_addresses
|
98
|
+
length = @addresses.length
|
99
|
+
length_s = length.to_s.length
|
100
|
+
progress = ((@addresses.index(address) || 0) + 1).indexize(length: length_s)
|
101
|
+
message = "{mark=blue}[{mark=bright white}#{progress}{mark=reset blue}/{/mark}#{length}{/mark}]{/mark}"
|
102
|
+
|
103
|
+
[true, build_command(type, address), message]
|
104
|
+
rescue ArgumentError
|
105
|
+
[false]
|
106
|
+
end
|
107
|
+
|
108
|
+
# :nodoc:
|
109
|
+
def filter_addresses(config, type)
|
110
|
+
filters = [:ipv4, :ipv6].select { |i| type == i || type == :all }.compact
|
111
|
+
config.addresses.select { |address| filters.any? { |filter| send("#{filter}?", address) } }.compact.uniq
|
112
|
+
end
|
113
|
+
|
114
|
+
# :nodoc:
|
115
|
+
def generate_addresses(config, type)
|
116
|
+
ip = IPAddr.new(config.start_address.ensure_string)
|
117
|
+
raise ArgumentError if type != :all && !ip.send("#{type}?")
|
118
|
+
Array.new([config.aliases, 1].max) do |_|
|
119
|
+
current = ip
|
120
|
+
ip = ip.succ
|
121
|
+
current
|
122
|
+
end
|
123
|
+
rescue ArgumentError
|
124
|
+
[]
|
125
|
+
end
|
126
|
+
|
127
|
+
# :nodoc:
|
128
|
+
def build_command(type, address)
|
129
|
+
template = config.send((type == :remove) ? :remove_command : :add_command)
|
130
|
+
Mustache.render(template, {interface: config.interface, address: address.to_s}) + " > /dev/null 2>&1"
|
131
|
+
end
|
132
|
+
|
133
|
+
# :nodoc:
|
134
|
+
def execute_manage(command, prefix, type, address, config)
|
135
|
+
rv = execute_command(command)
|
136
|
+
log_management(:run, prefix, type, i18n.removing, i18n.adding, address, config)
|
137
|
+
log_management_error(config, address, manage_labels(type)) unless rv
|
138
|
+
rv
|
139
|
+
end
|
140
|
+
|
141
|
+
# :nodoc:
|
142
|
+
def manage_labels(type)
|
143
|
+
type == :remove ? [i18n.remove, i18n.from] : [i18n.add, i18n.to]
|
144
|
+
end
|
145
|
+
|
146
|
+
# :nodoc:
|
147
|
+
def log_management_error(config, address, labels)
|
148
|
+
@logger.error(replace_markers(i18n.general_error(labels[0], address, labels[1], config.interface)))
|
149
|
+
end
|
150
|
+
|
151
|
+
# :nodoc:
|
152
|
+
def log_management(message, prefix, type, remove_label, add_label, address, config)
|
153
|
+
labels = (type == :remove ? [remove_label, i18n.from] : [add_label, i18n.to])
|
154
|
+
@logger.info(replace_markers(i18n.send(message, prefix, labels[0], address, labels[1], config.interface)))
|
155
|
+
end
|
156
|
+
|
157
|
+
# :nodoc:
|
158
|
+
def check_compat_ipv6(address)
|
159
|
+
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ address && ipv4?($')
|
160
|
+
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && ipv4?($')
|
161
|
+
throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && ipv4?($')
|
162
|
+
end
|
163
|
+
|
164
|
+
# :nodoc:
|
165
|
+
def check_normal_ipv6(address)
|
166
|
+
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ address
|
167
|
+
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
|
168
|
+
throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
data/lib/devdnsd/application.rb
CHANGED
@@ -6,676 +6,52 @@
|
|
6
6
|
|
7
7
|
# A small DNS server to enable local .dev domain resolution.
|
8
8
|
module DevDNSd
|
9
|
-
# Methods for the {Application Application} class.
|
10
|
-
module ApplicationMethods
|
11
|
-
# System management methods.
|
12
|
-
module System
|
13
|
-
extend ActiveSupport::Concern
|
14
|
-
|
15
|
-
# Class methods.
|
16
|
-
module ClassMethods
|
17
|
-
# Returns the name of the daemon.
|
18
|
-
#
|
19
|
-
# @return [String] The name of the daemon.
|
20
|
-
def daemon_name
|
21
|
-
File.basename(instance.config.pid_file, ".pid")
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns the standard location of the PID file.
|
25
|
-
#
|
26
|
-
# @return [String] The standard location of the PID file.
|
27
|
-
def working_directory
|
28
|
-
File.dirname(instance.config.pid_file)
|
29
|
-
end
|
30
|
-
alias_method :runtime_directory, :working_directory
|
31
|
-
|
32
|
-
# Returns the complete path of the PID file.
|
33
|
-
#
|
34
|
-
# @return [String] The complete path of the PID file.
|
35
|
-
def process_file_path
|
36
|
-
instance.config.pid_file
|
37
|
-
end
|
38
|
-
|
39
|
-
# Returns the complete path of the log file.
|
40
|
-
#
|
41
|
-
# @return [String] The complete path of the log file.
|
42
|
-
def log_file_path
|
43
|
-
instance.config.log_file
|
44
|
-
end
|
45
|
-
|
46
|
-
# Returns the standard location of the log file.
|
47
|
-
#
|
48
|
-
# @return [String] The standard location of the log file.
|
49
|
-
def log_directory
|
50
|
-
File.dirname(instance.config.log_file)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# Gets the path for the resolver file.
|
55
|
-
#
|
56
|
-
# @param tld [String] The TLD to manage.
|
57
|
-
# @return [String] The path for the resolver file.
|
58
|
-
def resolver_path(tld = nil)
|
59
|
-
tld ||= @config.tld
|
60
|
-
"/etc/resolver/#{tld}"
|
61
|
-
end
|
62
|
-
|
63
|
-
# Gets the path for the launch agent file.
|
64
|
-
#
|
65
|
-
# @param name [String] The base name for the agent.
|
66
|
-
# @return [String] The path for the launch agent file.
|
67
|
-
def launch_agent_path(name = "it.cowtech.devdnsd")
|
68
|
-
ENV["HOME"] + "/Library/LaunchAgents/#{name}.plist"
|
69
|
-
end
|
70
|
-
|
71
|
-
# Executes a shell command.
|
72
|
-
#
|
73
|
-
# @param command [String] The command to execute.
|
74
|
-
# @return [Boolean] `true` if command succeeded, `false` otherwise.
|
75
|
-
def execute_command(command)
|
76
|
-
system("#{command} 2>&1 > /dev/null")
|
77
|
-
end
|
78
|
-
|
79
|
-
# Updates DNS cache.
|
80
|
-
#
|
81
|
-
# @return [Boolean] `true` if command succeeded, `false` otherwise.
|
82
|
-
def dns_update
|
83
|
-
@logger.info(i18n.dns_update)
|
84
|
-
|
85
|
-
script = Tempfile.new("devdnsd-dns-cache-script")
|
86
|
-
script.write("dscacheutil -flushcache 2>&1 > /dev/null\n")
|
87
|
-
script.write("killall -9 mDNSResponder 2>&1 > /dev/null\n")
|
88
|
-
script.write("killall -9 mDNSResponderHelper 2>&1 > /dev/null\n")
|
89
|
-
script.close
|
90
|
-
|
91
|
-
Kernel.system("/usr/bin/osascript -e 'do shell script \"sh #{script.path}\" with administrator privileges' 2>&1 > /dev/null")
|
92
|
-
script.unlink
|
93
|
-
end
|
94
|
-
|
95
|
-
# Checks if we are running on MacOS X.
|
96
|
-
#
|
97
|
-
# System services are only available on that platform.
|
98
|
-
#
|
99
|
-
# @return [Boolean] `true` if the current platform is MacOS X, `false` otherwise.
|
100
|
-
def is_osx?
|
101
|
-
::RbConfig::CONFIG['host_os'] =~ /^darwin/
|
102
|
-
end
|
103
|
-
|
104
|
-
# Starts the server in background.
|
105
|
-
#
|
106
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
107
|
-
def action_start
|
108
|
-
get_logger.info(i18n.starting)
|
109
|
-
|
110
|
-
if !Process.respond_to?(:fork) then
|
111
|
-
logger.warn(i18n.no_fork)
|
112
|
-
@config.foreground = true
|
113
|
-
elsif @command.options[:foreground].value then
|
114
|
-
@config.foreground = true
|
115
|
-
end
|
116
|
-
|
117
|
-
@config.foreground ? perform_server : RExec::Daemon::Controller.start(self.class)
|
118
|
-
true
|
119
|
-
end
|
120
|
-
|
121
|
-
# Stops the server in background.
|
122
|
-
#
|
123
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
124
|
-
def action_stop
|
125
|
-
RExec::Daemon::Controller.stop(self.class)
|
126
|
-
true
|
127
|
-
end
|
128
|
-
|
129
|
-
# Restarts the server in background.
|
130
|
-
#
|
131
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
132
|
-
def action_restart
|
133
|
-
action_stop
|
134
|
-
action_start
|
135
|
-
true
|
136
|
-
end
|
137
|
-
|
138
|
-
# Shows the status of the server
|
139
|
-
#
|
140
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
141
|
-
def action_status
|
142
|
-
daemon = self.class
|
143
|
-
status = RExec::Daemon::ProcessFile.status(daemon)
|
144
|
-
pid = RExec::Daemon::ProcessFile.recall(daemon).to_s
|
145
|
-
status = :crashed if status == :unknown && daemon.crashed?
|
146
|
-
|
147
|
-
if status == :running then
|
148
|
-
logger.info(replace_markers(i18n.status_running(pid)))
|
149
|
-
elsif
|
150
|
-
logger.info(replace_markers(i18n.send("status_#{status}")))
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# Adds aliases to an interface.
|
155
|
-
#
|
156
|
-
# @param options [Hash] The options provided by the user.
|
157
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
158
|
-
def action_add(options)
|
159
|
-
manage_aliases(:add, i18n.add_empty, options)
|
160
|
-
end
|
161
|
-
|
162
|
-
# Removes aliases from an interface.
|
163
|
-
#
|
164
|
-
# @param options [Hash] The options provided by the user.
|
165
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
166
|
-
def action_remove(options)
|
167
|
-
manage_aliases(:remove, i18n.remove_empty, options)
|
168
|
-
end
|
169
|
-
|
170
|
-
|
171
|
-
# Installs the application into the autolaunch.
|
172
|
-
#
|
173
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
174
|
-
def action_install
|
175
|
-
manage_installation(launch_agent_path, resolver_path, :create_resolver, :create_agent, :load_agent)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Uninstalls the application from the autolaunch.
|
179
|
-
#
|
180
|
-
# @return [Boolean] `true` if action succeeded, `false` otherwise.
|
181
|
-
def action_uninstall
|
182
|
-
manage_installation(launch_agent_path, resolver_path, :delete_resolver, :unload_agent, :delete_agent)
|
183
|
-
end
|
184
|
-
|
185
|
-
private
|
186
|
-
# Manages a OSX agent.
|
187
|
-
#
|
188
|
-
# @param launch_agent [String] The agent path.
|
189
|
-
# @param resolver_path [String] The resolver path.
|
190
|
-
# @param first_operation [Symbol] The first operation to execute.
|
191
|
-
# @param second_operation [Symbol] The second operation to execute.
|
192
|
-
# @param third_operation [Symbol] The third operation to execute.
|
193
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
194
|
-
def manage_installation(launch_agent, resolver_path, first_operation, second_operation, third_operation)
|
195
|
-
rv = check_agent_available
|
196
|
-
|
197
|
-
logger.warn(replace_markers(i18n.admin_privileges_warning))
|
198
|
-
rv = send(first_operation, launch_agent, resolver_path) if rv
|
199
|
-
rv = send(second_operation, launch_agent, resolver_path) if rv
|
200
|
-
rv = send(third_operation, launch_agent, resolver_path) if rv
|
201
|
-
dns_update
|
202
|
-
rv
|
203
|
-
end
|
204
|
-
|
205
|
-
# Deletes a file
|
206
|
-
#
|
207
|
-
# @param file [String] The file to delete.
|
208
|
-
# @param before_message [Symbol] The message to show before deleting.
|
209
|
-
# @param error_message [Symbol] The message to show in case of errors.
|
210
|
-
# @return [Boolean] `true` if the file have been deleted, `false` otherwise.
|
211
|
-
def delete_file(file, before_message, error_message)
|
212
|
-
begin
|
213
|
-
logger.info(i18n.send(before_message, file))
|
214
|
-
::File.delete(file)
|
215
|
-
true
|
216
|
-
rescue
|
217
|
-
logger.warn(i18n.send(error_message))
|
218
|
-
false
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Checks if agent is enabled (that is, we are on OSX).
|
223
|
-
#
|
224
|
-
# @return [Boolean] `true` if the agent is enabled, `false` otherwise.
|
225
|
-
def check_agent_available
|
226
|
-
rv = true
|
227
|
-
if !is_osx? then
|
228
|
-
logger.fatal(i18n.no_agent)
|
229
|
-
rv = false
|
230
|
-
end
|
231
|
-
|
232
|
-
rv
|
233
|
-
end
|
234
|
-
|
235
|
-
# Creates a OSX resolver.
|
236
|
-
#
|
237
|
-
# @param resolver_path [String] The resolver path.
|
238
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
239
|
-
def create_resolver(_, resolver_path)
|
240
|
-
begin
|
241
|
-
logger.info(replace_markers(i18n.resolver_creating(resolver_path)))
|
242
|
-
|
243
|
-
script = Tempfile.new("devdnsd-install-script")
|
244
|
-
script.write("mkdir -p '#{File.dirname(resolver_path)}'\nrm -rf '#{resolver_path}'\n")
|
245
|
-
script.write("echo 'nameserver 127.0.0.1\\nport #{@config.port}' >> '#{resolver_path}'")
|
246
|
-
script.close
|
247
|
-
|
248
|
-
Kernel.system("/usr/bin/osascript -e 'do shell script \"sh #{script.path}\" with administrator privileges' 2>&1 > /dev/null")
|
249
|
-
script.unlink
|
250
|
-
true
|
251
|
-
rescue Exception
|
252
|
-
logger.error(i18n.resolver_creating_error)
|
253
|
-
false
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
# Deletes a OSX resolver.
|
258
|
-
#
|
259
|
-
# @param resolver_path [String] The resolver path.
|
260
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
261
|
-
def delete_resolver(_, resolver_path)
|
262
|
-
begin
|
263
|
-
logger.info(i18n.resolver_deleting(resolver_path))
|
264
|
-
Kernel.system("/usr/bin/osascript -e 'do shell script \"rm #{resolver_path}\" with administrator privileges' 2>&1 > /dev/null")
|
265
|
-
true
|
266
|
-
rescue Exception
|
267
|
-
logger.warn(i18n.resolver_deleting_error)
|
268
|
-
false
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
# Creates a OSX system agent.
|
273
|
-
#
|
274
|
-
# @param launch_agent [String] The agent path.
|
275
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
276
|
-
def create_agent(launch_agent, _)
|
277
|
-
begin
|
278
|
-
logger.info(replace_markers(i18n.agent_creating(launch_agent)))
|
279
|
-
program, args = prepare_agent
|
280
|
-
|
281
|
-
::File.open(launch_agent, "w") {|f|
|
282
|
-
f.write({"KeepAlive" => true, "Label" => "it.cowtech.devdnsd", "Program" => program, "ProgramArguments" => args, "RunAtLoad" => true}.to_plist)
|
283
|
-
f.flush
|
284
|
-
}
|
285
|
-
|
286
|
-
true
|
287
|
-
rescue
|
288
|
-
logger.error(i18n.agent_creating_error)
|
289
|
-
false
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
# Prepares arguments for an agent.
|
294
|
-
#
|
295
|
-
# @return [Array] The arguments for an agent.
|
296
|
-
def prepare_agent
|
297
|
-
[
|
298
|
-
(::Pathname.new(Dir.pwd) + $0).to_s,
|
299
|
-
(ARGV ? ARGV[0, ARGV.length - 1] : [])
|
300
|
-
]
|
301
|
-
end
|
302
|
-
|
303
|
-
# Deletes a OSX system agent.
|
304
|
-
#
|
305
|
-
# @param launch_agent [String] The agent path.
|
306
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
307
|
-
def delete_agent(launch_agent, _)
|
308
|
-
delete_file(launch_agent, :agent_deleting, :agent_deleting_error)
|
309
|
-
end
|
310
|
-
|
311
|
-
# Loads a OSX system agent.
|
312
|
-
#
|
313
|
-
# @param launch_agent [String] The agent path.
|
314
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
315
|
-
def load_agent(launch_agent, _)
|
316
|
-
toggle_agent(launch_agent, "load", :agent_loading, :agent_loading_error, :error)
|
317
|
-
end
|
318
|
-
|
319
|
-
# Unloads a OSX system agent.
|
320
|
-
#
|
321
|
-
# @param launch_agent [String] The agent path.
|
322
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
323
|
-
def unload_agent(launch_agent, _)
|
324
|
-
toggle_agent(launch_agent, "unload", :agent_unloading, :agent_unloading_error, :warn)
|
325
|
-
end
|
326
|
-
|
327
|
-
# Loads or unloads a OSX system agent.
|
328
|
-
#
|
329
|
-
# @param launch_agent [String] The agent path.
|
330
|
-
# @param operation [String] The operation to perform. Can be `load` or `unload`.
|
331
|
-
# @param info_message [Symbol] The message to show in case of errors.
|
332
|
-
# @param error_message [Symbol] The message to show in case of errors.
|
333
|
-
# @param error_level [Symbol] The error level to show. Can be `:warn` or `:error`.
|
334
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
335
|
-
def toggle_agent(launch_agent, operation, info_message, error_message, error_level)
|
336
|
-
begin
|
337
|
-
logger.info(i18n.send(info_message, launch_agent))
|
338
|
-
execute_command("launchctl #{operation} -w \"#{launch_agent}\" > /dev/null 2>&1")
|
339
|
-
true
|
340
|
-
rescue
|
341
|
-
logger.send(error_level, i18n.send(error_message))
|
342
|
-
false
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
# Replaces markers in a log message.
|
347
|
-
#
|
348
|
-
# @param message [String] The message to process.
|
349
|
-
# @return [String] The processed message.
|
350
|
-
def replace_markers(message)
|
351
|
-
@command.application.console.replace_markers(message)
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
# Methods to handle interfaces aliases.
|
356
|
-
module Aliases
|
357
|
-
extend ActiveSupport::Concern
|
358
|
-
|
359
|
-
# Manages aliases.
|
360
|
-
#
|
361
|
-
# @param operation [Symbol] The type of operation. Can be `:add` or `:remove`.
|
362
|
-
# @param message [String] The message to show if no addresses are found.
|
363
|
-
# @param options [Hash] The options provided by the user.
|
364
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
365
|
-
def manage_aliases(operation, message, options)
|
366
|
-
config = self.config
|
367
|
-
options.each { |k, v| config.send("#{k}=", v) if config.respond_to?("#{k}=") }
|
368
|
-
|
369
|
-
addresses = compute_addresses
|
370
|
-
|
371
|
-
if addresses.present? then
|
372
|
-
# Now, for every address, call the command
|
373
|
-
addresses.all? {|address| manage_address(operation, address, options[:dry_run]) }
|
374
|
-
else
|
375
|
-
@logger.error(message)
|
376
|
-
false
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
# Adds or removes an alias from the interface.
|
381
|
-
#
|
382
|
-
# @param type [Symbol] The operation to execute. Can be `:add` or `:remove`.
|
383
|
-
# @param address [String] The address to manage.
|
384
|
-
# @param dry_run [Boolean] If only show which modifications will be done.
|
385
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
386
|
-
def manage_address(type, address, dry_run = false)
|
387
|
-
locale = i18n
|
388
|
-
rv, command, prefix = setup_management(type, address)
|
389
|
-
|
390
|
-
# Now execute
|
391
|
-
if rv then
|
392
|
-
if !dry_run then
|
393
|
-
execute_manage(command, prefix, type, address, self.config)
|
394
|
-
else
|
395
|
-
log_management(:dry_run, prefix, type, locale.remove, locale.add, address, config)
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
rv
|
400
|
-
end
|
401
|
-
|
402
|
-
# Computes the list of address to manage.
|
403
|
-
#
|
404
|
-
# @param type [Symbol] The type of addresses to consider. Valid values are `:ipv4`, `:ipv6`, otherwise all addresses are considered.
|
405
|
-
# @return [Array] The list of addresses to add or remove from the interface.
|
406
|
-
def compute_addresses(type = :all)
|
407
|
-
config = self.config
|
408
|
-
config.addresses.present? ? filter_addresses(config, type) : generate_addresses(config, type)
|
409
|
-
end
|
410
|
-
|
411
|
-
# Checks if an address is a valid IPv4 address.
|
412
|
-
#
|
413
|
-
# @param address [String] The address to check.
|
414
|
-
# @return [Boolean] `true` if the address is a valid IPv4 address, `false` otherwise.
|
415
|
-
def is_ipv4?(address)
|
416
|
-
address = address.ensure_string
|
417
|
-
|
418
|
-
mo = /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/.match(address)
|
419
|
-
(mo && mo.captures.all? {|i| i.to_i < 256}) ? true : false
|
420
|
-
end
|
421
|
-
|
422
|
-
# Checks if an address is a valid IPv6 address.
|
423
|
-
#
|
424
|
-
# @param address [String] The address to check.
|
425
|
-
# @return [Boolean] `true` if the address is a valid IPv6 address, `false` otherwise.
|
426
|
-
def is_ipv6?(address)
|
427
|
-
address = address.ensure_string
|
428
|
-
|
429
|
-
catch(:valid) do
|
430
|
-
# IPv6 (normal)
|
431
|
-
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*\Z/ =~ address
|
432
|
-
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
|
433
|
-
throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*)?\Z/ =~ address
|
434
|
-
# IPv6 (IPv4 compat)
|
435
|
-
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:/ =~ address && is_ipv4?($')
|
436
|
-
throw(:valid, true) if /\A[\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && is_ipv4?($')
|
437
|
-
throw(:valid, true) if /\A::([\dA-Fa-f]{1,4}(:[\dA-Fa-f]{1,4})*:)?/ =~ address && is_ipv4?($')
|
438
|
-
|
439
|
-
false
|
440
|
-
end
|
441
|
-
end
|
442
|
-
|
443
|
-
private
|
444
|
-
# Setups management.
|
445
|
-
#
|
446
|
-
# @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
|
447
|
-
# @param address [String] The address to manage.
|
448
|
-
# @return [Array] A list of parameters for the management.
|
449
|
-
def setup_management(type, address)
|
450
|
-
begin
|
451
|
-
@addresses ||= compute_addresses
|
452
|
-
length = @addresses.length
|
453
|
-
length_s = length.to_s.length
|
454
|
-
[true, build_command(type, address), "{mark=blue}[{mark=bright white}#{((@addresses.index(address) || 0) + 1).indexize(length_s)}{mark=reset blue}/{/mark}#{length}{/mark}]{/mark}"]
|
455
|
-
rescue ArgumentError
|
456
|
-
[false]
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
# Filters a list of addresses to return just certain type(s).
|
461
|
-
#
|
462
|
-
# @param config [Configuration] The current configuration.
|
463
|
-
# @param type [Symbol] The type of addresses to return.
|
464
|
-
# @return [Array] A list of IPs.
|
465
|
-
def filter_addresses(config, type)
|
466
|
-
filters = [:ipv4, :ipv6].select {|i| type == i || type == :all }.compact
|
467
|
-
config.addresses.select { |address| filters.any? {|filter| send("is_#{filter}?", address) } }.compact.uniq
|
468
|
-
end
|
469
|
-
|
470
|
-
# Generates a list of addresses which are immediate successors of a start address.
|
471
|
-
#
|
472
|
-
# @param config [Configuration] The current configuration.
|
473
|
-
# @param type [Symbol] The type of addresses to return.
|
474
|
-
# @return [Array] A list of IPs.
|
475
|
-
def generate_addresses(config, type)
|
476
|
-
begin
|
477
|
-
ip = IPAddr.new(config.start_address.ensure_string)
|
478
|
-
raise ArgumentError if type != :all && !ip.send("#{type}?")
|
479
|
-
|
480
|
-
[config.aliases, 1].max.times.map {|_|
|
481
|
-
current = ip
|
482
|
-
ip = ip.succ
|
483
|
-
current
|
484
|
-
}
|
485
|
-
rescue ArgumentError
|
486
|
-
[]
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
|
-
# Builds the command to execute.
|
491
|
-
#
|
492
|
-
# @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
|
493
|
-
# @param address [String] The address to manage.
|
494
|
-
# @return [String] The command to execute.
|
495
|
-
def build_command(type, address)
|
496
|
-
Mustache.render(config.send((type == :remove) ? :remove_command : :add_command), {interface: config.interface, address: address.to_s}) + " > /dev/null 2>&1"
|
497
|
-
end
|
498
|
-
|
499
|
-
# Executes management.
|
500
|
-
#
|
501
|
-
# @param command [String] The command to execute.
|
502
|
-
# @param prefix [String] The prefix to apply to the message.
|
503
|
-
# @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
|
504
|
-
# @param address [String] The address that will be managed.
|
505
|
-
# @param config [Configuration] The current configuration.
|
506
|
-
# @return [Boolean] `true` if operation succeeded, `false` otherwise.
|
507
|
-
def execute_manage(command, prefix, type, address, config)
|
508
|
-
locale = i18n
|
509
|
-
log_management(:run, prefix, type, locale.removing, locale.adding, address, config)
|
510
|
-
rv = execute_command(command)
|
511
|
-
labels = (type == :remove ? [locale.remove, locale.from] : [locale.add, locale.to])
|
512
|
-
@logger.error(replace_markers(locale.general_error(labels[0], address, labels[1], config.interface))) if !rv
|
513
|
-
rv
|
514
|
-
end
|
515
|
-
|
516
|
-
# Logs an operation.
|
517
|
-
#
|
518
|
-
# @param message [Symbol] The message to print.
|
519
|
-
# @param prefix [String] The prefix to apply to the message.
|
520
|
-
# @param type [Symbol] The type of operation. Can be `:add` or `:remove`.
|
521
|
-
# @param remove_label [String] The label to use for removing.
|
522
|
-
# @param add_label [String] The label to use for adding.
|
523
|
-
# @param address [String] The address that will be managed.
|
524
|
-
# @param config [Configuration] The current configuration.
|
525
|
-
def log_management(message, prefix, type, remove_label, add_label, address, config)
|
526
|
-
locale = i18n
|
527
|
-
labels = (type == :remove ? [remove_label, locale.from] : [add_label, locale.to])
|
528
|
-
@logger.info(replace_markers(i18n.send(message, prefix, labels[0], address, labels[1], config.interface)))
|
529
|
-
end
|
530
|
-
end
|
531
|
-
|
532
|
-
# Methods to process requests.
|
533
|
-
module Server
|
534
|
-
# Starts the DNS server.
|
535
|
-
#
|
536
|
-
# @return [Object] The result of stop callbacks.
|
537
|
-
def perform_server
|
538
|
-
application = self
|
539
|
-
|
540
|
-
RubyDNS::run_server(listen: build_listen_interfaces) do
|
541
|
-
self.logger = application.logger
|
542
|
-
|
543
|
-
match(/.+/, DevDNSd::Application::ANY_CLASSES) do |transaction, match_data|
|
544
|
-
application.config.rules.each { |rule| application.process_rule_in_classes(rule, match_data, transaction) } # During debugging, wrap the inside of the block with a begin rescue and PRINT the exception because RubyDNS hides it.
|
545
|
-
end
|
546
|
-
|
547
|
-
# Default DNS handler and event handlers
|
548
|
-
otherwise { |transaction| transaction.failure!(:NXDomain) }
|
549
|
-
on(:start) { application.on_start }
|
550
|
-
on(:stop) { application.on_stop }
|
551
|
-
end
|
552
|
-
end
|
553
|
-
|
554
|
-
# Processes a DNS rule.
|
555
|
-
#
|
556
|
-
# @param rule [Rule] The rule to process.
|
557
|
-
# @param type [Symbol] The type of the query.
|
558
|
-
# @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
|
559
|
-
# @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
|
560
|
-
def process_rule(rule, type, match_data, transaction)
|
561
|
-
reply, type = perform_process_rule(rule, type, match_data, transaction)
|
562
|
-
logger.debug(reply ? i18n.reply(reply, type) : i18n.no_reply)
|
563
|
-
|
564
|
-
if reply then
|
565
|
-
transaction.respond!(*finalize_reply(reply, rule, type))
|
566
|
-
elsif reply.is_a?(FalseClass) then
|
567
|
-
false
|
568
|
-
else
|
569
|
-
nil
|
570
|
-
end
|
571
|
-
end
|
572
|
-
|
573
|
-
# Processes a rule against a set of DNS resource classes.
|
574
|
-
#
|
575
|
-
# @param rule [Rule] The rule to process.
|
576
|
-
# @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
|
577
|
-
# @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
|
578
|
-
def process_rule_in_classes(rule, match_data, transaction)
|
579
|
-
# Get the subset of handled class that is valid for the rule
|
580
|
-
resource_classes = DevDNSd::Application::ANY_CLASSES & rule.resource_class.ensure_array
|
581
|
-
resource_classes = resource_classes & [transaction.resource_class] if transaction.resource_class != DevDNSd::Application::ANY_REQUEST
|
582
|
-
|
583
|
-
if resource_classes.present? then
|
584
|
-
resource_classes.each do |resource_class| # Now for every class
|
585
|
-
matches = rule.match_host(match_data[0])
|
586
|
-
process_rule(rule, resource_class, rule.is_regexp? ? matches : nil, transaction) if matches
|
587
|
-
end
|
588
|
-
end
|
589
|
-
end
|
590
|
-
|
591
|
-
private
|
592
|
-
# Builds the list of listening interfaces.
|
593
|
-
#
|
594
|
-
# @return [Array] Array of addresses.
|
595
|
-
def build_listen_interfaces
|
596
|
-
port = @config.port.to_integer
|
597
|
-
@config.bind_addresses.ensure_array {|address| [:udp, address, port] }
|
598
|
-
end
|
599
|
-
|
600
|
-
# Performs the processing of a rule.
|
601
|
-
#
|
602
|
-
# @param rule [Rule] The rule to process.
|
603
|
-
# @param type [Symbol] The type of query.
|
604
|
-
# @param match_data [MatchData|nil] If the rule pattern was a Regexp, then this holds the match data, otherwise `nil` is passed.
|
605
|
-
# @param transaction [RubyDNS::Transaction] The current DNS transaction (http://rubydoc.info/gems/rubydns/RubyDNS/Transaction).
|
606
|
-
# @return [Array] The type and reply to the query.
|
607
|
-
def perform_process_rule(rule, type, match_data, transaction)
|
608
|
-
type = DevDNSd::Rule.resource_class_to_symbol(type)
|
609
|
-
reply = !rule.block.nil? ? rule.block.call(match_data, type, transaction) : rule.reply
|
610
|
-
reply = match_data[0].gsub(rule.match, reply.gsub("$", "\\")) if rule.match.is_a?(::Regexp) && reply && match_data && match_data[0]
|
611
|
-
|
612
|
-
logger.debug(i18n.match(rule.match, type))
|
613
|
-
[reply, type]
|
614
|
-
end
|
615
|
-
|
616
|
-
# Finalizes a query to return to the client.
|
617
|
-
#
|
618
|
-
# @param reply [String] The reply to send to the client.
|
619
|
-
# @param rule [Rule] The rule to process.
|
620
|
-
# @param type [Symbol] The type of query.
|
621
|
-
def finalize_reply(reply, rule, type)
|
622
|
-
rv = []
|
623
|
-
rv << rule.options.delete(:priority).to_integer(10) if type == :MX
|
624
|
-
rv << ([:A, :AAAA].include?(type) ? reply : Resolv::DNS::Name.create(reply))
|
625
|
-
rv << rule.options.merge({resource_class: DevDNSd::Rule.symbol_to_resource_class(type, @locale), ttl: validate_ttl(rule.options.delete(:ttl))})
|
626
|
-
rv
|
627
|
-
end
|
628
|
-
|
629
|
-
# Validates a TTL.
|
630
|
-
#
|
631
|
-
# @param current [Fixnum] The current value.
|
632
|
-
# @param default [Fixnum] The value to return if current is not valid.
|
633
|
-
# @return [Fixnum] The validated TTL.
|
634
|
-
def validate_ttl(current, default = 300)
|
635
|
-
current = current.to_integer
|
636
|
-
current > 0 ? current : default
|
637
|
-
end
|
638
|
-
end
|
639
|
-
end
|
640
|
-
|
641
9
|
# The main DevDNSd application.
|
642
10
|
#
|
643
11
|
# @attribute [r] config
|
644
12
|
# @return [Configuration] The {Configuration Configuration} of this application.
|
13
|
+
# @attribute [r] server
|
14
|
+
# @return [RubyDNS::RuleBasedServer] The server of this application.
|
645
15
|
# @attribute [r] command
|
646
16
|
# @return [Bovem::Command] The Bovem command.
|
647
17
|
# @attribute logger
|
648
18
|
# @return [Bovem::Logger] The logger for this application.
|
649
19
|
# @attribute [r] locale
|
650
20
|
# @return [Symbol|nil] The current application locale.
|
651
|
-
|
21
|
+
# @attribute [r] :i18n
|
22
|
+
# @return [Bovem::I18n] A localizer object.
|
23
|
+
class Application < Process::Daemon
|
652
24
|
# Class for ANY DNS request.
|
653
25
|
ANY_REQUEST = Resolv::DNS::Resource::IN::ANY
|
654
26
|
|
655
27
|
# List of classes handled in case of DNS request with resource class ANY.
|
656
|
-
ANY_CLASSES = [
|
28
|
+
ANY_CLASSES = [
|
29
|
+
Resolv::DNS::Resource::IN::A, Resolv::DNS::Resource::IN::AAAA, Resolv::DNS::Resource::IN::ANY, Resolv::DNS::Resource::IN::CNAME,
|
30
|
+
Resolv::DNS::Resource::IN::HINFO, Resolv::DNS::Resource::IN::MINFO, Resolv::DNS::Resource::IN::MX, Resolv::DNS::Resource::IN::NS,
|
31
|
+
Resolv::DNS::Resource::IN::PTR, Resolv::DNS::Resource::IN::SOA, Resolv::DNS::Resource::IN::TXT
|
32
|
+
].freeze
|
657
33
|
|
658
|
-
include
|
659
|
-
include DevDNSd::
|
660
|
-
include DevDNSd::
|
661
|
-
include DevDNSd::
|
34
|
+
include DevDNSd::Aliases
|
35
|
+
include DevDNSd::Server
|
36
|
+
include DevDNSd::System
|
37
|
+
include DevDNSd::OSX
|
662
38
|
|
663
39
|
attr_reader :config
|
664
40
|
attr_reader :command
|
665
41
|
attr_accessor :logger
|
666
42
|
attr_reader :locale
|
43
|
+
attr_reader :i18n
|
44
|
+
attr_reader :server
|
667
45
|
|
668
46
|
# Creates a new application.
|
669
47
|
#
|
670
48
|
# @param command [Bovem::Command] The current Bovem command.
|
671
49
|
# @param locale [Symbol] The locale to use for the application.
|
672
50
|
def initialize(command, locale)
|
673
|
-
|
674
|
-
self.i18n = locale
|
675
|
-
|
51
|
+
@i18n = Bovem::I18n.new(locale, root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/")
|
676
52
|
@locale = locale
|
677
53
|
@command = command
|
678
|
-
options =
|
54
|
+
options = load_options
|
679
55
|
|
680
56
|
# Setup logger
|
681
57
|
create_logger(options)
|
@@ -683,14 +59,22 @@ module DevDNSd
|
|
683
59
|
# Open configuration
|
684
60
|
read_configuration(options)
|
685
61
|
|
62
|
+
super(working_directory)
|
686
63
|
self
|
687
64
|
end
|
688
65
|
|
66
|
+
# Stops the server.
|
67
|
+
def shutdown
|
68
|
+
server.actors.first.links.each(&:terminate) if server
|
69
|
+
end
|
70
|
+
|
689
71
|
# Gets the current logger of the application.
|
690
72
|
#
|
73
|
+
# @param force [Boolean] If to force recreation of the logger.
|
691
74
|
# @return [Logger] The current logger of the application.
|
692
|
-
def
|
693
|
-
@logger
|
75
|
+
def logger(force = false)
|
76
|
+
@logger = nil if force
|
77
|
+
@logger ||= Bovem::Logger.create(@config.foreground ? $stdout : @config.log_file, level: @config.log_level, formatter: @log_formatter)
|
694
78
|
end
|
695
79
|
|
696
80
|
# This method is called when the server starts. By default is a no-op.
|
@@ -726,85 +110,90 @@ module DevDNSd
|
|
726
110
|
|
727
111
|
# Stops the application.
|
728
112
|
def self.quit
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
end
|
113
|
+
instance.shutdown
|
114
|
+
rescue
|
115
|
+
Kernel.exit(1)
|
733
116
|
end
|
734
117
|
|
735
118
|
# Check if the current implementation supports DevDNSd.
|
736
119
|
def self.check_ruby_implementation
|
737
|
-
if defined?(JRuby)
|
738
|
-
Kernel.puts(
|
120
|
+
if defined?(JRuby)
|
121
|
+
Kernel.puts(Bovem::I18n.new(root: "devdnsd", path: ::Pathname.new(::File.dirname(__FILE__)).to_s + "/../../locales/").no_jruby)
|
739
122
|
Kernel.exit(0)
|
740
123
|
end
|
741
124
|
end
|
742
125
|
|
743
126
|
private
|
744
|
-
# Creates a logger.
|
745
|
-
#
|
746
|
-
# @param options [Hash] The configuration to use.
|
747
|
-
def create_logger(options)
|
748
|
-
warn_failure = false
|
749
|
-
orig_file = file = Bovem::Logger.get_real_file(options["log_file"]) || Bovem::Logger.default_file
|
750
127
|
|
751
|
-
|
752
|
-
|
128
|
+
# :nodoc:
|
129
|
+
def load_options
|
130
|
+
@command.application.get_options.reject { |_, v| v.nil? }.merge(@command.get_options.reject { |_, v| v.nil? })
|
131
|
+
end
|
753
132
|
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
options["log_file"] = "STDOUT"
|
759
|
-
file = $stdout
|
760
|
-
warn_failure = true
|
761
|
-
end
|
762
|
-
end
|
133
|
+
# :nodoc:
|
134
|
+
def create_logger(options)
|
135
|
+
warn_failure = false
|
136
|
+
orig_file = file = Bovem::Logger.get_real_file(options["log_file"]) || Bovem::Logger.default_file
|
763
137
|
|
764
|
-
|
765
|
-
|
766
|
-
@logger
|
138
|
+
if file.is_a?(String)
|
139
|
+
file, warn_failure = load_logger(File.absolute_path(File.expand_path(file)), options)
|
767
140
|
end
|
768
141
|
|
769
|
-
|
770
|
-
|
771
|
-
# @param options [Hash] The configuration to read.
|
772
|
-
def read_configuration(options)
|
773
|
-
path = ::File.absolute_path(File.expand_path(options["configuration"]))
|
142
|
+
finalize_create_logger(file, orig_file, warn_failure)
|
143
|
+
end
|
774
144
|
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
ensure_directory_for(@config.pid_file)
|
145
|
+
# :nodoc:
|
146
|
+
def load_logger(file, options)
|
147
|
+
warn_failure = false
|
779
148
|
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
149
|
+
begin
|
150
|
+
FileUtils.mkdir_p(File.dirname(file))
|
151
|
+
@logger = Bovem::Logger.create(file, level: ::Logger::INFO)
|
152
|
+
rescue
|
153
|
+
options["log_file"] = "STDOUT"
|
154
|
+
file = $stdout
|
155
|
+
warn_failure = true
|
786
156
|
end
|
787
157
|
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
end
|
798
|
-
end
|
158
|
+
[file, warn_failure]
|
159
|
+
end
|
160
|
+
|
161
|
+
# :nodoc:
|
162
|
+
def finalize_create_logger(file, orig_file, warn_failure)
|
163
|
+
@logger = Bovem::Logger.create(file, level: ::Logger::INFO)
|
164
|
+
@logger.warn(replace_markers(i18n.logging_failed(orig_file))) if @logger && warn_failure
|
165
|
+
@logger
|
166
|
+
end
|
799
167
|
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
168
|
+
# :nodoc:
|
169
|
+
def read_configuration(options)
|
170
|
+
path = ::File.absolute_path(File.expand_path(options["configuration"]))
|
171
|
+
|
172
|
+
begin
|
173
|
+
@config = DevDNSd::Configuration.new(path, options, @logger)
|
174
|
+
ensure_directory_for(@config.log_file) if @config.log_file.is_a?(String)
|
175
|
+
ensure_directory_for(@config.pid_file)
|
176
|
+
@logger = logger(true)
|
177
|
+
rescue Bovem::Errors::InvalidConfiguration => e
|
178
|
+
log_failed_configuration(path, e)
|
179
|
+
shutdown
|
808
180
|
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# :nodoc:
|
184
|
+
def ensure_directory_for(path)
|
185
|
+
FileUtils.mkdir_p(File.dirname(path))
|
186
|
+
rescue
|
187
|
+
@logger.warn(replace_markers(i18n.invalid_directory(File.dirname(path))))
|
188
|
+
shutdown
|
189
|
+
Kernel.exit(1)
|
190
|
+
end
|
191
|
+
|
192
|
+
# :nodoc:
|
193
|
+
def log_failed_configuration(path, exception)
|
194
|
+
logger = Bovem::Logger.create($stderr)
|
195
|
+
logger.fatal(exception.message)
|
196
|
+
logger.warn(replace_markers(i18n.application_create_config(path)))
|
197
|
+
end
|
809
198
|
end
|
810
199
|
end
|