|
|
|
|
@ -27,6 +27,7 @@ import ( |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"tailscale.com/client/tailscale/apitype" |
|
|
|
|
"tailscale.com/clientupdate" |
|
|
|
|
"tailscale.com/envknob" |
|
|
|
|
"tailscale.com/health" |
|
|
|
|
"tailscale.com/hostinfo" |
|
|
|
|
@ -125,6 +126,9 @@ var handler = map[string]localAPIHandler{ |
|
|
|
|
"watch-ipn-bus": (*Handler).serveWatchIPNBus, |
|
|
|
|
"whois": (*Handler).serveWhoIs, |
|
|
|
|
"query-feature": (*Handler).serveQueryFeature, |
|
|
|
|
"update/check": (*Handler).serveUpdateCheck, |
|
|
|
|
"update/install": (*Handler).serveUpdateInstall, |
|
|
|
|
"update/progress": (*Handler).serveUpdateProgress, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
@ -2418,6 +2422,75 @@ func (h *Handler) serveDebugLog(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
w.WriteHeader(http.StatusNoContent) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// serveUpdateCheck returns the ClientVersion from Status, which contains
|
|
|
|
|
// information on whether an update is available, and if so, what version,
|
|
|
|
|
// *if* we support auto-updates on this platform. If we don't, this endpoint
|
|
|
|
|
// always returns a ClientVersion saying we're running the newest version.
|
|
|
|
|
// Effectively, it tells us whether serveUpdateInstall will be able to install
|
|
|
|
|
// an update for us.
|
|
|
|
|
func (h *Handler) serveUpdateCheck(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
if r.Method != "GET" { |
|
|
|
|
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_, err := clientupdate.NewUpdater(clientupdate.Arguments{ |
|
|
|
|
ForAutoUpdate: true, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
// if we don't support auto-update, just say that we're up to date
|
|
|
|
|
if errors.Is(err, errors.ErrUnsupported) { |
|
|
|
|
json.NewEncoder(w).Encode(tailcfg.ClientVersion{RunningLatest: true}) |
|
|
|
|
} else { |
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError) |
|
|
|
|
} |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cv := h.b.StatusWithoutPeers().ClientVersion |
|
|
|
|
// ipnstate.Status documentation notes that ClientVersion may be nil on some
|
|
|
|
|
// platforms where this information is unavailable. In that case, return a
|
|
|
|
|
// ClientVersion that says we're up to date, since we have no information on
|
|
|
|
|
// whether an update is possible.
|
|
|
|
|
if cv == nil { |
|
|
|
|
cv = &tailcfg.ClientVersion{RunningLatest: true} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
json.NewEncoder(w).Encode(cv) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// serveUpdateInstall sends a request to the LocalBackend to start a Tailscale
|
|
|
|
|
// self-update. A successful response does not indicate whether the update
|
|
|
|
|
// succeeded, only that the request was accepted. Clients should use
|
|
|
|
|
// serveUpdateProgress after pinging this endpoint to check how the update is
|
|
|
|
|
// going.
|
|
|
|
|
func (h *Handler) serveUpdateInstall(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
if r.Method != "POST" { |
|
|
|
|
http.Error(w, "only POST allowed", http.StatusMethodNotAllowed) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusAccepted) |
|
|
|
|
|
|
|
|
|
go h.b.DoSelfUpdate() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// serveUpdateProgress returns the status of an in-progress Tailscale self-update.
|
|
|
|
|
// This is provided as a slice of ipnstate.UpdateProgress structs with various
|
|
|
|
|
// log messages in order from oldest to newest. If an update is not in progress,
|
|
|
|
|
// the returned slice will be empty.
|
|
|
|
|
func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
if r.Method != "GET" { |
|
|
|
|
http.Error(w, "only GET allowed", http.StatusMethodNotAllowed) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ups := h.b.GetSelfUpdateProgress() |
|
|
|
|
|
|
|
|
|
json.NewEncoder(w).Encode(ups) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
metricInvalidRequests = clientmetric.NewCounter("localapi_invalid_requests") |
|
|
|
|
|
|
|
|
|
|