gokrazy, clientupdate: add start of Gokrazy auto-updates, tests
This adds support for Gokrazy GAF (Gokrazy Archive Format) zip auto-updates, starting to wire up Tailscale's clientupdate mechanism to Gokrazy's update mechanism. Currently there's just a CLI command to update from a GAF URL, with an --unsigned flag for use in a new natlab vmtest. Next step would be publishing unstable track GAF files on pkgs.tailscale.com, with detached signatures, and then making the clientupdate mechanism also download those and check signatures. Updates #20002 Change-Id: Ib03c56f17a57f8a4638398ef83549dac4813323d Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
6ff761c5f8
commit
772be1b0cc
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/peterbourgon/ff/v3/ffcli"
|
||||
@@ -67,8 +68,19 @@ var updateArgs struct {
|
||||
version string // explicit version; empty means auto
|
||||
}
|
||||
|
||||
const gokrazyUpdateFromURLMagicArg = "--gokrazy-update-from-url"
|
||||
|
||||
func runUpdate(ctx context.Context, args []string) error {
|
||||
if len(args) > 0 {
|
||||
if runtime.GOOS == "linux" && distro.Get() == distro.Gokrazy {
|
||||
gokArgs, err := gokrazyUpdateArgsFromMagicArg(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if gokArgs != nil {
|
||||
return clientupdate.GokrazyUpdateFromURL.Get()(ctx, *gokArgs)
|
||||
}
|
||||
}
|
||||
return flag.ErrHelp
|
||||
}
|
||||
if updateArgs.version != "" && updateArgs.track != "" {
|
||||
@@ -102,3 +114,39 @@ func confirmUpdate(ver string) bool {
|
||||
msg := fmt.Sprintf("This will update Tailscale from %v to %v. Continue?", version.Short(), ver)
|
||||
return prompt.YesNo(msg, true)
|
||||
}
|
||||
|
||||
// gokrazyUpdateArgsFromMagicArg parses the Gokrazy update-from-URL command-line
|
||||
// flow. It returns nil if args do not select that flow. A non-nil result means
|
||||
// the caller may safely invoke clientupdate.GokrazyUpdateFromURL.
|
||||
func gokrazyUpdateArgsFromMagicArg(args []string) (*clientupdate.GokrazyUpdateArgs, error) {
|
||||
var updateURL string
|
||||
var unsigned bool
|
||||
|
||||
fs := flag.NewFlagSet("gokrazy-update", flag.ContinueOnError)
|
||||
fs.SetOutput(io.Discard)
|
||||
// This flag path is exercised end-to-end by TestGokrazyUpdatesItselfToSameImage.
|
||||
fs.StringVar(&updateURL, gokrazyUpdateFromURLMagicArg[2:], "", "URL of the Gokrazy archive format file to install")
|
||||
fs.BoolVar(&unsigned, "unsigned", false, "allow an unsigned GAF; for tests only")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if updateURL == "" {
|
||||
return nil, nil
|
||||
}
|
||||
if !unsigned {
|
||||
return nil, errors.New("signed GAF verification is not implemented yet; see https://github.com/tailscale/tailscale/issues/20002; pass --unsigned for test updates")
|
||||
}
|
||||
if !clientupdate.GokrazyUpdateFromURL.IsSet() {
|
||||
return nil, errors.New("gokrazy update support is not linked into this binary")
|
||||
}
|
||||
return &clientupdate.GokrazyUpdateArgs{
|
||||
URL: updateURL,
|
||||
AllowUnsigned: unsigned,
|
||||
Logf: func(format string, args ...any) {
|
||||
printf(format+"\n", args...)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -372,6 +372,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
vendor/golang.org/x/text/unicode/bidi from vendor/golang.org/x/net/idna+
|
||||
vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna
|
||||
archive/tar from tailscale.com/clientupdate
|
||||
L archive/zip from tailscale.com/clientupdate
|
||||
bufio from compress/flate+
|
||||
bytes from archive/tar+
|
||||
cmp from slices+
|
||||
|
||||
@@ -572,6 +572,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
vendor/golang.org/x/text/unicode/bidi from vendor/golang.org/x/net/idna+
|
||||
vendor/golang.org/x/text/unicode/norm from vendor/golang.org/x/net/idna
|
||||
archive/tar from tailscale.com/clientupdate
|
||||
L archive/zip from tailscale.com/clientupdate
|
||||
bufio from compress/flate+
|
||||
bytes from archive/tar+
|
||||
cmp from slices+
|
||||
|
||||
@@ -213,6 +213,24 @@ func main() {
|
||||
}
|
||||
serveCmd(w, "tailscale", args...)
|
||||
})
|
||||
ttaMux.HandleFunc("/tailscale", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveCmd(w, "tailscale", r.URL.Query()["arg"]...)
|
||||
})
|
||||
ttaMux.HandleFunc("/gokrazy-root", func(w http.ResponseWriter, r *http.Request) {
|
||||
cmdLine, err := os.ReadFile("/proc/cmdline")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for s := range strings.FieldsSeq(string(cmdLine)) {
|
||||
if root, ok := strings.CutPrefix(s, "root="); ok {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
io.WriteString(w, root+"\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
http.Error(w, "no root= in /proc/cmdline", http.StatusInternalServerError)
|
||||
})
|
||||
ttaMux.HandleFunc("/ip", func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, ok := r.Context().Value(connContextKey).(net.Conn)
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user