1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package service
import (
"context"
"crypto/tls"
"net"
"net/http"
"time"
"github.com/armanlife/firefox-vpn-client/core/internal/controlplane"
"github.com/armanlife/firefox-vpn-client/core/internal/update"
)
// The desktop app's updates (internal/update): the app finds a newer
// release and asks its core to download it (POST /api/update/download) and,
// once downloaded, to install it (POST /api/update/install); the status
// carries the progress (StatusView.Update). Only the program on this
// computer may ask, with the local token.
// UpdateTransports are the ways an update downloads, in order: through the
// proxy while it is on, as the proxy's clients connect (waking it, and
// routed), since GitHub may be blocked; then directly.
func (d *Daemon) UpdateTransports() []http.RoundTripper {
var out []http.RoundTripper
if engine := d.currentEngine(); engine != nil {
out = append(out, &http.Transport{
Proxy: nil,
DialContext: func(_ context.Context, _, address string) (net.Conn, error) {
return engine.routed.OpenTunnel(address)
},
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
TLSHandshakeTimeout: 30 * time.Second,
ForceAttemptHTTP2: true,
})
}
return append(out, controlplane.Transport())
}
// handleUpdateDownload starts downloading {"version": "1.2.3"}.
func (s *dashboardServer) handleUpdateDownload(w http.ResponseWriter, r *http.Request) {
m := s.updater(w, r)
if m == nil {
return
}
var in struct {
Version string `json:"version"`
}
if !readJSON(w, r, &in) {
return
}
if err := m.Download(in.Version); err != nil {
writeError(w, http.StatusBadRequest, err.Error())
return
}
writeJSON(w, http.StatusOK, m.Status())
}
// handleUpdateInstall installs the downloaded {"version": "1.2.3"}; "pid"
// is the app's process, which quits once this answers, and "args" are its
// arguments for the next start. The proxy comes back on after the update
// if it is on now.
func (s *dashboardServer) handleUpdateInstall(w http.ResponseWriter, r *http.Request) {
m := s.updater(w, r)
if m == nil {
return
}
var in struct {
Version string `json:"version"`
PID int `json:"pid"`
Args []string `json:"args"`
}
if !readJSON(w, r, &in) {
return
}
if err := m.Install(update.InstallRequest{Version: in.Version, PID: in.PID, Args: in.Args, On: s.d.IsOn()}); err != nil {
writeError(w, http.StatusConflict, err.Error())
return
}
writeJSON(w, http.StatusOK, m.Status())
}
// updater returns the daemon's updater, or answers why there is none.
func (s *dashboardServer) updater(w http.ResponseWriter, r *http.Request) *update.Manager {
if !s.d.hasLocalToken(r) {
writeError(w, http.StatusForbidden, "only the app on this computer can update it")
return nil
}
if s.d.Updater == nil {
writeError(w, http.StatusConflict, "this copy does not update itself; download the new version instead")
return nil
}
return s.d.Updater
}