concerto_client 0.0.1

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