concerto_client 0.0.1

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,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: []