miab 0.2.0 → 0.4.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/miab.rb +314 -129
- metadata +6 -26
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a339ac7a434f6d8f59cfacb59a5f70e56c4cbe490712a9ec70bec7992cae9036
|
4
|
+
data.tar.gz: f44e1bf9f71fe2fc250841c9b69ae4eb62a69176bff014bc8293f23c17b6cf35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42d047bc15e959ff24b194063547065875d495f23ccd09f5e7d0f607f326f6a5f9aeef0894c9f90d5459bcc64453d1abdfe5d8031bb8e5389e68181343ef525c
|
7
|
+
data.tar.gz: c390aebd8577fcbba6569549a69358c673bd85e44ac1b1f5f49fcd904885af78084a0aa76b478796adfcf7a7097af48514b8d5a48ba5f9283473b0994914d3ed
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/lib/miab.rb
CHANGED
@@ -4,188 +4,373 @@
|
|
4
4
|
|
5
5
|
# Desc: Message in a bottle (MIAB) is designed to execute remote commands
|
6
6
|
# through SSH for system maintenance purposes.
|
7
|
+
#
|
8
|
+
# Note: Intended for a Debian based distro
|
7
9
|
|
8
10
|
require 'net/ssh'
|
9
11
|
require 'c32'
|
10
|
-
require '
|
11
|
-
|
12
|
+
require 'resolv'
|
13
|
+
|
14
|
+
|
15
|
+
# available commands:
|
16
|
+
#
|
17
|
+
# * backup - initiates rsync on the remote machine to be backed up
|
18
|
+
# * date - returns the system date and time
|
19
|
+
# * directory_exists? - returns true if the directory exists
|
20
|
+
# * disk_space - returns the available disk space
|
21
|
+
# * echo - returns whatever string is passed in
|
22
|
+
# * file_exists? - returns true if the file exists
|
23
|
+
# * file_write - writes contents to a file
|
24
|
+
# * installable? - returns true if the package name exists in apt-cache search
|
25
|
+
# * installed? - returns true if a package is already installed
|
26
|
+
# * internet? - returns true if a ping request to an external IP address succeeds
|
27
|
+
# * memory - returns the amount of free RAM available etc
|
28
|
+
# * ping - returns the latency of a ping request to the node
|
29
|
+
# * pwd - returns the working directory
|
30
|
+
# * temperature - returns the temperature of the CPU
|
31
|
+
|
32
|
+
# usage: puts Miab.new("temperature", domain: 'home', target: 'angelo',
|
33
|
+
# user: 'pi', password: 'secret').cast
|
34
|
+
# nearest SSH equivalent `ssh pi@angelo.home exec \
|
35
|
+
# "cat /sys/class/thermal/thermal_zone0/temp"`
|
12
36
|
|
13
37
|
class Miab
|
14
38
|
using ColouredText
|
39
|
+
|
40
|
+
class Session
|
41
|
+
|
42
|
+
def initialize( host, ssh=nil, debug: false, dns: '1.1.1.1')
|
43
|
+
|
44
|
+
@ssh, @host, @debug, @dns = ssh, host, debug, dns
|
45
|
+
|
46
|
+
puts 'Session dns: ' + dns.inspect if @debug
|
47
|
+
@results = {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def exec(s)
|
51
|
+
eval s
|
52
|
+
@results
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
# backup
|
58
|
+
# e.g. from: /mnt/usbdisk/gem_src/.
|
59
|
+
# to: pi@192.168.4.158:backup2020/gem_src/.
|
60
|
+
#
|
61
|
+
def backup(from: nil, to: nil)
|
62
|
+
|
63
|
+
if @debug then
|
64
|
+
puts 'ready to perform backup'
|
65
|
+
puts "from: %s to: %s" % [from, to]
|
66
|
+
end
|
67
|
+
|
68
|
+
instructions = "rsync -akL -e ssh %s %s" % [from, to]
|
69
|
+
|
70
|
+
puts 'instructions: ' + instructions if @debug
|
71
|
+
|
72
|
+
# note: compression is not enabled since this is aimed at
|
73
|
+
# single board computers which have limited CPU capability
|
74
|
+
|
75
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
76
|
+
puts 'r: ' + r.inspect if @debug
|
77
|
+
|
78
|
+
# since it's running in the background, an empty string will be returned
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
# return the local date and time
|
83
|
+
#
|
84
|
+
def date()
|
15
85
|
|
16
|
-
|
17
|
-
|
86
|
+
instructions = 'date'
|
87
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
88
|
+
puts 'r: ' + r.inspect if @debug
|
89
|
+
@results[:date] = r.chomp
|
18
90
|
|
19
|
-
|
91
|
+
end
|
92
|
+
|
93
|
+
def directory_exists?(file)
|
94
|
+
|
95
|
+
instructions = "test -d #{file}; echo $?"
|
96
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
97
|
+
puts 'r: ' + r.inspect if @debug
|
98
|
+
|
99
|
+
@results[:directory_exists?] = r.chomp == '0'
|
20
100
|
|
21
|
-
|
101
|
+
end
|
102
|
+
|
103
|
+
alias dir_exists? directory_exists?
|
22
104
|
|
23
|
-
|
105
|
+
# query the available disk space etc.
|
106
|
+
#
|
107
|
+
def disk_space()
|
24
108
|
|
25
|
-
|
109
|
+
instructions = 'df -h'
|
110
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
26
111
|
|
27
|
-
|
28
|
-
host = domain ? x + '.' + domain : x
|
29
|
-
passwd = pwlist[x] || password
|
30
|
-
userhost = user ? user + '@' + host : host
|
31
|
-
r.merge({userhost => passwd})
|
32
|
-
end
|
112
|
+
@results[:disk_usage] = {}
|
33
113
|
|
34
|
-
|
35
|
-
{}
|
36
|
-
end
|
37
|
-
end
|
114
|
+
a = r.lines.grep(/\/dev\/root/)
|
38
115
|
|
39
|
-
|
40
|
-
#
|
41
|
-
def cast()
|
116
|
+
puts ('a: ' + a.inspect).debug if @debug
|
42
117
|
|
43
|
-
|
118
|
+
if a.any? then
|
119
|
+
size, used, avail = a[0].split(/ +/).values_at(1,2,3)
|
44
120
|
|
45
|
-
|
121
|
+
@results[:disk_usage][:root] = {size: size, used: used,
|
122
|
+
avail: avail}
|
123
|
+
end
|
46
124
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
@results[host] = nil
|
57
|
-
end
|
58
|
-
|
125
|
+
a2 = r.lines.grep(/\/dev\/sda1/)
|
126
|
+
|
127
|
+
puts ('a2: ' + a2.inspect).debug if @debug
|
128
|
+
|
129
|
+
if a2.any? then
|
130
|
+
size, used, avail = a2[0].split(/ +/).values_at(1,2,3)
|
131
|
+
|
132
|
+
@results[:disk_usage][:sda1] = {size: size, used: used,
|
133
|
+
avail: avail}
|
59
134
|
end
|
60
135
|
|
61
|
-
else
|
62
|
-
@results[`hostname`.chomp] = {}
|
63
|
-
eval @scroll if @scroll
|
64
136
|
end
|
65
137
|
|
66
|
-
|
67
|
-
|
68
|
-
|
138
|
+
alias df disk_space
|
139
|
+
|
140
|
+
# return the string supplied
|
141
|
+
#
|
142
|
+
def echo(s)
|
69
143
|
|
70
|
-
|
71
|
-
|
72
|
-
|
144
|
+
instructions = 'echo ' + s
|
145
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
146
|
+
puts 'r: ' + r.inspect if @debug
|
147
|
+
@results[:echo] = r.chomp
|
73
148
|
|
74
|
-
|
75
|
-
|
76
|
-
|
149
|
+
end
|
150
|
+
|
151
|
+
def exec_success?(instruction, expected)
|
152
|
+
|
153
|
+
r = @ssh ? @ssh.exec!(instruction) : `#{instruction}`
|
154
|
+
puts 'r: ' + r.inspect if @debug
|
155
|
+
|
156
|
+
@results[:exec_success?] = (r =~ /#{expected}/ ? true : r)
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
def file_exists?(file)
|
161
|
+
|
162
|
+
instructions = "test -f #{file}; echo $?"
|
163
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
164
|
+
puts 'r: ' + r.inspect if @debug
|
165
|
+
|
166
|
+
@results[:file_exists?] = r == 0
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
# e.g. file_write 'desc.txt', 'Controls the door entry system.'
|
171
|
+
#
|
172
|
+
def file_write(file, content)
|
173
|
+
|
174
|
+
instructions = "echo #{content.inspect} >> #{file}"
|
175
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
176
|
+
puts 'r: ' + r.inspect if @debug
|
177
|
+
|
178
|
+
@results[:file_write] = r
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
# return the string supplied
|
183
|
+
#
|
184
|
+
def install(package)
|
185
|
+
|
186
|
+
return @results[:install] = 'no route to internet' unless internet?
|
187
|
+
return @results[:install] = 'already installed' if installed? package
|
188
|
+
|
189
|
+
instructions = "apt-get update && apt-get install #{package} -y"
|
190
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
191
|
+
puts 'r: ' + r.inspect if @debug
|
192
|
+
@results[:install] = r.chomp
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
def installable?(package)
|
197
|
+
|
198
|
+
instructions = "apt-cache search --names-only ^#{package}$"
|
199
|
+
results = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
200
|
+
puts 'results: ' + results.inspect if @debug
|
201
|
+
|
202
|
+
@results[:installable?] = !results.empty?
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
def installed?(package)
|
207
|
+
|
208
|
+
instructions = 'dpkg --get-selections | grep -i ' + package
|
209
|
+
results = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
210
|
+
puts 'results: ' + results.inspect if @debug
|
211
|
+
|
212
|
+
return @results[:installed?] = nil if results.empty?
|
213
|
+
r = results.lines.grep /^#{package}/
|
214
|
+
|
215
|
+
@results[:installed?] = r.any?
|
216
|
+
end
|
77
217
|
|
78
|
-
|
218
|
+
def internet?()
|
219
|
+
|
220
|
+
instructions = "ping #{@dns} -W 1 -c 1"
|
221
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
222
|
+
puts 'r: ' + r.inspect if @debug
|
223
|
+
|
224
|
+
@results[:internet?] = r.lines[1][/icmp_seq/] ? true : false
|
79
225
|
|
80
|
-
|
81
|
-
#
|
82
|
-
def disk_space()
|
226
|
+
end
|
83
227
|
|
84
|
-
|
85
|
-
|
228
|
+
# find out available memory etc
|
229
|
+
#
|
230
|
+
def memory()
|
86
231
|
|
87
|
-
|
232
|
+
instructions = 'free -h'
|
88
233
|
|
89
|
-
|
234
|
+
puts ('instructions: ' + instructions.inspect).debug if @debug
|
235
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
236
|
+
puts ('memory: ' + r.inspect).debug if @debug
|
237
|
+
a = r.lines
|
238
|
+
total, used, avail = a[1].split.values_at(1,2,-1)
|
239
|
+
@results[:memory] = {total: total, used: used, available: avail}
|
90
240
|
|
91
|
-
|
241
|
+
end
|
242
|
+
|
243
|
+
# query the ping time
|
244
|
+
#
|
245
|
+
def ping()
|
246
|
+
|
247
|
+
ip = Resolv.getaddress(@host)
|
248
|
+
puts ('ip: ' + ip.inspect).debug if @debug
|
249
|
+
valid = pingecho(ip)
|
250
|
+
puts ('valid: ' + valid.inspect).debug if @debug
|
251
|
+
|
252
|
+
@results[:ping] = if valid then
|
253
|
+
a = [valid]
|
254
|
+
4.times {sleep 0.01; a << pingecho(ip)}
|
255
|
+
(a.min * 1000).round(3)
|
256
|
+
else
|
257
|
+
nil
|
258
|
+
end
|
92
259
|
|
93
|
-
|
94
|
-
size, used, avail = a[0].split(/ +/).values_at(1,2,3)
|
260
|
+
end
|
95
261
|
|
96
|
-
|
97
|
-
|
262
|
+
# query the path of the current working directory
|
263
|
+
#
|
264
|
+
def pwd()
|
265
|
+
instructions = 'pwd'
|
266
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
267
|
+
@results[:pwd] = r.chomp
|
98
268
|
end
|
99
269
|
|
100
|
-
|
270
|
+
# query the CPU temperature
|
271
|
+
#
|
272
|
+
def temperature()
|
273
|
+
instructions = 'cat /sys/class/thermal/thermal_zone0/temp'
|
274
|
+
r = @ssh ? @ssh.exec!(instructions) : `#{instructions}`
|
275
|
+
@results[:temperature] = r.chomp
|
276
|
+
end
|
277
|
+
|
278
|
+
private
|
279
|
+
|
280
|
+
|
281
|
+
def pingecho(host, timeout=5, service="echo")
|
101
282
|
|
102
|
-
|
283
|
+
elapsed = nil
|
284
|
+
time = Time.new
|
103
285
|
|
104
|
-
|
105
|
-
size, used, avail = a2[0].split(/ +/).values_at(1,2,3)
|
286
|
+
begin
|
106
287
|
|
107
|
-
|
108
|
-
|
109
|
-
|
288
|
+
Timeout.timeout(timeout) do
|
289
|
+
s = TCPSocket.new(host, service)
|
290
|
+
s.close
|
291
|
+
end
|
110
292
|
|
293
|
+
rescue Errno::ECONNREFUSED
|
294
|
+
return Time.now - time
|
295
|
+
rescue Timeout::Error, StandardError
|
296
|
+
return false
|
297
|
+
end
|
298
|
+
|
299
|
+
# it should not reach this far
|
300
|
+
return true
|
301
|
+
end
|
302
|
+
|
303
|
+
|
111
304
|
end
|
112
305
|
|
113
|
-
|
306
|
+
def initialize(scroll, domain: nil, target: nil, pwlist: {}, password: nil,
|
307
|
+
user: nil, debug: false, dns: '208.67.222.222')
|
308
|
+
|
309
|
+
@results = {}
|
114
310
|
|
115
|
-
|
116
|
-
|
117
|
-
|
311
|
+
@scroll, @debug, @dns = scroll, debug, dns
|
312
|
+
|
313
|
+
puts '@dns: ' + @dns.inspect if @debug
|
314
|
+
|
315
|
+
target = [target] if target.is_a? String
|
118
316
|
|
119
|
-
|
317
|
+
@nodes = if target then
|
120
318
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
319
|
+
target = [target] if target.is_a? String
|
320
|
+
|
321
|
+
target.inject({}) do |r,x|
|
322
|
+
host = domain ? x + '.' + domain : x
|
323
|
+
passwd = pwlist[x] || password
|
324
|
+
userhost = user ? user + '@' + host : host
|
325
|
+
r.merge({userhost => passwd})
|
326
|
+
end
|
127
327
|
|
128
|
-
end
|
129
|
-
|
130
|
-
# query the ping time
|
131
|
-
#
|
132
|
-
def ping()
|
133
|
-
|
134
|
-
resolver = Resolve::Hostname.new
|
135
|
-
ip = resolver.getaddress(@host)
|
136
|
-
puts ('ip: ' + ip.inspect).debug if @debug
|
137
|
-
valid = pingecho(ip)
|
138
|
-
puts ('valid: ' + valid.inspect).debug if @debug
|
139
|
-
|
140
|
-
@results[@host][:ping] = if valid then
|
141
|
-
a = [valid]
|
142
|
-
4.times {sleep 0.01; a << pingecho(ip)}
|
143
|
-
(a.min * 1000).round(3)
|
144
328
|
else
|
145
|
-
|
329
|
+
{}
|
146
330
|
end
|
147
|
-
|
148
331
|
end
|
149
332
|
|
150
|
-
#
|
333
|
+
# cast out the thing and hope for the best
|
151
334
|
#
|
152
|
-
def
|
153
|
-
instructions = 'pwd'
|
154
|
-
r = @ssh ? @ssh.exec!(instructions) : system(instructions)
|
155
|
-
@results[@host][:pwd] = r.chomp
|
156
|
-
end
|
335
|
+
def cast()
|
157
336
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
r = @ssh ? @ssh.exec!(instructions) : system(instructions)
|
163
|
-
@results[@host][:temperature] = r.chomp
|
164
|
-
end
|
165
|
-
|
166
|
-
private
|
167
|
-
|
168
|
-
|
169
|
-
def pingecho(host, timeout=5, service="echo")
|
337
|
+
if @nodes.any? then
|
338
|
+
|
339
|
+
threads = []
|
340
|
+
dns = @dns
|
170
341
|
|
171
|
-
|
172
|
-
|
342
|
+
@nodes.each do |raw_host, password|
|
343
|
+
|
344
|
+
host, user = raw_host.split(/@/,2).reverse
|
345
|
+
@results[host] = {}
|
346
|
+
|
347
|
+
threads << Thread.new do
|
348
|
+
begin
|
349
|
+
puts ('host: ' + host.inspect).debug if @debug
|
350
|
+
ssh = Net::SSH.start( host, user, password: password)
|
351
|
+
@results[host] = Session.new(host, ssh, dns: dns, debug: @debug).exec @scroll
|
352
|
+
ssh.close
|
353
|
+
puts (host + ' result: ' + @results[host].inspect).debug if @debug
|
354
|
+
rescue
|
355
|
+
@results[host] = nil
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
173
360
|
|
174
|
-
|
361
|
+
threads.each(&:join)
|
175
362
|
|
176
|
-
|
177
|
-
|
178
|
-
|
363
|
+
else
|
364
|
+
|
365
|
+
if @scroll then
|
366
|
+
host = `hostname`.chomp
|
367
|
+
@results[host] = Session.new(host, dns: @dns, debug: @debug).exec(@scroll)
|
179
368
|
end
|
180
|
-
|
181
|
-
rescue Errno::ECONNREFUSED
|
182
|
-
return Time.now - time
|
183
|
-
rescue Timeout::Error, StandardError
|
184
|
-
return false
|
369
|
+
|
185
370
|
end
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
end
|
371
|
+
|
372
|
+
@scroll = nil
|
373
|
+
@results
|
374
|
+
end
|
190
375
|
|
191
376
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: miab
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Robertson
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
9irKsAp/hZt3dTbQOtnSlc9XREZZdegwOgu1FEqBqviNIn9R28OR237HExiWXwmw
|
36
36
|
HDTFIjk8XBqTunABuFUgr4qx
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date:
|
38
|
+
date: 2020-08-27 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: net-ssh
|
@@ -43,20 +43,20 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
46
|
+
version: 6.1.0
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '
|
49
|
+
version: '6.1'
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
56
|
+
version: 6.1.0
|
57
57
|
- - "~>"
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: '
|
59
|
+
version: '6.1'
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
61
|
name: c32
|
62
62
|
requirement: !ruby/object:Gem::Requirement
|
@@ -77,26 +77,6 @@ dependencies:
|
|
77
77
|
- - "~>"
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '0.2'
|
80
|
-
- !ruby/object:Gem::Dependency
|
81
|
-
name: resolve-hostname
|
82
|
-
requirement: !ruby/object:Gem::Requirement
|
83
|
-
requirements:
|
84
|
-
- - ">="
|
85
|
-
- !ruby/object:Gem::Version
|
86
|
-
version: 0.1.0
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0.1'
|
90
|
-
type: :runtime
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: 0.1.0
|
97
|
-
- - "~>"
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
version: '0.1'
|
100
80
|
description:
|
101
81
|
email: james@jamesrobertson.eu
|
102
82
|
executables: []
|
metadata.gz.sig
CHANGED
Binary file
|