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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/README.md +39 -0
- data/bin/vdns +142 -0
- data/lib/vdns.rb +3 -0
- data/lib/vdns/config.rb +64 -0
- data/lib/vdns/nslookup.rb +25 -0
- data/lib/vdns/provider.rb +22 -0
- data/lib/vdns/provider/aws-route53/README.md +20 -0
- data/lib/vdns/provider/aws-route53/aws-route53.rb +71 -0
- data/lib/vdns/version.rb +3 -0
- data/vdns.gemspec +25 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/README.md
ADDED
@@ -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
|
+
```
|
data/bin/vdns
ADDED
@@ -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
|
+
|
data/lib/vdns.rb
ADDED
data/lib/vdns/config.rb
ADDED
@@ -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
|
+
|
data/lib/vdns/version.rb
ADDED
data/vdns.gemspec
ADDED
@@ -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: []
|