diff --git a/README.md b/README.md index dff837a..402b123 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ dory: # doesn't exist. dnsmasq: enabled: true + autostart: false # true to set the container to restart unless stopped (e.g. after reboot) domains: # array of domains that will be resolved to the specified address - domain: docker # you can set '#' for a wildcard address: 127.0.0.1 # return for queries against the domain @@ -120,6 +121,7 @@ dory: service_start_delay: 5 # seconds to wait after restarting systemd services nginx_proxy: enabled: true + autostart: false # true to set the container to restart unless stopped (e.g. after reboot) container_name: dory_dinghy_http_proxy https_enabled: true ssl_certs_dir: '' # leave as empty string to use default certs @@ -130,6 +132,15 @@ dory: enabled: true nameserver: 127.0.0.1 port: 53 # port where the nameserver listens. On linux it must be 53 + + aliases: + enabled: true + autostart: false # true to make the aliases persist across restarts + # addresses: + # To allow containers to talk to each other by their names, + # set dnsmasq.domains[].address to a private IP and add it here + # (Supported on MacOS only) + # - ``` #### Upgrading existing config file diff --git a/bin/dory b/bin/dory index 8925faa..720421a 100755 --- a/bin/dory +++ b/bin/dory @@ -362,8 +362,22 @@ class DoryBin < Thor puts "resolv disabled in config file".yellow end end + + if services.include?('aliases') + if aliases_enabled?(settings) + puts "Aliases enabled in config file".green if options[:verbose] + if Dory::Alias.configure + puts "Successfully created loopback aliases".green + else + puts "Error configuring aliases".red + end + else + puts "Aliases disabled in config file".yellow + end + end end + def exec_status(_options) puts "Reading settings file at '#{Dory::Config.filename}'".green if options[:verbose] settings = Dory::Config.settings @@ -391,6 +405,17 @@ class DoryBin < Thor else puts "[*] Resolv is not configured".red end + + if !aliases_enabled?(settings) + puts "[*] Aliases are disabled in config file".yellow + else + addresses = Dory::Alias::check + if addresses.any? + puts "[*] Aliases: configured for address(es) #{addresses.join(", ")}".green + else + puts "[*] No aliases are configured".red + end + end end def exec_down(options, services) @@ -400,6 +425,18 @@ class DoryBin < Thor puts "Reading settings file at '#{Dory::Config.filename}'".green if options[:verbose] settings = Dory::Config.settings + if services.include?('aliases') + if Dory::Alias.clean + if aliases_enabled?(settings) + puts "Loopback aliases removed".green + else + puts "Aliases disabled in config file".yellow + end + else + puts "Unable to remove loopback aliases".red + end + end + if services.include?('resolv') if Dory::Resolv.clean if resolv_enabled?(settings) @@ -471,12 +508,16 @@ class DoryBin < Thor settings[:dory][:resolv][:enabled] end + def aliases_enabled?(settings) + settings[:dory][:aliases][:enabled] + end + def resolv_disabled?(settings) !resolv_enabled?(settings) end def valid_services - %w[proxy dns resolv] + %w[proxy dns resolv aliases] end def canonical_service(service) @@ -488,7 +529,8 @@ class DoryBin < Thor 'dns' => 'dns', 'dnsmasq' => 'dns', 'resolv' => 'resolv', - 'resolve' => 'resolv' + 'resolve' => 'resolv', + 'aliases' => 'aliases' }[service] end diff --git a/lib/dory/alias.rb b/lib/dory/alias.rb new file mode 100644 index 0000000..5d698a3 --- /dev/null +++ b/lib/dory/alias.rb @@ -0,0 +1,49 @@ +module Dory + module Alias + def self.get_module + return Dory::Alias::Macos if Os.macos? + Dory::Alias::Linux + end + + def self.configure() + puts "Requesting sudo to create loopback aliases".green + self.each_address lambda {|address| + puts "[DEBUG] creating loopback alias for #{address}" if Dory::Config.debug? + self.get_module.configure(address) + } + end + + def self.clean() + puts "Requesting sudo to remove loopback aliases".green + self.each_address lambda {|address| + puts "[DEBUG] creating loopback alias for #{address}" if Dory::Config.debug? + self.get_module.clean(address) + } + end + + def self.check() + addresses = [] + self.each_address lambda {|address| + addresses.push(address) if self.get_module.check(address) + } + addresses + end + + def self.each_address(cb) + success = true + self.addresses.each do |address| + begin + cb.call(address) + rescue Exception => error + puts error.message.red + success = false + end + end + success + end + + def self.addresses + Dory::Config.settings[:dory][:aliases][:addresses] || [] + end + end +end diff --git a/lib/dory/alias/linux.rb b/lib/dory/alias/linux.rb new file mode 100644 index 0000000..17c017c --- /dev/null +++ b/lib/dory/alias/linux.rb @@ -0,0 +1,14 @@ +module Dory + module Alias + module Linux + message = 'Loopback alias creation not implemented on Linux. Please create an alias using your distribution\'s supported configuration mechanism' + + def self.configure(address) + puts message.yellow + end + def self.clean(address) + puts message.yellow + end + end + end +end diff --git a/lib/dory/alias/macos.rb b/lib/dory/alias/macos.rb new file mode 100644 index 0000000..926cd35 --- /dev/null +++ b/lib/dory/alias/macos.rb @@ -0,0 +1,65 @@ +module Dory + module Alias + module Macos + def self.plist_file(address) + "/Library/LaunchDaemons/com.user.lo0-loopback-#{address.gsub('.', '-')}.plist" + end + + def self.plist(address) + <<-PLIST + + + + + Label + com.user.lo0-loopback + ProgramArguments + + /sbin/ifconfig + lo0 + alias + #{address} + + RunAtLoad + Nice + 10 + KeepAlive + + AbandonProcessGroup + + StandardErrorPath + /var/log/loopback-alias.log + StandardOutPath + /var/log/loopback-alias.log + + + PLIST + end + + def self.autostart + Dory::Config.settings[:dory][:aliases][:autostart] + end + + def self.configure(address) + Bash.run_command("sudo /sbin/ifconfig lo0 alias #{Shellwords.escape(address)} >/dev/null") + + if self.autostart && !File.exist?(self.plist_file(address)) + Bash.run_command("echo -e '#{Bash.escape_single_quotes(self.plist(address))}' | sudo /usr/bin/tee #{Shellwords.escape(self.plist_file(address))} >/dev/null") + end + end + + def self.clean(address) + Bash.run_command("sudo /sbin/ifconfig lo0 delete #{Shellwords.escape(address)} >/dev/null") + + if File.exist?(self.plist_file(address)) + Bash.run_command("sudo rm #{Shellwords.escape(self.plist_file(address))} >/dev/null") + end + end + + def self.check(address) + result = Bash.run_command("/sbin/ifconfig lo0") + result.stdout.include? "inet #{address}" + end + end + end +end diff --git a/lib/dory/config.rb b/lib/dory/config.rb index 7366586..cfb4335 100644 --- a/lib/dory/config.rb +++ b/lib/dory/config.rb @@ -61,6 +61,8 @@ def self.default_yaml enabled: true nameserver: 127.0.0.1 port: 53 # port where the nameserver listens. On linux it must be 53 + aliases: + enabled: false ).split("\n").map{|s| s.sub(' ' * 8, '')}.join("\n") end @@ -72,7 +74,7 @@ def self.settings(filename = self.filename) if File.exist?(filename) defaults = self.default_settings.dup config_file_settings = YAML.load_file(filename).with_indifferent_access - [:dnsmasq, :nginx_proxy, :resolv].each do |service| + [:dnsmasq, :nginx_proxy, :resolv, :aliases].each do |service| defaults[:dory][service].merge!(config_file_settings[:dory][service] || {}) end defaults[:dory][:debug] = config_file_settings[:dory][:debug] diff --git a/lib/dory/dnsmasq.rb b/lib/dory/dnsmasq.rb index dcfe20f..f56d961 100644 --- a/lib/dory/dnsmasq.rb +++ b/lib/dory/dnsmasq.rb @@ -110,11 +110,15 @@ def self.old_domain def self.old_address Dory::Config.settings[:dory][:dnsmasq][:address] end - + def self.address(addr) Dory::Dinghy.match?(addr) ? Dory::Dinghy.ip : addr end + def self.autostart + Dory::Config.settings[:dory][:dnsmasq][:autostart] + end + def self.domain_addr_arg_string if self.old_domain "#{Shellwords.escape(self.old_domain)} #{Shellwords.escape(self.address(self.old_address))}" @@ -127,6 +131,7 @@ def self.domain_addr_arg_string def self.run_command "docker run -d -p #{self.port}:#{self.port}/tcp -p #{self.port}:#{self.port}/udp " \ + "--restart=#{self.autostart ? 'unless-stopped' : 'no'} " \ "--name=#{Shellwords.escape(self.container_name)} " \ "--cap-add=NET_ADMIN #{Shellwords.escape(self.dnsmasq_image_name)} " \ "#{self.domain_addr_arg_string}" diff --git a/lib/dory/proxy.rb b/lib/dory/proxy.rb index a20b53e..db38fe4 100644 --- a/lib/dory/proxy.rb +++ b/lib/dory/proxy.rb @@ -42,8 +42,13 @@ def self.http_port Dory::Config.settings[:dory][:nginx_proxy][:port] end + def self.autostart + Dory::Config.settings[:dory][:nginx_proxy][:autostart] + end + def self.run_command "docker run -d -p #{http_port}:80 #{self.tls_arg} #{self.certs_arg} "\ + "--restart=#{self.autostart ? 'unless-stopped' : 'no'} " \ "-v /var/run/docker.sock:/tmp/docker.sock -e " \ "'CONTAINER_NAME=#{Shellwords.escape(self.container_name)}' --name " \ "'#{Shellwords.escape(self.container_name)}' " \