bandshell 0.0.8

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