keenetic 0.2.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3fa7552177bec3d537db352ed748bcd24e07bee6156272569a121b7cb18c6a2f
4
- data.tar.gz: e48b1c10d4cc62466ebd5e34b200f399e28e8261e0e1a83a1a4b1ee86efc4ee3
3
+ metadata.gz: cc71a113a4147e3ffd1035596420469ca8c7d9e9639623fd14172b2137d902f6
4
+ data.tar.gz: 7fcb0cbd7b8a1219715288354b58bfa7a8f15ed7900334be7fd4014a1de9e788
5
5
  SHA512:
6
- metadata.gz: f801f8df36437d0f2ea76f6765beaeee3a68750b2974b8b3fc4eed9145be5a5c123940245b56566c690ef906d791eaf8223ae3f016b4bb0472c1a2a3476f77df
7
- data.tar.gz: 984b84f49fb83117ab0c9c89b60a0b88718ccbbd92258c69987be8103bec4d2fd66554c4413c001e45167396735c634a578e839c35cce74b0124d2d3c829f930
6
+ metadata.gz: e03cfc9b96ac18afa8ec3526d735970e5381983325920aaf0f771c1e31337f4c0800158a320081c696ad973b75eab29846b961ea5ea76d2c12b6209516a894e4
7
+ data.tar.gz: fba387ac8b75676298f75715b22c1c348d3ca749fe44234b2c0dbd8d01ff6b9554f78ddf19fcea2b4dcfbee17ccadc253f91a5c777c7c91427d89140fab5335e
data/README.md CHANGED
@@ -20,62 +20,36 @@ Keenetic.configure do |config|
20
20
  end
21
21
  ```
22
22
 
23
- ## Usage
23
+ ## Quick Start
24
24
 
25
25
  ```ruby
26
26
  client = Keenetic.client
27
27
 
28
- # Connected devices
29
- client.devices.all
30
- client.devices.active
31
- client.devices.find(mac: 'AA:BB:CC:DD:EE:FF')
32
- client.devices.update(mac: 'AA:BB:CC:DD:EE:FF', name: 'My Phone')
33
-
34
28
  # System info
35
- client.system.info # model, firmware
36
- client.system.resources # CPU, memory, uptime
29
+ client.system.info # model, firmware
30
+ client.system.resources # CPU, memory, uptime
31
+
32
+ # Connected devices
33
+ client.devices.all # all registered devices
34
+ client.devices.active # currently connected
37
35
 
38
36
  # Network
39
- client.network.interfaces
40
-
41
- # WiFi
42
- client.wifi.access_points
43
- client.wifi.clients
44
-
45
- # Internet
46
- client.internet.status
47
- client.internet.speed
48
-
49
- # Ports
50
- client.ports.all
51
-
52
- # Static Routes
53
- client.routes.all
54
- client.routes.add(host: '1.2.3.4', interface: 'Wireguard0', comment: 'VPN host')
55
- client.routes.add(network: '10.0.0.0/24', interface: 'Wireguard0', comment: 'VPN network')
56
- client.routes.add_batch([
57
- { host: '1.2.3.4', interface: 'Wireguard0', comment: 'Host 1' },
58
- { network: '10.0.0.0/24', interface: 'Wireguard0', comment: 'Network 1' }
59
- ])
60
- client.routes.delete(host: '1.2.3.4')
61
- client.routes.delete(network: '10.0.0.0/24')
62
-
63
- # Hotspot / Policies
64
- client.hotspot.policies # all IP policies
65
- client.hotspot.hosts # all registered hosts
66
- client.hotspot.set_host_policy(mac: 'AA:BB:CC:DD:EE:FF', policy: 'Policy0')
67
- client.hotspot.set_host_policy(mac: 'AA:BB:CC:DD:EE:FF', policy: nil) # remove policy
37
+ client.network.interfaces # all interfaces
38
+ client.wifi.access_points # Wi-Fi networks
39
+ client.internet.status # internet connectivity
40
+
41
+ # Port forwarding
42
+ client.nat.rules # NAT rules
43
+ client.nat.add_forward(index: 1, protocol: 'tcp', port: 8080,
44
+ to_host: '192.168.1.100', to_port: 80)
45
+
46
+ # VPN
47
+ client.vpn.status # VPN server status
48
+ client.vpn.clients # connected VPN clients
68
49
 
69
50
  # Configuration
70
51
  client.system_config.save # save to flash
71
- client.system_config.download # download startup-config.txt
72
-
73
- # Raw RCI Access (for custom commands)
74
- client.rci({ 'show' => { 'system' => {} } })
75
- client.rci([
76
- { 'show' => { 'system' => {} } },
77
- { 'show' => { 'version' => {} } }
78
- ])
52
+ client.system_config.download # backup config
79
53
  ```
80
54
 
81
55
  ## Error Handling
@@ -89,18 +63,18 @@ rescue Keenetic::ConnectionError
89
63
  # router unreachable
90
64
  rescue Keenetic::TimeoutError
91
65
  # request timed out
92
- rescue Keenetic::NotFoundError
93
- # resource not found
94
66
  rescue Keenetic::ApiError => e
95
- # Other API errors
67
+ # other API errors
96
68
  e.status_code
97
69
  e.response_body
98
70
  end
99
71
  ```
100
72
 
101
- ## API Reference
73
+ ## Documentation
102
74
 
103
- See [KEENETIC_API.md](KEENETIC_API.md) for complete API documentation.
75
+ - [Complete API Reference](docs/API_REFERENCE.md) - All features with detailed examples
76
+ - [API Coverage](API_PROGRESS.md) - Implementation status
77
+ - [Keenetic API Specification](KEENETIC_API.md) - Raw API documentation
104
78
 
105
79
  ## Requirements
106
80
 
@@ -117,6 +117,71 @@ module Keenetic
117
117
  @system_config ||= Resources::Config.new(self)
118
118
  end
119
119
 
120
+ # @return [Resources::Nat] NAT and port forwarding resource
121
+ def nat
122
+ @nat ||= Resources::Nat.new(self)
123
+ end
124
+
125
+ # @return [Resources::Vpn] VPN server resource
126
+ def vpn
127
+ @vpn ||= Resources::Vpn.new(self)
128
+ end
129
+
130
+ # @return [Resources::Diagnostics] Network diagnostics resource
131
+ def diagnostics
132
+ @diagnostics ||= Resources::Diagnostics.new(self)
133
+ end
134
+
135
+ # @return [Resources::Firewall] Firewall resource
136
+ def firewall
137
+ @firewall ||= Resources::Firewall.new(self)
138
+ end
139
+
140
+ # @return [Resources::Mesh] Mesh Wi-Fi system resource
141
+ def mesh
142
+ @mesh ||= Resources::Mesh.new(self)
143
+ end
144
+
145
+ # @return [Resources::Usb] USB devices and storage resource
146
+ def usb
147
+ @usb ||= Resources::Usb.new(self)
148
+ end
149
+
150
+ # @return [Resources::Dns] DNS settings and cache resource
151
+ def dns
152
+ @dns ||= Resources::Dns.new(self)
153
+ end
154
+
155
+ # @return [Resources::Dyndns] Dynamic DNS resource
156
+ def dyndns
157
+ @dyndns ||= Resources::Dyndns.new(self)
158
+ end
159
+
160
+ # @return [Resources::Schedule] Access schedules resource
161
+ def schedule
162
+ @schedule ||= Resources::Schedule.new(self)
163
+ end
164
+
165
+ # @return [Resources::Users] User accounts resource
166
+ def users
167
+ @users ||= Resources::Users.new(self)
168
+ end
169
+
170
+ # @return [Resources::Components] System components resource
171
+ def components
172
+ @components ||= Resources::Components.new(self)
173
+ end
174
+
175
+ # @return [Resources::Qos] QoS and traffic control resource
176
+ def qos
177
+ @qos ||= Resources::Qos.new(self)
178
+ end
179
+
180
+ # @return [Resources::Ipv6] IPv6 network resource
181
+ def ipv6
182
+ @ipv6 ||= Resources::Ipv6.new(self)
183
+ end
184
+
120
185
  # Execute arbitrary RCI command(s).
121
186
  #
122
187
  # Provides raw access to the Keenetic RCI (Remote Command Interface).
@@ -211,6 +276,19 @@ module Keenetic
211
276
  request(:post, '/rci/', body: commands)
212
277
  end
213
278
 
279
+ # Make a POST request with raw body content (non-JSON).
280
+ #
281
+ # Used for file uploads like configuration restore.
282
+ #
283
+ # @param path [String] API path
284
+ # @param content [String] Raw content to send
285
+ # @param content_type [String] Content-Type header (default: text/plain)
286
+ # @return [String, nil] Response body
287
+ #
288
+ def post_raw(path, content, content_type: 'text/plain')
289
+ request(:post, path, raw_body: content, content_type: content_type)
290
+ end
291
+
214
292
  # Check if client is authenticated.
215
293
  # @return [Boolean]
216
294
  def authenticated?
@@ -254,7 +332,10 @@ module Keenetic
254
332
  url += "?#{URI.encode_www_form(options[:params])}"
255
333
  end
256
334
 
257
- if options[:body]
335
+ if options[:raw_body]
336
+ request_options[:body] = options[:raw_body]
337
+ request_options[:headers]['Content-Type'] = options[:content_type] || 'text/plain'
338
+ elsif options[:body]
258
339
  request_options[:body] = options[:body].to_json
259
340
  request_options[:headers]['Content-Type'] = 'application/json'
260
341
  end
@@ -17,6 +17,10 @@ module Keenetic
17
17
  client.post(path, body)
18
18
  end
19
19
 
20
+ def post_raw(path, content, content_type: 'text/plain')
21
+ client.post_raw(path, content, content_type: content_type)
22
+ end
23
+
20
24
  # Convert kebab-case keys to snake_case symbols
21
25
  # @param hash [Hash] Hash with string keys
22
26
  # @return [Hash] Hash with symbolized snake_case keys
@@ -0,0 +1,88 @@
1
+ module Keenetic
2
+ module Resources
3
+ # Components resource for managing installable router components.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === Installed Components
8
+ # GET /rci/show/components
9
+ #
10
+ # === Available Components
11
+ # GET /rci/show/components/available
12
+ #
13
+ # === Install Component
14
+ # POST /rci/components/install
15
+ #
16
+ # === Remove Component
17
+ # POST /rci/components/remove
18
+ #
19
+ class Components < Base
20
+ # Get installed components.
21
+ #
22
+ # @return [Array<Hash>] List of installed components
23
+ # @example
24
+ # installed = client.components.installed
25
+ #
26
+ def installed
27
+ response = get('/rci/show/components')
28
+ normalize_components(response)
29
+ end
30
+
31
+ # Get available components for installation.
32
+ #
33
+ # @return [Array<Hash>] List of available components
34
+ # @example
35
+ # available = client.components.available
36
+ #
37
+ def available
38
+ response = get('/rci/show/components/available')
39
+ normalize_components(response)
40
+ end
41
+
42
+ # Install a component.
43
+ #
44
+ # @param name [String] Component name
45
+ # @return [Hash, nil] API response
46
+ # @example
47
+ # client.components.install(name: 'transmission')
48
+ #
49
+ def install(name:)
50
+ post('/rci/components/install', { 'name' => name })
51
+ end
52
+
53
+ # Remove a component.
54
+ #
55
+ # @param name [String] Component name
56
+ # @return [Hash, nil] API response
57
+ # @example
58
+ # client.components.remove(name: 'transmission')
59
+ #
60
+ def remove(name:)
61
+ post('/rci/components/remove', { 'name' => name })
62
+ end
63
+
64
+ private
65
+
66
+ def normalize_components(response)
67
+ components_data = case response
68
+ when Array
69
+ response
70
+ when Hash
71
+ response['component'] || response['components'] || []
72
+ else
73
+ []
74
+ end
75
+
76
+ return [] unless components_data.is_a?(Array)
77
+
78
+ components_data.map { |component| normalize_component(component) }.compact
79
+ end
80
+
81
+ def normalize_component(data)
82
+ return nil unless data.is_a?(Hash)
83
+
84
+ deep_normalize_keys(data)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -13,6 +13,11 @@ module Keenetic
13
13
  # GET /ci/startup-config.txt
14
14
  # Returns: Plain text configuration file
15
15
  #
16
+ # === Upload Configuration
17
+ # POST /ci/startup-config.txt
18
+ # Body: Plain text configuration file
19
+ # Restores configuration from backup
20
+ #
16
21
  class Config < Base
17
22
  # Save current configuration to persistent storage.
18
23
  #
@@ -42,12 +47,43 @@ module Keenetic
42
47
  #
43
48
  # @return [String] Configuration file content
44
49
  # @example
45
- # config_text = client.config.download
50
+ # config_text = client.system_config.download
46
51
  # File.write('router-backup.txt', config_text)
47
52
  #
48
53
  def download
49
54
  get('/ci/startup-config.txt')
50
55
  end
56
+
57
+ # Upload and restore a configuration file.
58
+ #
59
+ # == Keenetic API Request
60
+ # POST /ci/startup-config.txt
61
+ # Content-Type: text/plain
62
+ # Body: Configuration file content
63
+ #
64
+ # Uploads a configuration file to the router. The configuration
65
+ # will be applied after a reboot.
66
+ #
67
+ # == Warning
68
+ # This is a potentially destructive operation. Uploading an
69
+ # invalid or incompatible configuration may make the router
70
+ # inaccessible. Always ensure you have physical access to the
71
+ # router before restoring configuration.
72
+ #
73
+ # @param content [String] Configuration file content
74
+ # @return [String, nil] Response from the router
75
+ # @example Upload from string
76
+ # client.system_config.upload(config_text)
77
+ #
78
+ # @example Upload from file
79
+ # config_text = File.read('router-backup.txt')
80
+ # client.system_config.upload(config_text)
81
+ #
82
+ def upload(content)
83
+ raise ArgumentError, 'Configuration content cannot be empty' if content.nil? || content.strip.empty?
84
+
85
+ post_raw('/ci/startup-config.txt', content)
86
+ end
51
87
  end
52
88
  end
53
89
  end
@@ -0,0 +1,102 @@
1
+ module Keenetic
2
+ module Resources
3
+ # Diagnostics resource for network troubleshooting tools.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === Ping
8
+ # POST /rci/tools/ping
9
+ # Body: { host, count }
10
+ #
11
+ # === Traceroute
12
+ # POST /rci/tools/traceroute
13
+ # Body: { host }
14
+ #
15
+ # === DNS Lookup
16
+ # POST /rci/tools/nslookup
17
+ # Body: { host }
18
+ #
19
+ class Diagnostics < Base
20
+ # Ping a host from the router.
21
+ #
22
+ # == Keenetic API Request
23
+ # POST /rci/tools/ping
24
+ # Body: { "host": "8.8.8.8", "count": 4 }
25
+ #
26
+ # @param host [String] Hostname or IP address to ping
27
+ # @param count [Integer] Number of ping packets (default: 4)
28
+ # @return [Hash] Ping results
29
+ #
30
+ # @example Ping Google DNS
31
+ # result = client.diagnostics.ping('8.8.8.8')
32
+ # # => { host: "8.8.8.8", transmitted: 4, received: 4, loss: 0, ... }
33
+ #
34
+ # @example Ping with custom count
35
+ # result = client.diagnostics.ping('google.com', count: 10)
36
+ #
37
+ def ping(host, count: 4)
38
+ response = post('/rci/tools/ping', { 'host' => host, 'count' => count })
39
+ normalize_ping_result(response)
40
+ end
41
+
42
+ # Trace route to a host from the router.
43
+ #
44
+ # == Keenetic API Request
45
+ # POST /rci/tools/traceroute
46
+ # Body: { "host": "google.com" }
47
+ #
48
+ # @param host [String] Hostname or IP address to trace
49
+ # @return [Hash] Traceroute results with hops
50
+ #
51
+ # @example Trace route to Google
52
+ # result = client.diagnostics.traceroute('google.com')
53
+ # # => { host: "google.com", hops: [{ hop: 1, ip: "192.168.1.1", time: 1 }, ...] }
54
+ #
55
+ def traceroute(host)
56
+ response = post('/rci/tools/traceroute', { 'host' => host })
57
+ normalize_traceroute_result(response)
58
+ end
59
+
60
+ # Perform DNS lookup from the router.
61
+ #
62
+ # == Keenetic API Request
63
+ # POST /rci/tools/nslookup
64
+ # Body: { "host": "google.com" }
65
+ #
66
+ # @param host [String] Hostname to resolve
67
+ # @return [Hash] DNS lookup results
68
+ #
69
+ # @example Lookup Google
70
+ # result = client.diagnostics.nslookup('google.com')
71
+ # # => { host: "google.com", addresses: ["142.250.185.46", ...], ... }
72
+ #
73
+ def nslookup(host)
74
+ response = post('/rci/tools/nslookup', { 'host' => host })
75
+ normalize_nslookup_result(response)
76
+ end
77
+
78
+ # Alias for nslookup
79
+ alias dns_lookup nslookup
80
+
81
+ private
82
+
83
+ def normalize_ping_result(response)
84
+ return {} unless response.is_a?(Hash)
85
+
86
+ deep_normalize_keys(response)
87
+ end
88
+
89
+ def normalize_traceroute_result(response)
90
+ return {} unless response.is_a?(Hash)
91
+
92
+ deep_normalize_keys(response)
93
+ end
94
+
95
+ def normalize_nslookup_result(response)
96
+ return {} unless response.is_a?(Hash)
97
+
98
+ deep_normalize_keys(response)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,95 @@
1
+ module Keenetic
2
+ module Resources
3
+ # DNS resource for managing DNS settings and cache.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === Reading DNS Servers
8
+ # GET /rci/show/ip/name-server
9
+ # Returns: Configured DNS servers
10
+ #
11
+ # === Reading DNS Cache
12
+ # GET /rci/show/dns/cache
13
+ # Returns: DNS cache entries
14
+ #
15
+ # === Reading DNS Proxy Settings
16
+ # GET /rci/show/dns/proxy
17
+ # Returns: DNS proxy configuration
18
+ #
19
+ # === Clear DNS Cache
20
+ # POST /rci/dns/cache/clear
21
+ # Body: {}
22
+ #
23
+ class Dns < Base
24
+ # Get configured DNS servers.
25
+ #
26
+ # == Keenetic API Request
27
+ # GET /rci/show/ip/name-server
28
+ #
29
+ # @return [Hash] DNS servers configuration
30
+ # @example
31
+ # servers = client.dns.servers
32
+ #
33
+ def servers
34
+ response = get('/rci/show/ip/name-server')
35
+ normalize_response(response)
36
+ end
37
+
38
+ # Alias for servers
39
+ alias name_servers servers
40
+
41
+ # Get DNS cache entries.
42
+ #
43
+ # == Keenetic API Request
44
+ # GET /rci/show/dns/cache
45
+ #
46
+ # @return [Hash] DNS cache data
47
+ # @example
48
+ # cache = client.dns.cache
49
+ #
50
+ def cache
51
+ response = get('/rci/show/dns/cache')
52
+ normalize_response(response)
53
+ end
54
+
55
+ # Get DNS proxy settings.
56
+ #
57
+ # == Keenetic API Request
58
+ # GET /rci/show/dns/proxy
59
+ #
60
+ # @return [Hash] DNS proxy configuration
61
+ # @example
62
+ # proxy = client.dns.proxy
63
+ #
64
+ def proxy
65
+ response = get('/rci/show/dns/proxy')
66
+ normalize_response(response)
67
+ end
68
+
69
+ # Alias for proxy
70
+ alias proxy_settings proxy
71
+
72
+ # Clear DNS cache.
73
+ #
74
+ # == Keenetic API Request
75
+ # POST /rci/dns/cache/clear
76
+ # Body: {}
77
+ #
78
+ # @return [Hash, nil] API response
79
+ # @example
80
+ # client.dns.clear_cache
81
+ #
82
+ def clear_cache
83
+ post('/rci/dns/cache/clear', {})
84
+ end
85
+
86
+ private
87
+
88
+ def normalize_response(response)
89
+ return {} unless response.is_a?(Hash)
90
+
91
+ deep_normalize_keys(response)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,71 @@
1
+ module Keenetic
2
+ module Resources
3
+ # Dynamic DNS resource for managing KeenDNS and third-party DDNS.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === KeenDNS Status
8
+ # GET /rci/show/rc/ip/http/dyndns
9
+ #
10
+ # === Configure KeenDNS
11
+ # POST /rci/ip/http/dyndns
12
+ #
13
+ # === Third-Party DDNS
14
+ # GET /rci/show/dyndns
15
+ #
16
+ class Dyndns < Base
17
+ # Get KeenDNS status.
18
+ #
19
+ # @return [Hash] KeenDNS configuration and status
20
+ # @example
21
+ # status = client.dyndns.keendns_status
22
+ #
23
+ def keendns_status
24
+ response = get('/rci/show/rc/ip/http/dyndns')
25
+ normalize_response(response)
26
+ end
27
+
28
+ # Configure KeenDNS.
29
+ #
30
+ # @param params [Hash] KeenDNS configuration parameters
31
+ # @return [Hash, nil] API response
32
+ # @example
33
+ # client.dyndns.configure_keendns(enabled: true, domain: 'myrouter')
34
+ #
35
+ def configure_keendns(**params)
36
+ post('/rci/ip/http/dyndns', normalize_params(params))
37
+ end
38
+
39
+ # Get third-party DDNS providers status.
40
+ #
41
+ # @return [Hash] Third-party DDNS configuration
42
+ # @example
43
+ # ddns = client.dyndns.third_party
44
+ #
45
+ def third_party
46
+ response = get('/rci/show/dyndns')
47
+ normalize_response(response)
48
+ end
49
+
50
+ # Alias for third_party
51
+ alias providers third_party
52
+
53
+ private
54
+
55
+ def normalize_response(response)
56
+ return {} unless response.is_a?(Hash)
57
+
58
+ deep_normalize_keys(response)
59
+ end
60
+
61
+ def normalize_params(params)
62
+ result = {}
63
+ params.each do |key, value|
64
+ api_key = key.to_s.tr('_', '-')
65
+ result[api_key] = value
66
+ end
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end