bandshell 0.0.8

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.
@@ -0,0 +1,11 @@
1
+ #Daemon control for the Bandshell daemon
2
+ #Usage: ruby myserver_control.rb start|stop|restart
3
+
4
+ require 'rubygems'
5
+ require 'daemons'
6
+
7
+ Daemons.run_proc('bandshell-daemon.rb') do
8
+ loop do
9
+ sleep(5)
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ #Requires "daemons" gem
3
+
4
+ #Starts the Web App Server & verifies that it is still alive
5
+ #Starts the Browser & verifies that it is still alive
6
+ #Periodically uses local library methods to check for updates from the server
7
+ #Controls screen according to rules downloaded from server
8
+
9
+ #Start the daemon loop - everything happens in there
10
+ loop do
11
+ sleep(5)
12
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'app.rb'
5
+ rescue LoadError => e
6
+ require 'rubygems'
7
+ path = File.expand_path '../../web', __FILE__
8
+ $:.unshift(path) if File.directory?(path) && !$:.include?(:path)
9
+ require 'app.rb'
10
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'concerto_client/netconfig'
4
+
5
+ ConcertoConfig::configure_system_network
6
+
@@ -0,0 +1,48 @@
1
+ require 'live_image'
2
+ require 'fileutils'
3
+
4
+ # A key/value store for strings.
5
+ # Right now implemented as disk files.
6
+ module ConcertoConfig
7
+ module ConfigStore
8
+ @@path = nil
9
+
10
+ def self.read_config(name, default='')
11
+ initialize_path if not @@path
12
+ file = File.join(@@path, name)
13
+ rofile = File.join(@@ropath, name)
14
+
15
+ # Check the read/write config location first. If nothing there,
16
+ # check the read-only location. If nothing is there, return default.
17
+ # This way writes can be made at runtime on read-only media while
18
+ # still allowing some settings to be "baked into" the media.
19
+ if File.exist?(file)
20
+ IO.read(file)
21
+ elsif File.exist?(rofile)
22
+ IO.read(rofile)
23
+ else
24
+ default
25
+ end
26
+ end
27
+
28
+ # Write a config to the read/write configuration location.
29
+ def self.write_config(name, value)
30
+ initialize_path if not @@path
31
+ file = File.join(@@path, name)
32
+
33
+ File.open(file, 'w') do |f|
34
+ f.write value
35
+ end
36
+ end
37
+
38
+ def self.initialize_path
39
+ @@ropath = File.join(LiveImage.mountpoint, 'concerto', 'config')
40
+ if LiveImage.readonly?
41
+ @@path = '/tmp/concerto/config'
42
+ else
43
+ @@path = @@ropath
44
+ end
45
+ FileUtils.mkdir_p @@path
46
+ end
47
+ end
48
+ end
data/lib/live_image.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'tempfile'
2
+
3
+ # Functions for dealing with the live image
4
+ # (where it's mounted, if it's read-only, etc)
5
+ module ConcertoConfig
6
+ module LiveImage
7
+ def self.mountpoint
8
+ '/live/image'
9
+ end
10
+
11
+ def self.readonly?
12
+ # on a readonly file system this will fail
13
+ if not File.exist? self.mountpoint
14
+ true
15
+ else
16
+ begin
17
+ f = Tempfile.new('test', self.mountpoint)
18
+ f.close!
19
+ false
20
+ rescue
21
+ # if the tempfile creation bombs we assume readonly
22
+ true
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
data/lib/netconfig.rb ADDED
@@ -0,0 +1,542 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'json'
5
+ require 'ipaddress'
6
+ require 'config_store'
7
+
8
+ # The big idea here is that we have connection methods (layer 2)
9
+ # and addressing methods (layer 3) and by combining that configuration
10
+ # information we end up with a complete network configuration.
11
+ #
12
+ # Each general layer-2 and layer-3 connection method is represented
13
+ # as a class; adding the specific details creates an instance of the class.
14
+ # The configuration is serialized as the name of the class plus the details
15
+ # needed to reconstruct the instance.
16
+ #
17
+ # Each instance can contribute lines to /etc/network/interfaces,
18
+ # the Debian standard network configuration file.
19
+ # Each instance also has the opportunity to write out other configuration
20
+ # files such as wpa_supplicant.conf, resolv.conf etc.
21
+
22
+ class Module
23
+ # Get the name of a class/module and strip off any leading modules.
24
+ # This is useful in determining arguments for Module#const_get.
25
+ def basename
26
+ name.gsub(/^.*::/, '')
27
+ end
28
+ end
29
+
30
+ module ConcertoConfig
31
+ # The Debian interfaces configuration file we are going to write out.
32
+ INTERFACES_FILE='/etc/network/interfaces'
33
+
34
+ # Some useful interface operations.
35
+ class Interface
36
+ # Wrap an interface name (eth0, wlan0 etc) with some useful operations.
37
+ def initialize(name)
38
+ @name = name
39
+ end
40
+
41
+ # Get the name of the interface as a string.
42
+ attr_reader :name
43
+
44
+ # Get the (first) IPv4 address assigned to the interface.
45
+ # Return "0.0.0.0" if we don't have any v4 addresses.
46
+ def ip
47
+ if ifconfig =~ /inet addr:([0-9.]+)/
48
+ $1
49
+ else
50
+ "0.0.0.0"
51
+ end
52
+ end
53
+
54
+ # Get the physical (mac, ethernet) address of the interface.
55
+ def mac
56
+ File.open("/sys/class/net/#{@name}/address") do |f|
57
+ f.read.chomp
58
+ end
59
+ end
60
+
61
+ def up
62
+ system("/sbin/ifconfig #{@name} up")
63
+ end
64
+
65
+ def down
66
+ system("/sbin/ifconfig #{@name} down")
67
+ end
68
+
69
+ def ifup
70
+ system("/sbin/ifup #{@name}")
71
+ end
72
+
73
+ def ifdown
74
+ system("/sbin/ifdown #{@name}")
75
+ end
76
+
77
+ def up?
78
+ if ifconfig =~ /UP/
79
+ true
80
+ else
81
+ false
82
+ end
83
+ end
84
+
85
+ def medium_present?
86
+ brought_up = false
87
+ result = false
88
+
89
+ if not up?
90
+ brought_up = true
91
+ up
92
+ sleep 10
93
+ end
94
+
95
+ if ifconfig =~ /RUNNING/
96
+ result = true
97
+ end
98
+
99
+ if brought_up
100
+ down
101
+ end
102
+
103
+ result
104
+ end
105
+ private
106
+ def ifconfig
107
+ `/sbin/ifconfig #{@name}`
108
+ end
109
+ end
110
+
111
+ # (Instance) methods that must be defined by all connection and
112
+ # addressing method classes
113
+ #
114
+ # creation and serialization:
115
+ #
116
+ # initialize(args={}): Create a new instance. When unserializing the args
117
+ # hash created during serialization is passed in.
118
+ #
119
+ # args: return a hash of data needed to reconstruct the instance. This
120
+ # hash will be passed to initialize() when unserializing.
121
+ #
122
+ # OS-level configuration:
123
+ #
124
+ # write_configs: Write out any additional configuration files needed
125
+ # (e.g. resolv.conf, wpa_supplicant.conf, ...)
126
+ #
127
+ # config_interface_name (only for connection methods): return the name of
128
+ # the physical interface to be used for the connection
129
+ #
130
+ # addressing_type (only for addressing methods): return the name of the
131
+ # addressing method (dhcp, static, manual...) to be used in the Debian
132
+ # network configuration file /etc/network/interfaces.
133
+ #
134
+ # interfaces_lines: an array of strings representing lines to be added
135
+ # to the /etc/network/interfaces file after the line naming the interface.
136
+ #
137
+ # Stuff for the Web interface:
138
+ #
139
+ # safe_assign: return an array of symbols representing fields the user
140
+ # should be allowed to modify.
141
+ #
142
+ # validate: check that the internal configuration is at least somewhat
143
+ # consistent and stands a chance of working; throw exception if not
144
+ # (FIXME! need a better error handling mechanism)
145
+ #
146
+ # and attr_accessors for everything the web interface
147
+ # should be able to assign to.
148
+ #
149
+ # Everyone must define a class method self.description as well.
150
+ # This returns a string that is displayed in the web interface dropdowns
151
+ # because using plain class identifiers there doesn't look good.
152
+ # This should be something short like "Wired Connection".
153
+
154
+ # Layer 2 connection via wired media.
155
+ # We will look for wired interfaces that have media connected,
156
+ # or use an interface chosen by the user via the args. There's
157
+ # nothing extra to be contributed to the interfaces file besides
158
+ # the name of the interface to be used.
159
+ class WiredConnection
160
+ def initialize(args={})
161
+ if args['interface_name']
162
+ @interface_name = args['interface_name']
163
+ end
164
+ end
165
+
166
+ def write_configs
167
+ # We don't need any.
168
+ end
169
+
170
+ def config_interface_name
171
+ if @interface_name && @interface_name.length > 0
172
+ # the user has specified an interface to use
173
+ @interface_name
174
+ else
175
+ # scan for the first wired interface that has media
176
+ scan_interfaces
177
+ end
178
+ end
179
+
180
+ # If interface_name is something other than nil or the empty string,
181
+ # we will override the automatic detection and use that interface.
182
+ attr_accessor :interface_name
183
+
184
+ def safe_assign
185
+ [ :interface_name ]
186
+ end
187
+
188
+ def args
189
+ {
190
+ 'interface_name' => @interface_name
191
+ }
192
+ end
193
+
194
+ def interfaces_lines
195
+ []
196
+ end
197
+
198
+ def validate
199
+ if @interface_name != ''
200
+ if self.class.interfaces.find {
201
+ |iface| iface.name == @interface_name
202
+ }.nil?
203
+ fail "The interface doesn't exist on the system"
204
+ end
205
+ end
206
+ end
207
+
208
+ # Try to find all wired interfaces on the system.
209
+ def self.interfaces
210
+ # This is somewhat Linux specific and may miss some oddballs.
211
+ devices = Dir.glob('/sys/class/net/eth*')
212
+ devices.map { |d| Interface.new(File.basename(d)) }
213
+ end
214
+
215
+ def self.description
216
+ "Wired connection"
217
+ end
218
+
219
+ private
220
+ # Find the first wired interface with medium present. If none
221
+ # is found default to eth0.
222
+ def scan_interfaces
223
+ first_with_medium = self.class.interfaces.find {
224
+ |iface| iface.medium_present?
225
+ }
226
+
227
+ if first_with_medium
228
+ first_with_medium.name
229
+ else
230
+ # if we get here no interface was found with a cable attached
231
+ # default to eth0 and hope for the best
232
+ STDERR.puts "warning: no suitable interface found, using eth0"
233
+ 'eth0'
234
+ end
235
+ end
236
+ end
237
+
238
+ # 802.11* unencrypted wireless connections.
239
+ # These are managed by wpa_supplicant on Debian so we need to create its
240
+ # configuration file and link it to the interfaces file.
241
+ class WirelessConnection
242
+ def initialize(args={})
243
+ @ssid = args['ssid'] || ''
244
+ @interface_name = args['interface_name'] if args['interface_name']
245
+ @wpa_config_file = '/tmp/wpa_supplicant.concerto.conf'
246
+ end
247
+
248
+ attr_accessor :ssid, :interface_name
249
+
250
+ def config_interface_name
251
+ # If the user has requested a specific interface, use it.
252
+ # Otherwise, just pick the first wlan interface, assuming
253
+ # it works and all wlan interfaces have approximately equal
254
+ # reception. When this assumption is wrong the user must force.
255
+ if @interface_name && @interface_name != ''
256
+ @interface_name
257
+ else
258
+ self.class.interfaces[0].name
259
+ end
260
+ end
261
+
262
+ def validate
263
+ if @ssid == ''
264
+ fail "Need SSID for wireless connection"
265
+ end
266
+ end
267
+
268
+ def safe_assign
269
+ [ :ssid, :interface_name ]
270
+ end
271
+
272
+ def write_configs
273
+ # Write a wpa_supplicant.conf file for an unsecured network.
274
+ File.open(@wpa_config_file, 'w') do |wpaconf|
275
+ # long lines, sorry!
276
+ wpaconf.puts "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev"
277
+ wpaconf.puts "network={"
278
+ wpaconf.puts "ssid=\"#{@ssid}\""
279
+ wpaconf.puts "scan_ssid=1"
280
+ wpaconf.puts "key_mgmt=NONE"
281
+ wpaconf.puts "}"
282
+ end
283
+ end
284
+
285
+ def interfaces_lines
286
+ # This links the wpa config to the interfaces file.
287
+ ["wpa-conf #{@wpa_config_file}"]
288
+ end
289
+
290
+ def args
291
+ {
292
+ 'interface_name' => @interface_name,
293
+ 'ssid' => @ssid
294
+ }
295
+ end
296
+
297
+ def self.description
298
+ "Wireless connection (no encryption)"
299
+ end
300
+
301
+ def self.interfaces
302
+ # Again this is not guaranteed to be a catch all.
303
+ devices = Dir.glob('/sys/class/net/{ath,wlan}*')
304
+ devices.map { |d| Interface.new(File.basename(d)) }
305
+ end
306
+ end
307
+
308
+ # Static IPv4 addressing.
309
+ # We use the IPAddress gem to validate that the address information
310
+ # is vaguely correct (weeding out errors like the gateway
311
+ # being on another subnet)
312
+ class StaticAddressing
313
+ def initialize(args={})
314
+ @nameservers = args['nameservers']
315
+ @address = args['address']
316
+ @netmask = args['netmask']
317
+ @gateway = args['gateway']
318
+ end
319
+
320
+ def addressing_type
321
+ 'static'
322
+ end
323
+
324
+ def args
325
+ {
326
+ 'address' => @address,
327
+ 'netmask' => @netmask,
328
+ 'gateway' => @gateway,
329
+ 'nameservers' => @nameservers
330
+ }
331
+ end
332
+
333
+ def interfaces_lines
334
+ [
335
+ "address #{@address}",
336
+ "netmask #{@netmask}",
337
+ "gateway #{@gateway}"
338
+ ]
339
+ end
340
+
341
+
342
+ def write_configs
343
+ File.open('/etc/resolv.conf','w') do |resolvconf|
344
+ @nameservers.each do |nameserver|
345
+ resolvconf.puts("nameserver #{nameserver}");
346
+ end
347
+ end
348
+ end
349
+
350
+ def self.description
351
+ "Static Addressing"
352
+ end
353
+
354
+ attr_accessor :address, :netmask, :gateway, :nameservers
355
+
356
+ def safe_assign
357
+ [ :address, :netmask, :gateway, :nameservers_flat ]
358
+ end
359
+
360
+ def validate
361
+ @address.strip!
362
+ @netmask.strip!
363
+ @gateway.strip!
364
+
365
+ if not IPAddress.valid_ipv4?(@address)
366
+ fail "Static address is invalid"
367
+ end
368
+
369
+ p @netmask
370
+ if not IPAddress.valid_ipv4_netmask?(@netmask)
371
+ fail "Static netmask is invalid"
372
+ end
373
+
374
+ p @netmask
375
+ subnet = IPAddress::IPv4.new(@address)
376
+ subnet.netmask = @netmask
377
+ if not subnet.include? IPAddress::IPv4.new(gateway)
378
+ fail "Gateway provided is unreachable"
379
+ end
380
+ end
381
+
382
+ # These next two methods are for the web interface, where it's
383
+ # more convenient to enter a bunch of nameservers on one line
384
+ # than to have to deal with an array of fields.
385
+ def nameservers_flat=(separated_list)
386
+ servers = separated_list.strip.split(/\s*[,|:;\s]\s*/)
387
+ servers.each do |server|
388
+ server.strip!
389
+ p server
390
+ if not IPAddress.valid? server
391
+ fail "One or more invalid IP addresses in nameserver list"
392
+ end
393
+ end
394
+ @nameservers = servers
395
+ end
396
+
397
+ def nameservers_flat
398
+ @nameservers.join(',')
399
+ end
400
+ end
401
+
402
+ # Dynamic IPv4 addressing via DHCP
403
+ class DHCPAddressing
404
+ def initialize(args={})
405
+ # we accept no args
406
+ end
407
+
408
+ def addressing_type
409
+ 'dhcp'
410
+ end
411
+
412
+ def interfaces_lines
413
+ # DHCP needs no additional interfaces args
414
+ # from the addressing side
415
+ []
416
+ end
417
+
418
+ def validate
419
+ # nothing to validate
420
+ end
421
+
422
+ def safe_assign
423
+ [] # no args
424
+ end
425
+
426
+ def args
427
+ { }
428
+ end
429
+
430
+ def write_configs
431
+ # dhclient will write our resolv.conf so we do not need
432
+ # to do anything
433
+ end
434
+
435
+ def self.description
436
+ "Dynamic Addressing - DHCP"
437
+ end
438
+ end
439
+
440
+ # Read a JSON formatted network configuration from the config store.
441
+ # This instantiates the connection and addressing method classes
442
+ # and returns the instances i.e. cm, am = read_network_config
443
+ #
444
+ # If no configuration is saved or it is corrupt this returns
445
+ # a default configuration that is somewhat likely to work.
446
+ def self.read_network_config
447
+ input = ConfigStore.read_config('network_config', '')
448
+
449
+ begin
450
+ args = JSON.parse(input)
451
+ rescue
452
+ # set up some sane defaults if we have no configuration
453
+ # or it can't be parsed
454
+ args = {
455
+ 'connection_method' => 'WiredConnection',
456
+ 'addressing_method' => 'DHCPAddressing',
457
+ 'connection_method_args' => { },
458
+ 'addressing_method_args' => { }
459
+ }
460
+ end
461
+
462
+ connection_method_class = ConcertoConfig.const_get(args['connection_method'])
463
+ addressing_method_class = ConcertoConfig.const_get(args['addressing_method'])
464
+
465
+ connection_method = connection_method_class.new(
466
+ args['connection_method_args']
467
+ )
468
+
469
+ addressing_method = addressing_method_class.new(
470
+ args['addressing_method_args']
471
+ )
472
+
473
+ return [connection_method, addressing_method]
474
+ end
475
+
476
+ # Save the network configuration to the configuration store.
477
+ # Arguments are instances of connection method and addressing
478
+ # method classes. Throws exception if either one is not valid.
479
+ def self.write_network_config(cm, am)
480
+ # Check that everything is consistent. If not, we currently throw
481
+ # an exception, which probably is not the best long term solution.
482
+ cm.validate
483
+ am.validate
484
+
485
+ # Serialize our instances as JSON data to be written to the config file.
486
+ json_data = {
487
+ 'connection_method' => cm.class.basename,
488
+ 'connection_method_args' => cm.args,
489
+ 'addressing_method' => am.class.basename,
490
+ 'addressing_method_args' => am.args
491
+ }.to_json
492
+
493
+ # Save the serialized configuration.
494
+ ConfigStore.write_config('network_config', json_data)
495
+ end
496
+
497
+ # This reads a JSON configuration file on STDIN and writes the interfaces
498
+ # file. Also the classes instantiated will have a chance to write
499
+ # out any auxiliary files needed.
500
+ def self.configure_system_network
501
+ connection_method, addressing_method = read_network_config
502
+
503
+ ifname = connection_method.config_interface_name
504
+
505
+ # squirrel away the name of the interface we are configuring
506
+ # This will be useful later for getting network status information.
507
+ ConfigStore.write_config('network_interface', ifname)
508
+
509
+ # Write the /etc/network/interfaces file.
510
+ File.open(INTERFACES_FILE, 'w') do |f|
511
+ f.puts "# Concerto Live network configuration"
512
+ f.puts "# Generated by netconfig.rb"
513
+ f.puts "# Changes will be lost on reboot"
514
+ f.puts "auto lo"
515
+ f.puts "iface lo inet loopback"
516
+ f.puts ""
517
+ f.puts "auto #{ifname}"
518
+ f.puts "iface #{ifname} inet #{addressing_method.addressing_type}"
519
+
520
+ addressing_method.interfaces_lines.each do |line|
521
+ f.puts "\t#{line}"
522
+ end
523
+
524
+ connection_method.interfaces_lines.each do |line|
525
+ f.puts "\t#{line}"
526
+ end
527
+ end
528
+
529
+ # Write auxiliary configuration files.
530
+ connection_method.write_configs
531
+ end
532
+
533
+ # Get the name of the interface we configured
534
+ def self.configured_interface
535
+ ifname = ConfigStore.read_config('network_interface', '')
536
+ if ifname != ''
537
+ Interface.new(ifname)
538
+ else
539
+ nil
540
+ end
541
+ end
542
+ end
data/web/app.rb ADDED
@@ -0,0 +1,320 @@
1
+ require 'rubygems'
2
+ require 'sinatra/base'
3
+ require 'haml'
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'ipaddress'
7
+ require 'netconfig'
8
+ require 'sys/uptime'
9
+ require 'sys/proctable'
10
+ include Sys
11
+
12
+ class ConcertoConfigServer < Sinatra::Base
13
+ # push these over to netconfig.rb?
14
+ # Our list of available physical-layer connection methods...
15
+ CONNECTION_METHODS = [
16
+ ConcertoConfig::WiredConnection,
17
+ ConcertoConfig::WirelessConnection
18
+ ]
19
+ # ... and available layer-3 addressing methods.
20
+ ADDRESSING_METHODS = [
21
+ ConcertoConfig::DHCPAddressing,
22
+ ConcertoConfig::StaticAddressing
23
+ ]
24
+
25
+ # Hosts we allow to access configuration without authenticating.
26
+ LOCALHOSTS = [
27
+ IPAddress.parse("127.0.0.1"),
28
+ IPAddress.parse("::1")
29
+ ]
30
+
31
+ set :haml, { :format => :html5, :layout => :main }
32
+
33
+ helpers do
34
+ # Get the return value of the method on obj if obj supports the method.
35
+ # Otherwise return the empty string.
36
+ # This is useful in views where arguments may be of diverse types.
37
+ def value_from(obj, method)
38
+ if obj.respond_to? method
39
+ obj.send method
40
+ else
41
+ ""
42
+ end
43
+ end
44
+
45
+ # Enforce authentication on actions.
46
+ # Calling from within an action will check authentication and return
47
+ # 401 if unauthorized.
48
+ def protected!
49
+ unless authorized?
50
+ response['WWW-Authenticate'] = \
51
+ %(Basic realm="Concerto Configuration")
52
+ throw(:halt, [401, "Not authorized\n"])
53
+ end
54
+ end
55
+
56
+ # Check authorization credentials.
57
+ # Currently configured to check if the REMOTE_ADDR is local and allow
58
+ # everything if so. This permits someone at local console to configure
59
+ # without the need for a password. Others must have the correct
60
+ # password to be considered authorized.
61
+ def authorized?
62
+ ip = IPAddress.parse(request.env['REMOTE_ADDR'])
63
+ password = ConcertoConfig::ConfigStore.read_config(
64
+ 'password', 'default'
65
+ )
66
+ if LOCALHOSTS.include? ip
67
+ # allow all requests from localhost no questions asked
68
+ true
69
+ else
70
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
71
+ @auth.provided? && @auth.basic? && @auth.credentials && \
72
+ @auth.credentials == ['root', password]
73
+ end
74
+ end
75
+
76
+ # Get our base URL from wherever it may be stored.
77
+ def concerto_url
78
+ ConcertoConfig::ConfigStore.read_config('concerto_url', '')
79
+ end
80
+
81
+ # Try to figure out what our current IPv4 address is
82
+ # and return it as a string.
83
+ def my_ip
84
+ iface = ConcertoConfig.configured_interface
85
+ if iface
86
+ iface.ip
87
+ else
88
+ "Network setup failed or bad configuration"
89
+ end
90
+ end
91
+
92
+ # Check if we have something resembling a network connection.
93
+ # This means we found a usable interface and it has an IPv4 address.
94
+ def network_ok
95
+ iface = ConcertoConfig.configured_interface
96
+ if iface
97
+ if iface.ip != "0.0.0.0"
98
+ true
99
+ else
100
+ false
101
+ end
102
+ else
103
+ false
104
+ end
105
+ end
106
+
107
+ # Check if we can retrieve a URL and get a 200 status code.
108
+ def validate_url(url)
109
+ begin
110
+ # this will fail with Errno::something if server
111
+ # can't be reached
112
+ response = Net::HTTP.get_response(URI(url))
113
+ if response.code != "200"
114
+ # also bomb out if we don't get an OK response
115
+ # maybe demanding 200 is too strict here?
116
+ fail
117
+ end
118
+
119
+ # if we get here we have a somewhat valid URL to go to
120
+ true
121
+ rescue
122
+ # our request bombed out for some reason
123
+ false
124
+ end
125
+ end
126
+ end
127
+
128
+ get '/' do
129
+ if concerto_url == ''
130
+ redirect '/setup'
131
+ else
132
+ redirect '/player_status'
133
+ end
134
+ end
135
+
136
+ # The local fullscreen browser will go to /screen.
137
+ # We should redirect to the screen URL if possible.
138
+ # Otherwise, we need to go to the setup page to show useful information
139
+ # and allow for local configuration if needed/wanted.
140
+ get '/screen' do
141
+ # if we don't have a URL go to setup
142
+ # if we do, check it out
143
+ if concerto_url == ''
144
+ redirect '/setup'
145
+ else
146
+ # check if the concerto server is reachable, if so redirect there
147
+ # if not redirect to a local error message screen
148
+ if validate_url(concerto_url)
149
+ redirect concerto_url
150
+ else
151
+ redirect '/problem'
152
+ end
153
+ end
154
+ end
155
+
156
+ # Present a form for entering the base URL.
157
+ get '/setup' do
158
+ protected!
159
+ if network_ok
160
+ # Everything's up and running, we just don't know what
161
+ # our URL should be.
162
+ haml :setup
163
+ else
164
+ # The network settings are not sane, we don't have an IP.
165
+ # Redirect the user to the network configuration page to
166
+ # take care of this.
167
+ #redirect '/netconfig'
168
+ end
169
+ end
170
+
171
+ # Save the Concerto base URL.
172
+ post '/setup' do
173
+ protected!
174
+ url = params[:url]
175
+ if validate_url(url)
176
+ # save to the configuration store
177
+ ConcertoConfig::ConfigStore.write_config('concerto_url', url)
178
+
179
+ # root will now redirect to the proper concerto_url
180
+ redirect '/screen'
181
+ else
182
+ # the URL was no good, back to setup!
183
+ # error handling flash something something something
184
+ redirect '/setup'
185
+ end
186
+ end
187
+
188
+ # render a page indicating that the concerto_url is no good.
189
+ # this page redirects to / every 5 seconds
190
+ get '/problem' do
191
+ haml :problem
192
+ end
193
+
194
+ get '/netconfig' do
195
+ protected!
196
+
197
+ # parse existing config file (if any)
198
+ # if there isn't one, nil will suffice because our
199
+ # value_from(...) helper will return the empty string if a method
200
+ # is not implemented. This is also how we get away with just having
201
+ # one instance each of the config classes that are currently selected.
202
+ begin
203
+ cm, am = ConcertoConfig.read_network_config
204
+ rescue Errno::ENOENT
205
+ cm = nil
206
+ am = nil
207
+ end
208
+
209
+ # view will grab what it can from our existing
210
+ # connection/addressing methods using value_from().
211
+ haml :netsettings, :locals => {
212
+ :connection_method => cm,
213
+ :addressing_method => am
214
+ }
215
+ end
216
+
217
+ # Given the name of a class, pick a class out of a list of allowed classes.
218
+ # This is used for parsing the form input for network configuration.
219
+ def pick_class(name, list)
220
+ list.find { |klass| klass.basename == name }
221
+ end
222
+
223
+ # Extract arguments from a set of form data that are intended to go to a
224
+ # specific network configuration class. These fields have names of the form
225
+ # 'ClassName/field_name'; this function returns a hash in the form
226
+ # { 'field_name' => 'value } containing the configuration arguments.
227
+ def extract_class_args(params, target_class)
228
+ result = { }
229
+ params.each do |key, value|
230
+ klass, arg = key.split('/', 2)
231
+ if klass == target_class
232
+ result[arg] = value
233
+ end
234
+ end
235
+
236
+ result
237
+ end
238
+
239
+ # Set the arguments on an instance of a given configuration class.
240
+ # This uses the safe_assign method that should be present in all
241
+ # configuration classes to determine which values are allowed to be passed
242
+ # via form fields (i.e. which ones are subject to validation)
243
+ def do_assign(params, instance)
244
+ safe = instance.safe_assign
245
+
246
+ params.each do |param, value|
247
+ if safe.include? param.intern
248
+ instance.send((param + '=').intern, value)
249
+ end
250
+ end
251
+ end
252
+
253
+ # Process the form fields and generate a JSON network configuration file.
254
+ post '/netconfig' do
255
+ protected!
256
+
257
+ # First we find the connection-method and addressing-method classes.
258
+ cmclass = pick_class(params[:connection_type], CONNECTION_METHODS)
259
+ fail "Connection method not supported" if cmclass.nil?
260
+
261
+ amclass = pick_class(params[:addressing_type], ADDRESSING_METHODS)
262
+ fail "Addressing method not supported" if amclass.nil?
263
+
264
+ # ... and create some instances of them.
265
+ cm = cmclass.new
266
+ am = amclass.new
267
+
268
+ # Now given the names of the specific classes the user has chosen,
269
+ # extract the corresponding form fields.
270
+ cmargs = extract_class_args(params, cmclass.basename)
271
+ amargs = extract_class_args(params, amclass.basename)
272
+
273
+ # Set properties on each instance given the form values passed in.
274
+ do_assign(cmargs, cm)
275
+ do_assign(amargs, am)
276
+
277
+ # Save the configuration file.
278
+ ConcertoConfig.write_network_config(cm, am)
279
+
280
+ # Reload network configuration.
281
+ STDERR.puts "Trying to bring down the interface"
282
+ if ConcertoConfig.configured_interface
283
+ ConcertoConfig.configured_interface.ifdown
284
+ end
285
+ STDERR.puts "Rewriting configuration files"
286
+ ConcertoConfig::configure_system_network
287
+ STDERR.puts "Bringing interface back up"
288
+ ConcertoConfig.configured_interface.ifup
289
+
290
+ # Back to the network form.
291
+ redirect '/netconfig' # as a get request
292
+ end
293
+
294
+ get '/password' do
295
+ protected!
296
+ haml :password
297
+ end
298
+
299
+ post '/password' do
300
+ protected!
301
+
302
+ if params[:newpass] != params[:newpass_confirm]
303
+ # something something error handling something
304
+ redirect '/password'
305
+ end
306
+
307
+ ConcertoConfig::ConfigStore.write_config('password', params[:newpass])
308
+ redirect '/setup'
309
+ end
310
+
311
+ #Shows uptime,firmware version, and general system and process information
312
+ #Requires ffi, sys-uptime, and sys-proctable gems
313
+ get '/player_status' do
314
+ @proctable = ProcTable.ps
315
+ haml :player_status
316
+ end
317
+
318
+ end
319
+
320
+ ConcertoConfigServer.run!
data/web/config.ru ADDED
@@ -0,0 +1,2 @@
1
+ require './app.rb'
2
+ run Sinatra::Application
@@ -0,0 +1,27 @@
1
+ function show_hide(id_to_show, where) {
2
+ $(where).each(function(k, el) {
3
+ if ($(el).attr('id') == id_to_show) {
4
+ $(el).show( );
5
+ } else {
6
+ $(el).hide( );
7
+ }
8
+ });
9
+ }
10
+
11
+ function update_connection_type_fields() {
12
+ var id_to_show = $("#connection_type").val();
13
+ show_hide(id_to_show, "#connection div");
14
+ }
15
+
16
+ function update_address_type_fields() {
17
+ var id_to_show = $("#addressing_type").val();
18
+ show_hide(id_to_show, "#address div");
19
+ }
20
+
21
+ $(function() {
22
+ update_connection_type_fields( );
23
+ update_address_type_fields( );
24
+
25
+ $("#connection_type").change(update_connection_type_fields);
26
+ $("#addressing_type").change(update_address_type_fields);
27
+ });
@@ -0,0 +1,7 @@
1
+ function redirect() {
2
+ window.location = '/screen';
3
+ }
4
+
5
+ $(function() {
6
+ setTimeout(redirect, 5000);
7
+ })
@@ -0,0 +1,3 @@
1
+ .error {
2
+ text-align: center;
3
+ }
Binary file
@@ -0,0 +1,6 @@
1
+ %htmlt
2
+ %head
3
+ %title="Concerto Player Network Configuration"
4
+ %script{:src=>"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"}
5
+ %link{:rel=>"stylesheet", :type=>"text/css", :href=>"/stylesheet.css"}
6
+ %body=yield
@@ -0,0 +1,64 @@
1
+ %script{:src=>"network.js"}
2
+ %a{:href=>"/setup"}="Back to setup"
3
+ %h1="Concerto Player Network Configuration"
4
+ %form{:method=>"post"}
5
+ %h2="Connection Type"
6
+ %select#connection_type{:name=>"connection_type"}
7
+ / list out our available connection methods as options
8
+ / set the html selected flag on the one that is currently
9
+ / selected in the config
10
+ - CONNECTION_METHODS.each do |cm|
11
+ %option{:value=>cm.basename,
12
+ :selected=>(cm==connection_method.class)}=cm.description
13
+ #connection
14
+ #WiredConnection
15
+ %h3="Wired Connection Settings"
16
+ %p
17
+ %label{:for=>"WiredConnection_interface_name"}="Interface Name"
18
+ %select#interface_name{:name=>"WiredConnection/interface_name"}
19
+ %option{:value=>""}="Auto Select"
20
+ / list out available interfaces and their MAC addresses
21
+ / preselect one if there was one chosen in config
22
+ - ConcertoConfig::WiredConnection.interfaces.each do |iface|
23
+ %option{:value=>iface.name, :selected=>value_from(connection_method, :interface_name)==iface.name}="#{iface.name} - #{iface.mac}"
24
+ #WirelessConnection
25
+ %h3="Wireless Connection Settings (no encryption)"
26
+ %p
27
+ %label{:for=>"WirelessConnection_interface_name"}="Interface Name"
28
+ %select#interface_name{:name=>"WirelessConnection/interface_name"}
29
+ %option{:value=>""}="Auto Select"
30
+ / same as above but with the wireless interfaces
31
+ - ConcertoConfig::WirelessConnection.interfaces.each do |iface|
32
+ %option{:value=>iface.name, :selected=>value_from(connection_method, :interface_name) == iface.name}="#{iface.name} - #{iface.mac}"
33
+ %p
34
+ %label{:for=>"WirelessConnection_ssid"}="SSID"
35
+ %input{:type=>"text", :name=>"WirelessConnection/ssid",
36
+ :value=>value_from(connection_method, :ssid)}
37
+ %h2="IP Address"
38
+ %select#addressing_type{:name=>"addressing_type"}
39
+ - ADDRESSING_METHODS.each do |am|
40
+ %option{:value=>am.basename,
41
+ :selected=>(am==addressing_method.class)}=am.description
42
+ #address
43
+ #DHCPAddressing
44
+ #StaticAddressing
45
+ %h3="Static Address Settings"
46
+ %p
47
+ %label{:for=>"StaticAddressing_address"}="Address"
48
+ %input{:type=>"text", :name=>"StaticAddressing/address",
49
+ :value=>value_from(addressing_method, :address)}
50
+ %p
51
+ %label{:for=>"StaticAddressing_netmask"}="Netmask"
52
+ %input{:type=>"text", :name=>"StaticAddressing/netmask",
53
+ :value=>value_from(addressing_method, :netmask)}
54
+ %p
55
+ %label{:for=>"StaticAddressing_gateway"}="Gateway"
56
+ %input{:type=>"text", :name=>"StaticAddressing/gateway",
57
+ :value=>value_from(addressing_method, :gateway)}
58
+ %p
59
+ %label{:for=>"StaticAddressing_address"}="Nameservers (separate with commas or spaces)"
60
+ %input{:type=>"text", :name=>"StaticAddressing/nameservers_flat",
61
+ :value=>value_from(addressing_method, :nameservers_flat)}
62
+
63
+ %input{:type=>"submit"}
64
+
@@ -0,0 +1,9 @@
1
+ %form{:method=>'post'}
2
+ %p
3
+ %label{:for=>'newpass'} New Password
4
+ %input{:type=>'password', :name=>'newpass'}
5
+ %p
6
+ %label{:for=>'newpass_confirm'} Confirm Password
7
+ %input{:type=>'password', :name=>'newpass_confirm'}
8
+ %p
9
+ %input{:type=>'submit', :value=>'Change Password'}
@@ -0,0 +1,8 @@
1
+ System Uptime: #{Uptime.uptime}
2
+
3
+ System Processes:
4
+ %br/
5
+ - for p in @proctable
6
+ %b= p.comm
7
+ (PID #{p.pid.to_s})
8
+ %br/
@@ -0,0 +1,15 @@
1
+ %script{:src=>"problem.js"}
2
+ .error
3
+ %p
4
+ %img{:src=>"trollface.png"}
5
+
6
+ %h1 Problem?
7
+ %p
8
+ Due to technical difficulties, this Concerto screen is currently not
9
+ operational. Please check again soon.
10
+ %p
11
+ ==The Concerto installation at #{concerto_url} could not be reached.
12
+ %p
13
+ If the URL needs to be changed, go back to
14
+ %a{:href=>"/setup"}
15
+ Player Configuration
@@ -0,0 +1,25 @@
1
+ %h1 Welcome to Concerto Player
2
+ %p
3
+ You're seeing this because your Concerto player has not yet been configured.
4
+ We need the URL of your Concerto instance before we can get up and running.
5
+
6
+ %form{:method=>'post'}
7
+ %p
8
+ %label{:for=>'url'} URL
9
+ %input{:type=>'text', :name=>'url'}
10
+ %input{:type=>'submit', :value=>'Here it is!'}
11
+
12
+ %p
13
+ The IPv4 address of this screen appears to be:
14
+ =my_ip
15
+ %p
16
+ ==This page can be accessed remotely via http://#{my_ip}/setup
17
+ %p
18
+ Username is root, default password is 'default'.
19
+ %p
20
+ Also, you may want to
21
+ %a{:href=>'/netconfig'} change network settings
22
+ or
23
+ %a{:href=>'/password'} change the configuration password.
24
+
25
+
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bandshell
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Armenia
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: haml
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sys-uptime
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sys-proctable
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: ipaddress
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Client-side tools for Concerto digital signage
95
+ email: andrew@asquaredlabs.com
96
+ executables:
97
+ - concerto_netsetup
98
+ - concerto_configserver
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - lib/live_image.rb
103
+ - lib/netconfig.rb
104
+ - lib/config_store.rb
105
+ - web/config.ru
106
+ - web/app.rb
107
+ - web/public/network.js
108
+ - web/public/stylesheet.css
109
+ - web/public/problem.js
110
+ - web/public/trollface.png
111
+ - web/views/main.haml
112
+ - web/views/password.haml
113
+ - web/views/setup.haml
114
+ - web/views/netsettings.haml
115
+ - web/views/problem.haml
116
+ - web/views/player_status.haml
117
+ - bin/concerto_configserver
118
+ - bin/bandshell-daemon.rb
119
+ - bin/concerto_netsetup
120
+ - bin/bandshell-control.rb
121
+ homepage:
122
+ licenses: []
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 1.8.24
142
+ signing_key:
143
+ specification_version: 3
144
+ summary: Concerto Client Tools
145
+ test_files: []