vdns 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|