radd 1.2.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3839d4afb39cef8bced2dd146aff7ef7a08dd7e130f2bd534cd820856e6f9c90
4
- data.tar.gz: d3ff659868d45d89068ab1628090c7f40370407cd2a162d17ddca5c5bf1c8a21
3
+ metadata.gz: aff371d343007b06413d5aeffa546a25824130d0ef8052b1ae7d300f97b665c7
4
+ data.tar.gz: cb860484d7cb6ad5531be423bffbdabecd96bdc5e9544fe97489cd747a6237f5
5
5
  SHA512:
6
- metadata.gz: 5d79c2896c91af7aee9d4d4c4d86f250f03f1f5fcf8233379c47be57eab5d6d519b0dc2008db930326a6420f0afcc91c7aedbe57c0563919b7acb647bc3d259c
7
- data.tar.gz: ad478c2fea9f293b3164d3ab7e73b059b87ce84a2493063962261c0c0b82922d39b0e3a1ac88185b61ccde9738cfb52db997971495900c7aabc6766682a8974c
6
+ metadata.gz: 3eef89dbaad0dfc736443237743a9123452df584cb3c60f1f09a8fe83efe13ba70de21bd1c4ee7fd0e9043477250ed03d08e2a3652cbf4db39f47ac9202b0a36
7
+ data.tar.gz: 5c822bafd10f4fde3714a7eea8051bce9e34197f261ed6eeb8d452ec1a87c0688cd4b6a48ddf1fffdecfc1e71f5976ef850f699bbd7a064d98df07fdd939be53
data/bin/radd CHANGED
@@ -6,4 +6,4 @@ require 'bundler/setup'
6
6
  require 'radd'
7
7
  require 'radd/cli'
8
8
 
9
- Radd::Cli.start
9
+ Radd::Cli.run
data/lib/radd/app.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  Radd::App = Rack::Builder.app do
2
+ use Radd::Middleware
3
+
2
4
  map '/ip' do
3
5
  run Radd::IP
4
6
  end
data/lib/radd/cli.rb CHANGED
@@ -2,19 +2,93 @@ require 'optparse'
2
2
 
3
3
  module Radd::Cli
4
4
 
5
+ COMMANDS = %w[help setup add delete list start].freeze
6
+
5
7
  class << self
6
8
 
7
- def load_config
9
+ def load_config(**opts)
8
10
  config = {}
9
11
  parser = OptionParser.new
10
- parser.banner = 'Usage: radd --config [FILE]'
12
+ parser.banner = 'Usage: radd COMMAND [--config FILE]'
11
13
  parser.on('--config FILE', 'Config file') do |file|
12
14
  config['file'] = file
13
15
  end
14
16
 
15
17
  parser.parse!
16
18
 
17
- Radd.configure!(config['file'])
19
+ Radd.configure!(config['file'], **opts)
20
+ end
21
+
22
+ def run
23
+ command = ARGV.shift
24
+ unless COMMANDS.include?(command)
25
+ puts "Unknown command #{command}"
26
+ exit(1)
27
+ end
28
+ send(command)
29
+ end
30
+
31
+ def help
32
+ puts <<~EOS
33
+ Usage:
34
+ radd COMMAND [--config FILE]
35
+
36
+ You must specify one of the following commands:
37
+
38
+ setup Create the database
39
+ add Add new record
40
+ list List available records
41
+ start Run the server
42
+
43
+ EOS
44
+ end
45
+
46
+ def setup
47
+ require_relative 'schema'
48
+ load_config(skip_db: true, skip_models: true)
49
+ Radd::Schema.create
50
+ end
51
+
52
+ def add
53
+ load_config
54
+ print "Enter name: "
55
+ name = STDIN.gets.chomp
56
+ print "Password: "
57
+ password = STDIN.gets.chomp
58
+ print "Re-type password: "
59
+ password_confirmation = STDIN.gets.chomp
60
+ raise "Password mismatch!" unless password == password_confirmation
61
+ record = Radd::Record.new(password: password)
62
+ record.name = name
63
+ record.save
64
+ puts "Added record '#{name}'\n"
65
+ end
66
+
67
+ def delete
68
+ load_config
69
+ print "Enter name: "
70
+ name = STDIN.gets.chomp
71
+ if record = Radd::Record.where(name: name).first
72
+ print "Do you really want to delete #{name} (y/N)?"
73
+ confirm = STDIN.gets.chomp
74
+ if 'y' == confirm
75
+ record.delete
76
+ puts "Record #{name} deleted"
77
+ end
78
+ else
79
+ puts "Record #{name} not found"
80
+ end
81
+ end
82
+
83
+ def list
84
+ load_config
85
+ puts
86
+ records = Radd::Record.all
87
+ tab = [records.map(&:name).map(&:size).max, 24].compact.max
88
+ records.each do |record|
89
+ puts "#{record.name.ljust(tab)} #{record.ip.to_s.ljust(15)} #{record.updated_at || 'never updated'}\n"
90
+ end
91
+ puts
18
92
  end
19
93
 
20
94
  def start
@@ -25,7 +99,6 @@ module Radd::Cli
25
99
 
26
100
  Async do
27
101
  dns_task, http_task = dns.run, http.run
28
-
29
102
  watchdog = Async do
30
103
  sleep(1) while !http_task.failed? && !dns_task.failed?
31
104
  puts "Task failed!"
data/lib/radd/config.rb CHANGED
@@ -1,44 +1,34 @@
1
1
  module Radd
2
2
 
3
3
  class << self
4
- def configure!(file)
4
+ attr_reader :domain, :ip, :db, :http_host, :http_port, :dns_host, :dns_port, :ttl
5
+
6
+ def configure!(file, skip_models: false, skip_db: false)
5
7
  file_path = Pathname.new(file || 'radd.yml')
6
8
  file_path = Radd.root + file_path unless file_path.absolute?
7
- raise Radd::ConfigurationError, "could not open config file #{file_path}" unless file_path.file?
8
- @config = YAML.load(file_path.read).slice(*%w[domain ip host dns_port http_port db])
9
- raise Radd::ConfigurationError, 'domain missing' unless Radd.domain
10
- raise Radd::ConfigurationError, 'invalid IP' unless Radd.valid_ip?(Radd.ip)
11
- db_path = Pathname.new(@config.delete('db') || 'db/radd.sqlite3')
9
+ raise ConfigurationError, "could not open config file #{file_path}" unless file_path.file?
10
+ config = YAML.load(file_path.read)
11
+ raise ConfigurationError, 'domain missing' unless config['domain']
12
+ @domain = config['domain']
13
+ raise ConfigurationError, 'invalid IP' unless Radd.valid_ip?(config['ip'])
14
+ @ip = config['ip']
15
+ uri = URI.parse("http://#{config['http']}")
16
+ @http_host = uri.host || '127.0.0.1'
17
+ @http_port = uri.port || 3003
18
+ uri = URI.parse("dns://#{config['dns']}")
19
+ @dns_host = uri.host || '0.0.0.0'
20
+ @dns_port = uri.port || 53
21
+ raise ConfigurationError, 'invalid TTL' if config['ttl'] && config['ttl'] < 1
22
+ @ttl = config['ttl'] || 300
23
+ db_path = Pathname.new(config['db'] || 'radd.sqlite3')
12
24
  db_path = Radd.root + db_path unless db_path.absolute?
13
- @config['db'] = db_path
14
- raise Radd::ConfigurationError, 'invalid database' unless Radd.db.file?
25
+ @db = db_path
26
+ raise ConfigurationError, 'invalid database' if !skip_db && !Radd.db.file?
27
+ #
15
28
  # Late loading required by Sequel architecture
29
+ #
16
30
  require_relative 'db'
17
- require_relative 'record'
18
- end
19
-
20
- def domain
21
- @config['domain']
22
- end
23
-
24
- def ip
25
- @config['ip']
26
- end
27
-
28
- def db
29
- @config['db']
30
- end
31
-
32
- def host
33
- @config['host'] || '127.0.0.1'
34
- end
35
-
36
- def dns_port
37
- @config['dns_port'] || 5300
38
- end
39
-
40
- def http_port
41
- @config['http_port'] || 3000
31
+ require_relative 'record' unless skip_models
42
32
  end
43
33
 
44
34
  # Check whether +ip+ is a valid IP address string
data/lib/radd/ip.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Radd
2
2
  # IP address query responder
3
3
  IP = Proc.new do |env|
4
- [200, {"Content-Type" => "text/plain"}, [env['REMOTE_ADDR']]]
4
+ [200, {"Content-Type" => "text/plain"}, ["#{env['rack.request'].ip}\n"]]
5
5
  end
6
- end
6
+ end
@@ -0,0 +1,12 @@
1
+ module Radd
2
+ class Middleware
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ env["rack.request"] = Rack::Request.new(env)
9
+ @app.call(env)
10
+ end
11
+ end
12
+ end
@@ -1,6 +1,6 @@
1
1
  class Radd::Nameserver < Async::DNS::Server
2
2
  def initialize
3
- super(Async::DNS::Endpoint.for(Radd.host, port: Radd.dns_port))
3
+ super(Async::DNS::Endpoint.for(Radd.dns_host, port: Radd.dns_port))
4
4
  end
5
5
 
6
6
  def process(name, resource_class, transaction)
@@ -12,7 +12,7 @@ class Radd::Nameserver < Async::DNS::Server
12
12
  ip = Radd.query(name)
13
13
  end
14
14
  end
15
- return transaction.respond!(ip, ttl: 300) if ip
15
+ return transaction.respond!(ip, ttl: Radd.ttl) if ip
16
16
  transaction.fail!(:NXDomain)
17
17
  end
18
18
  end
@@ -0,0 +1,17 @@
1
+ module Radd
2
+ module Schema
3
+ def self.create
4
+ if DB.table_exists?(:records)
5
+ puts "Database #{Radd.db} exists"
6
+ exit(1)
7
+ end
8
+ DB.create_table?(:records) do
9
+ String :name, primary_key: true
10
+ String :password_hash
11
+ String :ip
12
+ DateTime :updated_at
13
+ end
14
+ puts "Created database #{Radd.db}"
15
+ end
16
+ end
17
+ end
data/lib/radd/tasks.rb ADDED
@@ -0,0 +1,39 @@
1
+ namespace :radd do
2
+ task :config do
3
+ Radd::Cli.load_config
4
+ end
5
+
6
+ namespace :db do
7
+ desc 'Create database'
8
+ task create: :config do
9
+ load './db/schema.rb'
10
+ end
11
+ end
12
+
13
+ desc 'Add record'
14
+ task add: :config do
15
+ print "Enter name: "
16
+ name = STDIN.gets.chomp
17
+ print "Password: "
18
+ password = STDIN.gets.chomp
19
+ print "Re-type password: "
20
+ password_confirmation = STDIN.gets.chomp
21
+ raise "Password mismatch!" unless password == password_confirmation
22
+ record = Radd::Record.new(password: password)
23
+ record.name = name
24
+ record.save
25
+ puts "Added record '#{name}'\n"
26
+ end
27
+
28
+ desc 'List all records'
29
+ task list: :config do
30
+ puts
31
+ records = Radd::Record.all
32
+ tab = [records.map(&:name).map(&:size).max, 24].compact.max
33
+ records.each do |record|
34
+ puts "#{record.name.ljust(tab)} #{record.ip.to_s.ljust(15)} #{record.updated_at || 'never updated'}\n"
35
+ end
36
+ puts
37
+ end
38
+
39
+ end
data/lib/radd/update.rb CHANGED
@@ -15,7 +15,14 @@ module Radd
15
15
  end
16
16
 
17
17
  def ip
18
- addr = env['REMOTE_ADDR']
18
+ @ip ||= begin
19
+ addr = env['rack.request'].params['ip'] || remote_ip
20
+ addr && Radd.valid_ip?(addr) && addr
21
+ end
22
+ end
23
+
24
+ def remote_ip
25
+ addr = env['rack.request'].ip
19
26
  addr && Radd.valid_ip?(addr) && addr
20
27
  end
21
28
 
@@ -24,6 +31,7 @@ module Radd
24
31
  raise InvalidRequest.new('Invalid IP address') unless ip
25
32
  record.ip = ip
26
33
  record.save
34
+ Radd.logger.info "Updated record #{record.name} to #{ip} from #{remote_ip}"
27
35
  [200, {'Content-Type' => 'text/plain'}, ["OK #{ip}"]]
28
36
  rescue RaddError => boom
29
37
  status = case boom
@@ -32,8 +40,10 @@ module Radd
32
40
  else
33
41
  500
34
42
  end
43
+ Radd.logger.error boom
35
44
  respond status, "ERROR #{boom.message}"
36
45
  rescue Exception => e
46
+ Radd.logger.error e
37
47
  respond 500, "ERROR"
38
48
  end
39
49
 
data/lib/radd/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Radd
2
- VERSION = '1.2.0'
2
+ VERSION = '1.3.0'
3
3
  end
@@ -1,6 +1,9 @@
1
1
  class Radd::Webserver < Async::HTTP::Server
2
2
  def initialize
3
- endpoint = Async::HTTP::Endpoint.parse("http://#{Radd.host}:#{Radd.http_port}")
3
+ endpoint = Async::HTTP::Endpoint.parse("http://#{Radd.http_host}:#{Radd.http_port}")
4
+ # TODO: support UNIX endpoints
5
+ # endpoint = Async::HTTP::Endpoint.parse("http://127.0.0.1")
6
+ # endpoint.endpoint = IO::Endpoint.unix("/tmp/radd1.sock")
4
7
  middleware = Protocol::Rack::Adapter.new(Radd::App)
5
8
  super(middleware, endpoint)
6
9
  end
data/lib/radd.rb CHANGED
@@ -2,6 +2,7 @@ require 'yaml'
2
2
  require 'sequel'
3
3
  require 'resolv'
4
4
  require 'bcrypt'
5
+ require 'logger'
5
6
 
6
7
  require 'async'
7
8
  require 'async/dns'
@@ -9,11 +10,15 @@ require 'async/http/server'
9
10
  require 'async/http/endpoint'
10
11
  require 'protocol/rack/adapter'
11
12
 
13
+ # require 'io/endpoint'
14
+ # require 'io/endpoint/unix_endpoint'
15
+
12
16
  require_relative 'radd/version'
13
17
  require_relative 'radd/errors'
14
18
  require_relative 'radd/config'
15
19
  require_relative 'radd/ip'
16
20
  require_relative 'radd/update'
21
+ require_relative 'radd/middleware'
17
22
  require_relative 'radd/nameserver'
18
23
  require_relative 'radd/webserver'
19
24
  require_relative 'radd/app'
@@ -24,11 +29,15 @@ module Radd
24
29
  def root=(path)
25
30
  @root = Pathname.new(path)
26
31
  end
27
-
32
+
28
33
  def root
29
34
  @root ||= Pathname.new(Dir.pwd)
30
35
  end
31
-
36
+
37
+ def logger
38
+ @logger = Logger.new(STDOUT)
39
+ end
40
+
32
41
  # Check whether +name+ is authorized with +password+
33
42
  def authorized?(name, password)
34
43
  return false unless record = Record.where(name: name).first
@@ -42,7 +51,7 @@ module Radd
42
51
  return unless record = Record.active.where(name: name).first
43
52
  record.ip
44
53
  end
45
-
54
+
46
55
  private
47
56
 
48
57
  def config
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radd
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Grosser
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-04-28 00:00:00.000000000 Z
10
+ date: 2026-04-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: rake
13
+ name: yaml
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - ">="
@@ -24,7 +24,7 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: yaml
27
+ name: logger
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - ">="
@@ -94,13 +94,13 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  - !ruby/object:Gem::Dependency
97
- name: irb
97
+ name: rack
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - ">="
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
- type: :development
103
+ type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
@@ -108,7 +108,7 @@ dependencies:
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  - !ruby/object:Gem::Dependency
111
- name: rack
111
+ name: sequel
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - ">="
@@ -122,7 +122,7 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  - !ruby/object:Gem::Dependency
125
- name: sequel
125
+ name: sqlite3
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - ">="
@@ -136,7 +136,7 @@ dependencies:
136
136
  - !ruby/object:Gem::Version
137
137
  version: '0'
138
138
  - !ruby/object:Gem::Dependency
139
- name: sqlite3
139
+ name: bcrypt
140
140
  requirement: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - ">="
@@ -150,13 +150,27 @@ dependencies:
150
150
  - !ruby/object:Gem::Version
151
151
  version: '0'
152
152
  - !ruby/object:Gem::Dependency
153
- name: bcrypt
153
+ name: rake
154
154
  requirement: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - ">="
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
- type: :runtime
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: irb
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ type: :development
160
174
  prerelease: false
161
175
  version_requirements: !ruby/object:Gem::Requirement
162
176
  requirements:
@@ -170,7 +184,6 @@ executables:
170
184
  extensions: []
171
185
  extra_rdoc_files: []
172
186
  files:
173
- - CHANGELOG
174
187
  - LICENSE
175
188
  - README.md
176
189
  - bin/radd
@@ -185,8 +198,11 @@ files:
185
198
  - lib/radd/http.rb
186
199
  - lib/radd/http_service.rb
187
200
  - lib/radd/ip.rb
201
+ - lib/radd/middleware.rb
188
202
  - lib/radd/nameserver.rb
189
203
  - lib/radd/record.rb
204
+ - lib/radd/schema.rb
205
+ - lib/radd/tasks.rb
190
206
  - lib/radd/update.rb
191
207
  - lib/radd/version.rb
192
208
  - lib/radd/webserver.rb
data/CHANGELOG DELETED
@@ -1,7 +0,0 @@
1
- * 0.2.0
2
-
3
- Use RubyDNS
4
-
5
- * 0.1.0
6
-
7
- Use knot DNS