vdns 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a4d9ac01b82fdb26798ae44dee31fd629763537e
4
+ data.tar.gz: 7c34306f0902d28a7bc1ee610484728afa988ab1
5
+ SHA512:
6
+ metadata.gz: 2fa2824b695a7823273c8383802bc894e4f042296d9629f2e4fc2820eb507de1d7a656b612591376c88d6b8329625f4e4699042a69b33a422125f24e49203d9a
7
+ data.tar.gz: 3953cd201544813ce872a646c51de439c9983e9e7fb9cf1e26e09f582e4998888895bf58b7fa4feef593d57c292a28c83e53ef12cd47cff651bbdfce8a761ad5
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ *.gem
@@ -0,0 +1,39 @@
1
+ Dynamic DNS for developers and teams
2
+ ====================================
3
+
4
+ * Work locally with virtual hosts (online and offline)
5
+ * Test on devices in the same network
6
+ * Share with teammates in the same network
7
+ * Share with anybody over the web (with port forwarding)
8
+
9
+ This tool will create/ update ``user_name.domain A`` and ``*.user_name.domain A`` DNS records.
10
+
11
+
12
+ ```bash
13
+ vdns host # Update Nameserver with local loopback IP (host-only access)
14
+ vdns lan # Update Nameserver with local area IP (local network access)
15
+ vdns wan # Update Nameserver with wide area IP (Internet access)
16
+ vdns off # Delete Nameserver records
17
+
18
+
19
+ # specials (@todo):
20
+
21
+ vdns host --offline # Start a local Nameserver server and work offline
22
+ vdns wan --forward-port 80 # Set up Port forwarding in your router via UPnP
23
+ ```
24
+
25
+
26
+ ## Supported Nameservers
27
+
28
+ * [AWS Route 53](https://github.com/mattes/vdns/tree/master/lib/vdns/provider/aws-route53)
29
+
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ gem install vdns
35
+
36
+ # run vdns once and update config file ...
37
+ vdns
38
+ vi ~/.vdns/conf.yml
39
+ ```
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # when developing and calling ./vdns directly, uncomment the following line.
4
+ # $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + "/../lib")
5
+
6
+ require "yaml"
7
+ require "optparse"
8
+ require "socket"
9
+ require 'open-uri'
10
+ require "vdns"
11
+
12
+ config = VDNS::Config.new("~/.vdns/conf.yml")
13
+ %w(provider user domain).each do |var|
14
+ if config.blank?(var)
15
+ puts "Error: Missing '#{var}' setting! Please define in #{config.file_name}."
16
+ exit 1
17
+ end
18
+ end
19
+
20
+
21
+ ip = nil
22
+ args = {}
23
+ args["offline"] = false
24
+ args["skip-check"] = false
25
+ args["ip"] = nil
26
+ args["port"] = nil
27
+ args["help"] = false
28
+ args["provider"] = "aws-route53"
29
+
30
+ opts = OptionParser.new do |o|
31
+ o.banner = "Update Nameserver with IP."
32
+ o.separator ""
33
+ o.separator "vdns host --offline, --skip-check"
34
+ o.separator "vdns lan --skip-check"
35
+ o.separator "vdns wan --forward-port, --skip-check"
36
+ o.separator "vdns custom --ip, --forward-port, --skip-check"
37
+ o.separator "vdns off"
38
+ o.separator ""
39
+ o.separator "Options:"
40
+ o.on("-o", "--offline", "Work in offline mode") { args["offline"] = true }
41
+ o.on("-p", "--forward-port [port]", "Set up Port forwarding via UPnP") {|var| args["port"] = var }
42
+ o.on("-i", "--ip [address]", "Use custom IP address") {|var| args["ip"] = var }
43
+ o.on("-s", "--skip-check", "Skip DNS check after update") { args["skip-check"] = true }
44
+ o.on("-h", "--help", "Show this help") { args["help"] = true }
45
+ end
46
+ opts.parse!
47
+
48
+ if args["help"]
49
+ puts opts
50
+ exit
51
+ end
52
+
53
+ # update helper method
54
+ def _update(config, provider_name, ip, verify=true)
55
+ provider = VDNS::ProviderFactory::get(provider_name, config)
56
+
57
+ puts "Updating: #{config.get("user")}.#{config.get("domain")} -> #{ip} (#{provider_name})"
58
+ ["*.#{config.get("user")}.#{config.get("domain")}",
59
+ "#{config.get("user")}.#{config.get("domain")}"].each do |domain|
60
+ provider.save(domain, ip)
61
+ end
62
+
63
+ if verify
64
+ check_ns = %w(8.8.8.8 8.8.4.4)
65
+ ns = VDNS::NSLookup.new(check_ns)
66
+
67
+ printf "Waiting for updates to become active ..."
68
+
69
+ def _sleep(seconds)
70
+ while seconds > 0
71
+ printf "." if seconds % 5 == 0
72
+ seconds -= 1
73
+ sleep 1
74
+ end
75
+ end
76
+
77
+ _sleep 65
78
+ updated = false
79
+ until ns.updated?("#{config.get("user")}.#{config.get("domain")}", ip)
80
+ _sleep 30
81
+ end
82
+
83
+ printf "\nUpdates should be active at nameservers #{check_ns.join(", ")}.\n"
84
+ end
85
+ end
86
+
87
+ def _port_forwarding(port)
88
+ unless port.nil?
89
+ puts "Warning: Port forwarding not supported, yet."
90
+ end
91
+ end
92
+
93
+
94
+ case ARGV[0]
95
+ when "host"
96
+ ip = "127.0.0.1"
97
+ if args["offline"]
98
+ puts "Error: Offline mode is not supported, yet."
99
+ exit 1
100
+ else
101
+ _update(config, args["provider"], ip, !args["skip-check"])
102
+ end
103
+
104
+
105
+ when "lan"
106
+ ips = Socket.ip_address_list
107
+ ips.reject! {|ip| not ip.ipv4_private? }
108
+ ips.map! {|ip| ip.ip_address}
109
+ ip = ips.first
110
+ _update(config, args["provider"], ip, !args["skip-check"])
111
+
112
+
113
+ when "wan"
114
+ ip = URI.parse('http://echoip.com').read.strip
115
+ _port_forwarding(args["port"])
116
+ _update(config, args["provider"], ip, !args["skip-check"])
117
+
118
+
119
+ when "custom"
120
+ if args["ip"].nil?
121
+ puts "Error: Missing --ip option."
122
+ exit 1
123
+ end
124
+ ip = args["ip"]
125
+ _port_forwarding(args["port"])
126
+ _update(config, args["provider"], ip, !args["skip-check"])
127
+
128
+ when "off"
129
+ provider = VDNS::ProviderFactory::get(args["provider"], config)
130
+ ["*.#{config.get("user")}.#{config.get("domain")}",
131
+ "#{config.get("user")}.#{config.get("domain")}"].each do |domain|
132
+ provider.delete(domain)
133
+ end
134
+
135
+ else
136
+ puts opts
137
+ exit
138
+ end
139
+
140
+
141
+
142
+
@@ -0,0 +1,3 @@
1
+ require "vdns/config"
2
+ require "vdns/provider"
3
+ require "vdns/nslookup"
@@ -0,0 +1,64 @@
1
+ module VDNS
2
+
3
+ class Config
4
+ @file = nil
5
+ @config = {}
6
+
7
+ def initialize(file)
8
+ @file = File.expand_path(file)
9
+
10
+ unless File.exist?(@file)
11
+ dirname = File.dirname(@file)
12
+ Dir.mkdir(dirname) unless Dir.exist?(dirname)
13
+ File.open(@file, "w") do |f|
14
+ f.puts %Q(
15
+ user: #{`whoami`}
16
+ domain:
17
+ provider: aws-route53
18
+
19
+ aws_r53_access_key:
20
+ aws_r53_secret_access_key:
21
+ aws_r53_hosted_zone_id:
22
+ )
23
+ end
24
+ end
25
+
26
+ read_file
27
+ end
28
+
29
+ def get(name)
30
+ if include?(name)
31
+ @config[name]
32
+ else
33
+ raise ArgumentError, "Undefined config variable #{name}"
34
+ end
35
+ end
36
+
37
+ def set(name, var)
38
+ @config[name] = var
39
+ end
40
+
41
+ def include?(name)
42
+ @config.include? name
43
+ end
44
+
45
+ def blank?(name)
46
+ @config[name].nil? || @config[name].empty?
47
+ end
48
+
49
+ def read_file
50
+ @config = YAML.load_file(@file)
51
+ end
52
+
53
+ def save_file
54
+ File.open(@file, 'w') do |f|
55
+ f.puts YAML.dump(@config)
56
+ end
57
+ end
58
+
59
+ def file_name
60
+ @file
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,25 @@
1
+ require "resolv"
2
+
3
+ module VDNS
4
+ class NSLookup
5
+
6
+ def initialize(nameservers)
7
+ raise ArgumentError, "Specify nameserver array" unless nameservers.is_a? Array
8
+ @nameservers = nameservers
9
+ end
10
+
11
+ def updated?(domain, to_ip)
12
+ updated = true
13
+ @nameservers.each do |ns|
14
+ resolv = Resolv::DNS.new(:nameserver => ns, :ndots => 1)
15
+ resolv.timeouts = 10
16
+ if resolv.getaddress(domain).to_s != to_ip
17
+ updated = false
18
+ break
19
+ end
20
+ end
21
+ return updated
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ module VDNS
2
+
3
+ class ProviderFactory
4
+ def self.get(provider, config)
5
+ require "vdns/provider/#{provider}/#{provider}"
6
+ return VDNS::Provider.new(config)
7
+ end
8
+ end
9
+
10
+
11
+ class ProviderBase
12
+ def warning(message)
13
+ puts "Warn: #{message}"
14
+ end
15
+
16
+ def error_and_exit(message)
17
+ puts "Error: #{message}"
18
+ exit 1
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,20 @@
1
+ AWS Route 53
2
+ ============
3
+
4
+ Amazon Web Services Route 53 Provider
5
+
6
+ http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html
7
+
8
+ Required config variables:
9
+ * ``domain``
10
+ * ``aws_r53_access_key``
11
+ * ``aws_r53_secret_access_key``
12
+ * ``aws_r53_hosted_zone_id`` (see AWS Route 53 Console)
13
+
14
+ ## Setup
15
+
16
+ 1. Create new hosted zone in AWS console.
17
+ 1. Add hosted zone id to config file (aws_r53_hosted_zone_id).
18
+ 1. Set ``TTL (Seconds)`` to 60 for the SOA record.
19
+ 1. Set ``refresh-time, retry-time, expire-time, minimum TTL`` for SOA record to ``60 60 1209600 60``
20
+
@@ -0,0 +1,71 @@
1
+ require "vdns/provider"
2
+ require "aws-sdk"
3
+
4
+ module VDNS
5
+ class Provider < VDNS::ProviderBase
6
+
7
+ def initialize(config)
8
+ %w(domain
9
+ aws_r53_access_key
10
+ aws_r53_secret_access_key
11
+ aws_r53_hosted_zone_id).each do |var|
12
+ error_and_exit("Missing '#{var}' setting! Please define in #{config.file_name}.") if config.blank?(var)
13
+ end
14
+
15
+ AWS.config(access_key_id: config.get("aws_r53_access_key"),
16
+ secret_access_key: config.get("aws_r53_secret_access_key"))
17
+
18
+ @hosted_zone = AWS::Route53::HostedZone.new(config.get("aws_r53_hosted_zone_id"))
19
+ @rrsets = @hosted_zone.rrsets
20
+
21
+ @domain = parse_domain(config.get("domain"))
22
+ end
23
+
24
+ def save(domain, ip)
25
+ domain = parse_domain(domain)
26
+ rrset = @rrsets[domain, "A"]
27
+ if rrset.exists?
28
+ rrset.resource_records = [{:value => ip}]
29
+ rrset.update
30
+ else
31
+ @rrsets.create(
32
+ domain,
33
+ "A",
34
+ :ttl => 60,
35
+ :resource_records => [{:value => ip}])
36
+ end
37
+ update_soa_serial_number
38
+ return true
39
+ end
40
+
41
+ def delete(domain)
42
+ domain = parse_domain(domain)
43
+ rrset = @rrsets[domain, "A"]
44
+ if rrset.exists?
45
+ rrset.delete
46
+ update_soa_serial_number
47
+ end
48
+ return true
49
+ end
50
+
51
+ private
52
+
53
+ def parse_domain(domain)
54
+ unless domain.end_with?(".")
55
+ domain = "#{domain}."
56
+ end
57
+ domain = domain.gsub(/\*/, "\\\\052") # * -> 052 ascii, aws bug?!
58
+ return domain
59
+ end
60
+
61
+ def update_soa_serial_number
62
+ soa = @rrsets[@domain, "SOA"]
63
+ soa_parts = soa.resource_records[0][:value].split(" ")
64
+ soa_parts[2] = Time.now.to_i
65
+ soa.resource_records = [{:value => soa_parts.join(" ")}]
66
+ soa.update
67
+ end
68
+
69
+ end
70
+ end
71
+
@@ -0,0 +1,3 @@
1
+ module VDNS
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'vdns/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "vdns"
8
+ spec.version = VDNS::VERSION
9
+ spec.authors = ["mattes"]
10
+ spec.email = ["matthias.kadenbach@gmail.com"]
11
+ spec.description = "Dynamic DNS for developers and teams."
12
+ spec.summary = spec.description
13
+ spec.homepage = "https://github.com/mattes/vdns"
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
+
24
+ spec.add_runtime_dependency "aws-sdk", "~> 1.28.0"
25
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vdns
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - mattes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.28.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.28.0
27
+ description: Dynamic DNS for developers and teams.
28
+ email:
29
+ - matthias.kadenbach@gmail.com
30
+ executables:
31
+ - vdns
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - ".gitignore"
36
+ - README.md
37
+ - bin/vdns
38
+ - lib/vdns.rb
39
+ - lib/vdns/config.rb
40
+ - lib/vdns/nslookup.rb
41
+ - lib/vdns/provider.rb
42
+ - lib/vdns/provider/aws-route53/README.md
43
+ - lib/vdns/provider/aws-route53/aws-route53.rb
44
+ - lib/vdns/version.rb
45
+ - vdns.gemspec
46
+ homepage: https://github.com/mattes/vdns
47
+ licenses:
48
+ - MIT
49
+ metadata: {}
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 2.0.3
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Dynamic DNS for developers and teams.
70
+ test_files: []