Merge ~knitzsche/snappy-hwe-snaps/+git/wifi-connect:defaults into ~snappy-hwe-team/snappy-hwe-snaps/+git/wifi-connect:master

Proposed by Kyle Nitzsche
Status: Merged
Approved by: Roberto Mier Escandon
Approved revision: 174a30c8d126a5c65dd79770c04e9561a52b0c16
Merged at revision: d8fe23b5e46dd61a32c597d146aa434feb2958cd
Proposed branch: ~knitzsche/snappy-hwe-snaps/+git/wifi-connect:defaults
Merge into: ~snappy-hwe-team/snappy-hwe-snaps/+git/wifi-connect:master
Diff against target: 941 lines (+700/-38)
15 files modified
README.md (+1/-1)
daemon/daemon.go (+66/-6)
daemon/daemon_test.go (+121/-20)
docs/faq.md (+10/-0)
docs/index.md (+24/-0)
docs/installation.md (+121/-0)
docs/integrate.md (+70/-0)
docs/metadata.yaml (+12/-0)
hooks/configure.go (+111/-0)
hooks/configure_test.go (+110/-0)
service/service.go (+14/-6)
snap/snapcraft.yaml (+8/-3)
static/tests/pre-config0.json (+7/-0)
static/tests/pre-config1.json (+5/-0)
wifiap/wifiap.go (+20/-2)
Reviewer Review Type Date Requested Status
Roberto Mier Escandon (community) Approve
Konrad Zapałowicz (community) code Approve
System Enablement Bot continuous-integration Approve
Review via email: mp+326172@code.launchpad.net

Description of the change

Add configure hook to port certain snap wifi-connect keys into SNAP_COMMON/pre-config.json.

Deamon (service/service.go and daemon pkg) uses SetDefaults() to use the json to take the appropriate actions. [1]

docs/ added this, including integrate.md to doc the exactl preconfiguration stuff for system intergrators.

wifiap/wifiap.go: added Wifiaper interface to make wifiap unit-testable in SetDefaults()

Enhanced daemon_test.go TestSetDefaults to handle various preconfigs via static/tests/pre-config*.json

[1] https://docs.google.com/document/d/1QaKcHikGi6ttuKLgT2Id42-hQXRng1B68ucxEK-2zws/edit#heading=h.ikzb28sk2cu3

To post a comment you must log in.
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sheila Miguez (codersquid) wrote :

minor inline comment

Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sheila Miguez (codersquid) wrote :

Ignore my inline comment, I missed where the user gets feedback way below.

Revision history for this message
Konrad Zapałowicz (kzapalowicz) wrote :

Comments inline.

review: Needs Fixing (code)
Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

I've left a pair of inline comments.

review: Needs Fixing
Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

After discussion with Konrad, I'll leave the markdown as is since I consistently use code blocks.

After discussion with Roberto, I'll leave the name of "Wifiaper" interface as is for consistency with golang interface naming conventions.

Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

replied to all other comments inline, mostly saying "yes you are right", one way or another ;)

Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

> After discussion with Konrad, I'll leave the markdown as is since I
> consistently use code blocks.
>
> After discussion with Roberto, I'll leave the name of "Wifiaper" interface as
> is for consistency with golang interface naming conventions.

A note here about the name of the interface:

https://golang.org/doc/effective_go.html#interface-names says "one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun". But nothing specific says about interfaces having more than one method. So, golang naming conventions nothing say about this case. (or at least I haven't found anything)

Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

thanks for pointing this out Roberto

On 06/26/2017 07:09 AM, Roberto Mier Escandón  wrote:
>> After discussion with Konrad, I'll leave the markdown as is since I
>> consistently use code blocks.
>>
>> After discussion with Roberto, I'll leave the name of "Wifiaper" interface as
>> is for consistency with golang interface naming conventions.
>
> A note here about the name of the interface:
>
> https://golang.org/doc/effective_go.html#interface-names says "one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun". But nothing specific says about interfaces having more than one method. So, golang naming conventions nothing say about this case. (or at least I haven't found anything)
>

Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

READY for review - thanks

Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

Some inline comments

review: Needs Fixing
Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

Roberto, pls see my responses, a couple small code changes coming.

Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

rereplied

Revision history for this message
Kyle Nitzsche (knitzsche) wrote :
Download full text (7.2 KiB)

On 06/27/2017 09:08 AM, Roberto Mier Escandón  wrote:
> rereplied
>
> Diff comments:
>
>> diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go
>> index c6242cd..a2bfca3 100644
>> --- a/daemon/daemon_test.go
>> +++ b/daemon/daemon_test.go
>> @@ -94,8 +98,76 @@ func TestManualMode(t *testing.T) {
>> }
>> }
>>
>> +type mockWifiap struct{}
>> +
>> +//var wifiapClient wifiapInterface
>
> commented on purpose?
>
>> +
>> +func (mock *mockWifiap) Do(req *http.Request) (*http.Response, error) {
>> + fmt.Println("==== MY do called")
>> + url := req.URL.String()
>> + if url != "http://unix/v1/configuration" {
>> + return nil, fmt.Errorf("Not valid request URL: %v", url)
>> + }
>> +
>> + if req.Method != "GET" {
>> + return nil, fmt.Errorf("Method is not valid. Expected GET, got %v", req.Method)
>> + }
>> +
>> + rawBody := `{"result":{
>> + "debug":false,
>> + "dhcp.lease-time": "12h",
>> + "dhcp.range-start": "10.0.60.2",
>> + "dhcp.range-stop": "10.0.60.199",
>> + "disabled": true,
>> + "share.disabled": false,
>> + "share-network-interface": "tun0",
>> + "wifi-address": "10.0.60.1",
>> + "wifi.channel": "6",
>> + "wifi.hostapd-driver": "nl80211",
>> + "wifi.interface": "wlan0",
>> + "wifi.interface-mode": "direct",
>> + "wifi.netmask": "255.255.255.0",
>> + "wifi.operation-mode": "g",
>> + "wifi.security": "
>> + "wifi.security-passphrase": "passphrase123",
>> + "wifi.ssid": "AP"},"status":"OK","status-code":200,""sync"}`
>> +
>> + response := http.Response{
>> + StatusCode: 200,
>> + Status: "200 OK",
>> + Body: ioutil.NopCloser(strings.NewReader(rawBody)),
>> + }
>> +
>> + return &response, nil
>> +}
>> +
>> +func (mock *mockWifiap) Show() (map[string]interface{}, error) {
>> + wifiAp := make(map[string]interface{})
>> + wifiAp["wifi.security-passphrase"] = "randompassphrase"
>> + return wifiAp, nil
>> +}
>> +
>> +func (mock *mockWifiap) Enabled() (bool, error) {
>> + return true, nil
>> +}
>> +
>> +func (mock *mockWifiap) Enable() error {
>> + return nil
>> +}
>> +func (mock *mockWifiap) Disable() error {
>> + return nil
>> +}
>> +func (mock *mockWifiap) SetSsid(s string) error {
>> + return nil
>> +}
>> +
>> +func (mock *mockWifiap) SetPassphrase(p string) error {
>> + return nil
>> +}
>> +
>> func TestSetDefaults(t *testing.T) {
>> client := GetClient()
>> + PreConfigFile = "../static/tests/pre-config0.json"
>> hfp := "/tmp/hash"
>> if _, err := os.Stat(hfp); err == nil {
>> err = os.Remove(hfp)
>> diff --git a/hooks/configure.go b/hooks/configure.go
>> new file mode 100644
>> index 0000000..c674cca
>> --- /dev/null
>> +++ b/hooks/configure.go
>> @@ -0,0 +1,111 @@
>> +// -*- Mode: Go; indent-tabs-mode: t -*-
>> +
>> +/*
>> + * Copyright (C) 2017 Canonical Ltd
>> + *
>> + * This program is free software: you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 3 as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + ...

Read more...

Revision history for this message
Roberto Mier Escandon (rmescandon) wrote :

1) can we agree to disagree on this or do you feel strongly?

+1. This is not crucial.

2) That's not quite the reason I tried to explain previously. Should we discuss in hang out so we don't keep going round in circles on this point?

Np. This way also is valid and are the names I'm using in config-ui pr. +1 also

Revision history for this message
Konrad Zapałowicz (kzapalowicz) wrote :

Some more comments, this SetDefaults thing is an interesting exercise! I'm half-way through, will finish later today.

review: Needs Fixing (code)
Revision history for this message
Konrad Zapałowicz (kzapalowicz) wrote :

Finished

review: Needs Fixing (code)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kyle Nitzsche (knitzsche) wrote :

Ready for re-review - pls see comments above (although lp formatting seems to have injected numerous extra "*"s ;)

Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
System Enablement Bot (system-enablement-ci-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Konrad Zapałowicz (kzapalowicz) wrote :

ack

review: Approve (code)
Revision history for this message
Roberto Mier Escandon (rmescandon) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/README.md b/README.md
index d100e2f..1759797 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ snap connect wifi-connect:network-manager network-manager:service
44Note: wifi-ap and network-manager interfaces auto-connect.44Note: wifi-ap and network-manager interfaces auto-connect.
45Note: Currently content share interface requires a reboot after connection, as described below.45Note: Currently content share interface requires a reboot after connection, as described below.
4646
47# Set NetWorkManager to control all networking47# Set NetworkManager to control all networking
4848
49Note: This is a temporary manual step before network-manager snap provides a config option for this.49Note: This is a temporary manual step before network-manager snap provides a config option for this.
5050
diff --git a/daemon/daemon.go b/daemon/daemon.go
index 04ff59e..a888969 100644
--- a/daemon/daemon.go
+++ b/daemon/daemon.go
@@ -18,8 +18,11 @@
18package daemon18package daemon
1919
20import (20import (
21 "encoding/json"
21 "fmt"22 "fmt"
23 "io/ioutil"
22 "os"24 "os"
25 "path/filepath"
2326
24 "launchpad.net/wifi-connect/avahi"27 "launchpad.net/wifi-connect/avahi"
25 "launchpad.net/wifi-connect/server"28 "launchpad.net/wifi-connect/server"
@@ -40,6 +43,19 @@ var waitFlagPath string
40var previousState = STARTING43var previousState = STARTING
41var state = STARTING44var state = STARTING
4245
46// PreConfigFile is the path to the file that stores the hash of the portals password
47var PreConfigFile = filepath.Join(os.Getenv("SNAP_COMMON"), "pre-config.json")
48
49// PreConfig is the struct representing a configuration
50type PreConfig struct {
51 Passphrase string `json:"wifi.security-passphrase,omitempty"`
52 Ssid string `json:"wifi.ssid,omitempty"`
53 Interface string `json:"wifi.interface,omitempty"`
54 Password string `json:"portal.password,omitempty"`
55 NoOperational bool `json:"portal.no-operational,omitempty"` //whether to show the operational portal
56 NoResetCreds bool `json:"portal.no-reset-creds,omitempty"` //whether user must reset passphrase and password on first use of mgmt portal
57}
58
43// Client is the base type for both testing and runtime59// Client is the base type for both testing and runtime
44type Client struct {60type Client struct {
45}61}
@@ -189,11 +205,55 @@ func (c *Client) OperationalServerDown() {
189 }205 }
190}206}
191207
192// SetDefaults sets defaults if not yet set. Currently the hash208// LoadPreConfig returns a PreConfig based on the pre-config.json, if present, and an error to indicate
193// for the portals password is set.209// possible json unmarshal failure
194// TODO: set default password based on MAC addr or Serial number210func LoadPreConfig() (*PreConfig, error) {
195func (c *Client) SetDefaults() {211 config := &PreConfig{}
196 if _, err := os.Stat(utils.HashFile); os.IsNotExist(err) {212 content, err := ioutil.ReadFile(PreConfigFile)
197 utils.HashIt("wifi-connect")213 if err == nil {
214 fmt.Println("== wifi-connect/daemon: preconfiguration file found")
215 }
216 err = json.Unmarshal(content, config)
217 if err != nil {
218 return config, err
219 }
220 return config, nil
221}
222
223// SetDefaults creates the run time configuration based on wifi-ap and the pre-config.json
224// configuration file, if any. The configuration is returned with an error. PreConfig.PreConfigfile
225// indicates whether a pre-config file exists.
226func (c *Client) SetDefaults(cw wifiap.Operations, config *PreConfig) error {
227 if err != nil {
228 fmt.Println("== wifi-connect/daemon/SetDefaults: preconfig unmarshall errorr:", err)
229 }
230 ap, errShow := cw.Show()
231 if errShow != nil {
232 fmt.Println("== wifi-connect/daemon/SetDefaults: wifi-ap.Show err:", errShow)
233 }
234 if ap["wifi.security-passphrase"] != config.Passphrase {
235 if len(config.Passphrase) > 0 {
236 err = cw.SetPassphrase(config.Passphrase)
237 fmt.Println("== wifi-connect/SetDefaults wifi-ap passphrase being set")
238 if err != nil {
239 fmt.Println("== wifi-connect/daemon/SetDefaults: passphrase err:", err)
240 return err
241 }
242 }
243 }
244 if len(config.Password) > 0 {
245 fmt.Println("== wifi-connect/SetDefaults portal password being set")
246 _, err = utils.HashIt(config.Password)
247 if err != nil {
248 fmt.Println("== wifi-connect/daemon/SetDefaults: password err:", err)
249 return err
250 }
251 }
252 if config.NoOperational {
253 fmt.Println("== wifi-connect/SetDefaults: operational portal is now disabled")
254 }
255 if config.NoResetCreds {
256 fmt.Println("== wifi-connect/SetDefaults: reset creds requirement is now disabled")
198 }257 }
258 return nil
199}259}
diff --git a/daemon/daemon_test.go b/daemon/daemon_test.go
index c6242cd..177c26b 100644
--- a/daemon/daemon_test.go
+++ b/daemon/daemon_test.go
@@ -18,7 +18,11 @@
18package daemon18package daemon
1919
20import (20import (
21 "fmt"
22 "io/ioutil"
23 "net/http"
21 "os"24 "os"
25 "strings"
22 "testing"26 "testing"
2327
24 "launchpad.net/wifi-connect/utils"28 "launchpad.net/wifi-connect/utils"
@@ -94,8 +98,92 @@ func TestManualMode(t *testing.T) {
94 }98 }
95}99}
96100
101type mockWifiap struct{}
102
103func (mock *mockWifiap) Do(req *http.Request) (*http.Response, error) {
104 fmt.Println("==== MY do called")
105 url := req.URL.String()
106 if url != "http://unix/v1/configuration" {
107 return nil, fmt.Errorf("Not valid request URL: %v", url)
108 }
109
110 if req.Method != "GET" {
111 return nil, fmt.Errorf("Method is not valid. Expected GET, got %v", req.Method)
112 }
113
114 rawBody := `{"result":{
115 "debug":false,
116 "dhcp.lease-time": "12h",
117 "dhcp.range-start": "10.0.60.2",
118 "dhcp.range-stop": "10.0.60.199",
119 "disabled": true,
120 "share.disabled": false,
121 "share-network-interface": "tun0",
122 "wifi-address": "10.0.60.1",
123 "wifi.channel": "6",
124 "wifi.hostapd-driver": "nl80211",
125 "wifi.interface": "wlan0",
126 "wifi.interface-mode": "direct",
127 "wifi.netmask": "255.255.255.0",
128 "wifi.operation-mode": "g",
129 "wifi.security": "
130 "wifi.security-passphrase": "passphrase123",
131 "wifi.ssid": "AP"},"status":"OK","status-code":200,""sync"}`
132
133 response := http.Response{
134 StatusCode: 200,
135 Status: "200 OK",
136 Body: ioutil.NopCloser(strings.NewReader(rawBody)),
137 }
138
139 return &response, nil
140}
141
142func (mock *mockWifiap) Show() (map[string]interface{}, error) {
143 wifiAp := make(map[string]interface{})
144 wifiAp["wifi.security-passphrase"] = "randompassphrase"
145 return wifiAp, nil
146}
147
148func (mock *mockWifiap) Enabled() (bool, error) {
149 return true, nil
150}
151
152func (mock *mockWifiap) Enable() error {
153 return nil
154}
155func (mock *mockWifiap) Disable() error {
156 return nil
157}
158func (mock *mockWifiap) SetSsid(s string) error {
159 return nil
160}
161
162func (mock *mockWifiap) SetPassphrase(p string) error {
163 return nil
164}
165
166func TestLoadPreConfig(t *testing.T) {
167 PreConfigFile = "../static/tests/pre-config0.json"
168 config, err := LoadPreConfig()
169 if err != nil {
170 t.Errorf("Unexpected error using LoadPreConfig: %s", err)
171 }
172 if config.Passphrase != "abcdefghijklmnop" {
173 t.Errorf("Passphrase of %s expected but got %s:", "abcdefghijklmnop", config.Passphrase)
174 }
175 if !config.NoOperational {
176 t.Errorf("portal.no-operational was set to true but the loaded config is %t", config.NoOperational)
177 }
178 if !config.NoResetCreds {
179 t.Errorf("portal.no-reset-creds was set to true but the loaded config is %t", config.NoResetCreds)
180 }
181
182}
183
97func TestSetDefaults(t *testing.T) {184func TestSetDefaults(t *testing.T) {
98 client := GetClient()185 client := GetClient()
186 PreConfigFile = "../static/tests/pre-config0.json"
99 hfp := "/tmp/hash"187 hfp := "/tmp/hash"
100 if _, err := os.Stat(hfp); err == nil {188 if _, err := os.Stat(hfp); err == nil {
101 err = os.Remove(hfp)189 err = os.Remove(hfp)
@@ -103,35 +191,48 @@ func TestSetDefaults(t *testing.T) {
103 t.Errorf("Could not remove previous file version")191 t.Errorf("Could not remove previous file version")
104 }192 }
105 }193 }
194 config, _ := LoadPreConfig()
106 utils.SetHashFile(hfp)195 utils.SetHashFile(hfp)
107 client.SetDefaults()196 client.SetDefaults(&mockWifiap{}, config)
108 _, err := os.Stat(utils.HashFile)197 expectedPassphrase := "abcdefghijklmnop"
198 expectedPassword := "qwerzxcv"
199 if config.Passphrase != expectedPassphrase {
200 t.Errorf("SetDefaults: Preconfig passphrase should be %s but is %s", expectedPassphrase, config.Passphrase)
201 }
109 if os.IsNotExist(err) {202 if os.IsNotExist(err) {
110 t.Errorf("SetDefaults should have created %s but did not", hfp)203 t.Errorf("SetDefaults should have created %s but did not", hfp)
111 }204 }
112 res, _ := utils.MatchingHash("wifi-connect")205 res, _ := utils.MatchingHash(expectedPassword)
113 if !res {206 if !res {
114 t.Errorf("SetDefaults password match did not match")207 t.Errorf("SetDefaults: Preconfig password hash did not match actual")
208 }
209 if !config.NoOperational {
210 t.Errorf("SetDefaults: Preconfig portal.no-operational should be true (set) but is %t", config.NoOperational)
211 }
212 if !config.NoResetCreds {
213 t.Errorf("SetDefaults: Preconfig portal.no-reset-creds should be true (set) but is %t", config.NoResetCreds)
115 }214 }
116}
117215
118func TestSetDefaultsAlreadyExistsHashFile(t *testing.T) {216 if _, err := os.Stat(hfp); err == nil {
119 client := GetClient()217 err = os.Remove(hfp)
120 hfp := "/tmp/hash"218 if err != nil {
121 // create file if not exists219 t.Errorf("Could not remove previous file version")
122 if _, err := os.Stat(utils.HashFile); os.IsNotExist(err) {
123 if _, err = os.OpenFile(hfp, os.O_CREATE, 0666); err != nil {
124 t.Errorf("Error creating %v file", hfp)
125 }220 }
126 }221 }
127 utils.SetHashFile(hfp)222 PreConfigFile = "../static/tests/pre-config1.json"
128 client.SetDefaults()223 config, _ = LoadPreConfig()
129 _, err := os.Stat(utils.HashFile)224 client.SetDefaults(&mockWifiap{}, config)
130 if os.IsNotExist(err) {225 if len(config.Passphrase) > 0 {
131 t.Errorf("SetDefaults should have created %s but did not", hfp)226 t.Errorf("SetDefaults: Preconfig passphrase was not set but is %s", config.Passphrase)
132 }227 }
133 res, _ := utils.MatchingHash("wifi-connect")228 res2, _ := utils.MatchingHash(expectedPassword)
134 if !res {229 if res2 {
135 t.Errorf("SetDefaults password match did not match")230 t.Errorf("SetDefaults: Preconfig password was not set, but the hash matched")
231 }
232 if config.NoOperational {
233 t.Errorf("SetDefaults: Preconfig portal.no-operational should be false (unset) but is %t", config.NoOperational)
234 }
235 if config.NoResetCreds {
236 t.Errorf("SetDefaults: Preconfig portal.no-reset-creds should be false (unnset) but is %t", config.NoResetCreds)
136 }237 }
137}238}
diff --git a/docs/faq.md b/docs/faq.md
138new file mode 100644239new file mode 100644
index 0000000..ec8f87e
--- /dev/null
+++ b/docs/faq.md
@@ -0,0 +1,10 @@
1---
2title: "Frequently asked questions"
3table_of_contents: True
4---
5
6# Restarting the wifi-connect daemon
7
8```bash
9sudo systemctl restart snap.wifi-connect.daemon.service
10```
diff --git a/docs/index.md b/docs/index.md
0new file mode 10064411new file mode 100644
index 0000000..679b180
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,24 @@
1---
2title: "Overview"
3table_of_contents: True
4---
5
6# Overview
7
8The wifi-connect snap allows you to connect your device to an external Wi-Fi access point. It does this by putting up its own Wi-Fi AP and web page. You join the AP and the web page lists external APs, which you can then select and join.
9
10Wifi-connect is appropriate for simple use cases where there is no other control of networking. Wifi-connect has a daemon that takes over networking and controls device state automatically:
11
12 * When there is no external AP connection, wifi-connect starts its own AP and the Management web page (which allows you to select and join external WiFI APs)
13 * When there is an external AP connection, wifi-connect ensures its own AP is down and puts up the Operational web page (which allows you to disconnect from the external AP)
14
15Wifi-connect uses two other snaps:
16
17 * wifi-ap: provides the AP function
18 * network-manager: handles networking (as a part of installation, the device netplan is modified to make network-manager the renderer for all networking).
19
20Wifi-connect can be:
21
22 * Installed at run time
23 * Integrated into an image, with options. See "Integrating into an Image" section
24
diff --git a/docs/installation.md b/docs/installation.md
0new file mode 10064425new file mode 100644
index 0000000..deba1ec
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,121 @@
1---
2title: "Installation"
3table_of_contents: True
4---
5
6# Overview
7
8The wifi-connect snap is currently publish in edge and beta channels.
9
10## Install snaps
11
12```bash
13snap install wifi-ap
14snap install network-manager
15snap install --edge|beta wifi-connect
16```
17
18## Connect interfaces
19
20```bash
21snap connect wifi-connect:control wifi-ap:control
22snap connect wifi-connect:network core:network
23snap connect wifi-connect:network-bind core:network-bind
24snap connect wifi-connect:network-manager network-manager:service
25```
26
27## Set NetWorkManager to control all networking
28
29**Note**: This is a temporary manual step before network-manager snap provides a config option for this.
30
31**Note**: Depending on your environment, after this you may need to use a new IP address to connect to the device.
32
33 1. Backup the existing /etc/netplan/00-snapd-config.yaml file
34
35 sudo mv /etc/netplan/00-snapd-config.yaml ~/
36
37 1. Create a new netplan config file named /etc/netplan/00-default-nm-renderer.yaml:
38
39 sudo vi /etc/netplan/00-default-nm-renderer.yaml
40
41 Add the following two lines:
42
43 network:
44 renderer: NetworkManager
45
46## Reboot
47
48Rebooting addresses a potential content sharing interface issue.
49
50Rebooting also consolidates all networking into NetworkManager.
51
52## Optionally configure wifi-ap SSID/passphrase
53
54If you skip these steps, the wifi-AP put up by the device has an SSID of "Ubuntu" and is unsecure (with no passphrase).
55
56 1. Set the wifi-ap AP SSID
57
58 sudo wifi-connect ssid MYSSID
59
60 1. Set the AP passphrase:
61
62 sudo wifi-connect passphrase MYPASSPHRASE
63
64## Display the AP config
65
66```bash
67sudo wifi-connect show-ap
68```
69
70**Note** the DHCP range:
71
72 dhcp.range-start: 10.0.60.2
73 dhcp.range-stop: 10.0.60.199
74
75## Set the portal password
76
77The portal password must be entered to access wifi-connect web pages.
78
79```bash
80sudo wifi-connect set-portal-password PASSWORD
81```
82
83## Join the device AP
84
85When the device AP is up and available to you, join it.
86
87## Open the the Management portal web page
88
89This portal displays external wifi APs and let's you join them.
90
91After you connect to the device AP, you can open its http portal at the .1 IP address just before the start of the DHCP range (see previous steps) using port 8080:
92
93 10.0.60.1:8080
94
95You then need to enter the portal password to continue.
96
97### Avahi and hostname
98
99You can also connect to the device's web page using the device host name:
100
101 http://HOSTNAME.local:8080
102
103Where HOSTNAME is the hostname of the device when it booted. (Changing hostname with the hostname command at run time is not sufficient.)
104
105**Note**: The system trying to open the web page must support Avahi. Android systems may not, for example.
106
107## Be patient, it takes minutes
108
109Wifi-connect pauses for about a minute at daemon start to allow any external AP connections to complete.
110
111## Disconnect from wifi
112
113When connected to an external AP, the Operational portal is available on the device IP address (assigned by the external AP). Open it using IP:8080, enter the portal password, and you may then disconnect with the "Disconnect from Wifi" button.
114
115You can also ssh to the device and:
116
117 * Use `nmcli c` to display connections.
118 * Use `nmcli c delete CONNECTION_NAME` to disconnect and delete. This puts the device into management mode, bringing up the AP and portal.
119
120Disconnecting sets the device back in Management mode. Its AP is started and you can open the portal (as discussed above) to see external APs and connect to one.
121
diff --git a/docs/integrate.md b/docs/integrate.md
0new file mode 100644122new file mode 100644
index 0000000..b1cdd4f
--- /dev/null
+++ b/docs/integrate.md
@@ -0,0 +1,70 @@
1---
2title: "Integrating into an image""
3table_of_contents: True
4---
5
6# Overview
7
8When you pre-install wifi-connect snap into an image, you can use the gadget snap's [gadget.yaml](https://forum.snapcraft.io/t/the-gadget-snap/696) file to pre-configure some options.
9
10Here we explain the snap key and value needed to preconfigure. Refer to the above like for details on how to set these in the gadget snap's gadget.yaml file.
11
12You may also set these at run time from terminal with:
13
14```bash
15$ snap set wifi-connect KEY=VALUE
16```
17
18To apply such run-time changes, see the Frequently asked questions page.
19
20**Warning**: These changes may create security risks. Only use take these steps if you are completely aware of take responsitbility for the potential risk.
21
22
23**Note** When the deamon starts, it logs any preconfigurations found and applied, for example:
24
25```bash
26Jun 20 22:07:16 thehost snap[18004]: == wifi-connect/SetDefaults portal password being set
27Jun 20 22:07:16 thehost snap[18004]: == wifi-connect/SetDefaults: reset creds requirement is now disabled
28```
29
30## AP passphrase
31
32Normally the wifi-conect AP's passphrase is randomly created by the wifi-ap snap. A normal part of the installation process is resetting this from the terminal. However, some integrators may want to preset the passphrase.
33
34Preset the passphrase with the following:
35
36 * snapd key: wifi.security-passphrase
37 * value: the passhrase (8-13 characters, must start with a letter)
38
39## Portal password
40
41To access any wifi-connect web page you need to enter the portal password. A normal part of the installation process is setting this from the terminal. However, some integrators may want to preset the portal password.
42
43Preset the portal password with the following:
44
45 * snapd key: portal.password
46 * value: the password (8-13 characters, must start with a letter)
47
48## Disable credential resetting
49
50Normally, the first user to access the Management portal is required to reset the wifi-connect AP passphrase and the portal password (used to access wifi-connect web pages). This is an important security feature, especially for integrators that preset these in their image because every image has the same passphrase and password.
51
52However, some integrators may want to disable this requirement.
53
54Disable the requirement to reset the AP passphrase and portal password on first use of the Management portal as follows:
55
56 * snapd key: portal.no-reset-creds
57 * value: true
58
59## Disable display of the Operational Portal
60
61The Operational portal is available when the device is connected to an external AP. It provides a button the user can click to disconnect. Display of this page can be disabled. This is a normal part of wifi-connect. When the page is disabled one must use the terminal (or some other means) to direct the device to disconnect form the external AP.
62
63Disable the Operational portal as follows:
64
65 * snapd key: portal.no-opertaional
66 * value: true
67
68
69
70
diff --git a/docs/metadata.yaml b/docs/metadata.yaml
0new file mode 10064471new file mode 100644
index 0000000..2fb6ea0
--- /dev/null
+++ b/docs/metadata.yaml
@@ -0,0 +1,12 @@
1site_title: wifi-connect
2site_logo_url: https://assets.ubuntu.com/v1/c5cb0f8e-picto-ubuntu.svg
3navigation:
4 - title: Introduction
5 location: index.md
6 children:
7 - title: Installation
8 location: installation.md
9 - title: Integrating into an image
10 location: integrate.md
11 - title: Frequently asked questions
12 location: faq.md
diff --git a/hooks/configure.go b/hooks/configure.go
0new file mode 10064413new file mode 100644
index 0000000..601a93a
--- /dev/null
+++ b/hooks/configure.go
@@ -0,0 +1,111 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2017 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package main
21
22import (
23 "encoding/json"
24 "io/ioutil"
25 "log"
26 "os/exec"
27 "strings"
28
29 "launchpad.net/wifi-connect/daemon"
30)
31
32// Client is the base struct for runtime and testing
33type Client struct {
34 getter Getter
35}
36
37// Get is the test obj for overridding functions
38type Get struct{}
39
40// Getter interface is for overriding SnapGet for testing
41type Getter interface {
42 SnapGet(string) (string, error)
43}
44
45// GetClient returns a normal runtime client
46func GetClient() *Client {
47 return &Client{getter: &Get{}}
48}
49
50// ModClient returns a testing client
51func ModClient(g Getter) *Client {
52 return &Client{getter: g}
53}
54
55// SnapGet uses snapctrl to get a value from a key, or returns error
56func (g *Get) SnapGet(key string) (string, error) {
57 out, err := exec.Command("snapctl", "get", key).Output()
58 if err != nil {
59 return "", err
60 }
61 return strings.TrimSpace(string(out)), nil
62
63}
64
65// snapGetStr wraps SnapGet for string types and verifies the snap var is valid
66func (c *Client) snapGetStr(key string, target *string) {
67 val, err := c.getter.SnapGet(key)
68 if err != nil {
69 return
70 }
71 if len(val) == 0 {
72 log.Printf("== wifi-connect/configure error: key %s exists but has zero length", key)
73 return
74 }
75 *target = val
76}
77
78// snapGetBool wraps SnapGet for bool types and verifies the snap var is valid
79func (c *Client) snapGetBool(key string, target *bool) {
80 val, err := c.getter.SnapGet(key)
81 if err != nil {
82 return
83 }
84 if len(val) == 0 {
85 log.Printf("== wifi-connect/configure error: key %s exists but has zero length", key)
86 return
87 }
88
89 if val == "true" {
90 *target = true
91 } else {
92 *target = false
93 }
94}
95
96func main() {
97 client := GetClient()
98 preConfig := &daemon.PreConfig{}
99 client.snapGetStr("wifi.security-passphrase", &preConfig.Passphrase)
100 client.snapGetStr("portal.password", &preConfig.Password)
101 client.snapGetBool("portal.no-operational", &preConfig.NoOperational)
102 client.snapGetBool("portal.no-reset-creds", &preConfig.NoResetCreds)
103
104 b, errJM := json.Marshal(preConfig)
105 if errJM == nil {
106 errWJ := ioutil.WriteFile(daemon.PreConfigFile, b, 0644)
107 if errWJ != nil {
108 log.Print("== wifi-connect/configure error:", errWJ)
109 }
110 }
111}
diff --git a/hooks/configure_test.go b/hooks/configure_test.go
0new file mode 100644112new file mode 100644
index 0000000..8aebd68
--- /dev/null
+++ b/hooks/configure_test.go
@@ -0,0 +1,110 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2017 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package main
21
22import (
23 "fmt"
24 "testing"
25)
26
27type mock0 struct{}
28
29func (mock *mock0) SnapGet(s string) (string, error) {
30 return "snapgetreturn", nil
31}
32
33type Config struct {
34 AString string
35 ABool bool
36}
37
38func TestSnapGetStr0(t *testing.T) {
39 c := ModClient(&mock0{})
40 config := &Config{}
41 config.AString = "defaultstring"
42 c.snapGetStr("key", &config.AString)
43 if config.AString != "snapgetreturn" {
44 t.Errorf("snapGetStr expected snapgetreturn but got %s", config.AString)
45 }
46}
47
48type mock1 struct{}
49
50func (mock *mock1) SnapGet(s string) (string, error) {
51 return "", fmt.Errorf("intentional error 1")
52}
53
54func TestSnapGetStr1(t *testing.T) {
55 c := ModClient(&mock1{})
56 config := &Config{}
57 config.AString = "defaultstring"
58 c.snapGetStr("key", &config.AString)
59 if config.AString != "defaultstring" {
60 t.Errorf("snapGetStr expected defaultstring but got %s", config.AString)
61 }
62}
63
64type mock2 struct{}
65
66func (mock *mock2) SnapGet(s string) (string, error) {
67 return "true", nil
68}
69
70func TestSnapGetBool0(t *testing.T) {
71 c := ModClient(&mock2{})
72 config := &Config{}
73 config.ABool = false
74 c.snapGetBool("key", &config.ABool)
75 if !config.ABool {
76 t.Errorf("snapGetBool should be true but is %t", config.ABool)
77 }
78}
79
80type mock3 struct{}
81
82func (mock *mock3) SnapGet(s string) (string, error) {
83 return "false", nil
84}
85
86func TestSnapGetBool1(t *testing.T) {
87 c := ModClient(&mock3{})
88 config := &Config{}
89 config.ABool = true
90 c.snapGetBool("key", &config.ABool)
91 if config.ABool {
92 t.Errorf("snapGetBool should be false but is %t", config.ABool)
93 }
94}
95
96type mock4 struct{}
97
98func (mock *mock4) SnapGet(s string) (string, error) {
99 return "", nil
100}
101
102func TestSnapGetBool2(t *testing.T) {
103 c := ModClient(&mock4{})
104 config := &Config{}
105 config.ABool = true
106 c.snapGetBool("key", &config.ABool)
107 if !config.ABool {
108 t.Errorf("snapGetBool should be true but is %t", config.ABool)
109 }
110}
diff --git a/service/service.go b/service/service.go
index ec39f5f..c988f55 100644
--- a/service/service.go
+++ b/service/service.go
@@ -29,16 +29,22 @@ import (
29)29)
3030
31func main() {31func main() {
3232 c := netman.DefaultClient()
33 cw := wifiap.DefaultClient()
33 client := daemon.GetClient()34 client := daemon.GetClient()
34 client.SetDefaults()35
36 config, err := daemon.LoadPreConfig()
37 if err != nil {
38 fmt.Println("== wifi-connect/daemon: preconfiguration error:", err)
39 }
40 err = client.SetDefaults(cw, config)
41 if err != nil {
42 fmt.Println("== wifi-connect/daemon: SetDetaults error:", err)
43 }
35 first := true44 first := true
36 client.SetWaitFlagPath(os.Getenv("SNAP_COMMON") + "/startingApConnect")45 client.SetWaitFlagPath(os.Getenv("SNAP_COMMON") + "/startingApConnect")
37 client.SetManualFlagPath(os.Getenv("SNAP_COMMON") + "/manualMode")46 client.SetManualFlagPath(os.Getenv("SNAP_COMMON") + "/manualMode")
3847
39 c := netman.DefaultClient()
40 cw := wifiap.DefaultClient()
41
42 client.ManagementServerDown()48 client.ManagementServerDown()
43 client.OperationalServerDown()49 client.OperationalServerDown()
4450
@@ -95,7 +101,9 @@ func main() {
95 if client.GetPreviousState() == daemon.MANAGING {101 if client.GetPreviousState() == daemon.MANAGING {
96 client.ManagementServerDown()102 client.ManagementServerDown()
97 }103 }
98 client.OperationalServerUp()104 if !config.NoOperational {
105 client.OperationalServerUp()
106 }
99 continue107 continue
100 }108 }
101109
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 8bd7f04..c5fb86e 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -1,8 +1,8 @@
1name: wifi-connect 1name: wifi-connect
2version: 0.10-12version: 0.10-2-dev
3summary: Connect your device to external wifi over temp wifi AP3summary: Connect your device to external wifi over temp wifi AP
4description: |4description: |
5 A solution to enable your device to connect to ian external5 A solution to enable your device to connect to an external
6 wifi AP using a temporary wifi AP the device puts up and then6 wifi AP using a temporary wifi AP the device puts up and then
7 opening its web portal. Note that wifi-connect daemon assumes7 opening its web portal. Note that wifi-connect daemon assumes
8 control of device network management and other management solutions8 control of device network management and other management solutions
@@ -21,6 +21,10 @@ apps:
21 daemon: simple21 daemon: simple
22 plugs: [network-manager, control, network-bind]22 plugs: [network-manager, control, network-bind]
2323
24hooks:
25 configure:
26 plugs: [network]
27
24plugs:28plugs:
25 control:29 control:
26 interface: content30 interface: content
@@ -46,9 +50,10 @@ parts:
46 export GOPATH=$PWD/../go50 export GOPATH=$PWD/../go
47 cd $GOPATH/src/launchpad.net/wifi-connect51 cd $GOPATH/src/launchpad.net/wifi-connect
48 ./run-checks all52 ./run-checks all
53 mkdir -p $SNAPCRAFT_PART_INSTALL/snap/hooks
54 mv $SNAPCRAFT_PART_INSTALL/bin/hooks $SNAPCRAFT_PART_INSTALL/snap/hooks/configure
49 assets:55 assets:
50 plugin: dump56 plugin: dump
51 source: .57 source: .
52 stage:58 stage:
53 - static59 - static
54
diff --git a/static/tests/pre-config0.json b/static/tests/pre-config0.json
55new file mode 10064460new file mode 100644
index 0000000..ad904cd
--- /dev/null
+++ b/static/tests/pre-config0.json
@@ -0,0 +1,7 @@
1{
2 "wifi.security-passphrase": "abcdefghijklmnop",
3 "portal.password": "qwerzxcv",
4 "portal.no-operational": true,
5 "portal.no-reset-creds": true
6}
7
diff --git a/static/tests/pre-config1.json b/static/tests/pre-config1.json
0new file mode 1006448new file mode 100644
index 0000000..f28c4ae
--- /dev/null
+++ b/static/tests/pre-config1.json
@@ -0,0 +1,5 @@
1{
2 "portal.no-operational": false,
3 "portal.no-reset-creds": false
4}
5
diff --git a/wifiap/wifiap.go b/wifiap/wifiap.go
index b609a55..faf0c15 100644
--- a/wifiap/wifiap.go
+++ b/wifiap/wifiap.go
@@ -25,6 +25,11 @@ import (
25 "time"25 "time"
26)26)
2727
28const (
29 minPassphraseLen int = 8
30 maxPassphraseLen int = 63
31)
32
28// Client struct exposing wifi-ap operations33// Client struct exposing wifi-ap operations
29type Client struct {34type Client struct {
30 restClient *RestClient35 restClient *RestClient
@@ -40,6 +45,16 @@ func DefaultClient() *Client {
40 return &Client{restClient: defaultRestClient()}45 return &Client{restClient: defaultRestClient()}
41}46}
4247
48// Operations interface enables mock testing
49type Operations interface {
50 Show() (map[string]interface{}, error)
51 Enabled() (bool, error)
52 Enable() error
53 Disable() error
54 SetSsid(string) error
55 SetPassphrase(string) error
56}
57
43func defaultServiceURI() string {58func defaultServiceURI() string {
44 return fmt.Sprintf("http://unix%s", filepath.Join(versionURI, configurationURI))59 return fmt.Sprintf("http://unix%s", filepath.Join(versionURI, configurationURI))
45}60}
@@ -163,8 +178,11 @@ func (client *Client) SetSsid(ssid string) error {
163178
164// SetPassphrase sets the credential to access the wifi ap179// SetPassphrase sets the credential to access the wifi ap
165func (client *Client) SetPassphrase(passphrase string) error {180func (client *Client) SetPassphrase(passphrase string) error {
166 if len(passphrase) < 13 {181 if len(passphrase) < minPassphraseLen {
167 return fmt.Errorf("Passphrase must be at least 13 chars in length. Please try again")182 return fmt.Errorf("Passphrase must be at least 8 chars in length. Please try again")
183 }
184 if len(passphrase) > maxPassphraseLen {
185 return fmt.Errorf("Passphrase must be less than 64 long. Please try again")
168 }186 }
169187
170 params := map[string]string{188 params := map[string]string{

Subscribers

People subscribed via source and target branches