|
|
|
@ -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")
|
|
|
|
|
|
|
|
|
|