diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 8f9d20305..ebb220591 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -31,7 +31,8 @@ func ActLikeCLI() bool { return false } switch os.Args[1] { - case "up", "status", "netcheck", "-h", "--help": + case "up", "status", "netcheck", "version", + "-V", "--version", "-h", "--help": return true } return false @@ -39,6 +40,10 @@ func ActLikeCLI() bool { // Run runs the CLI. The args do not include the binary name. func Run(args []string) error { + if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") { + args = []string{"version"} + } + rootfs := flag.NewFlagSet("tailscale", flag.ExitOnError) rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled's unix socket") @@ -54,6 +59,7 @@ change in the future. upCmd, netcheckCmd, statusCmd, + versionCmd, }, FlagSet: rootfs, Exec: func(context.Context, []string) error { return flag.ErrHelp }, diff --git a/cmd/tailscale/cli/version.go b/cmd/tailscale/cli/version.go new file mode 100644 index 000000000..813b6731e --- /dev/null +++ b/cmd/tailscale/cli/version.go @@ -0,0 +1,69 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cli + +import ( + "context" + "flag" + "fmt" + "log" + + "github.com/peterbourgon/ff/v2/ffcli" + "tailscale.com/ipn" + "tailscale.com/version" +) + +var versionCmd = &ffcli.Command{ + Name: "version", + ShortUsage: "version [flags]", + ShortHelp: "Print Tailscale version", + FlagSet: (func() *flag.FlagSet { + fs := flag.NewFlagSet("version", flag.ExitOnError) + fs.BoolVar(&versionArgs.daemon, "daemon", true, "also print local node's daemon version") + return fs + })(), + Exec: runVersion, +} + +var versionArgs struct { + daemon bool // also check local node's daemon version +} + +func runVersion(ctx context.Context, args []string) error { + if len(args) > 0 { + log.Fatalf("too many non-flag arguments: %q", args) + } + if !versionArgs.daemon { + fmt.Println(version.LONG) + return nil + } + fmt.Printf("Client: %s\n", version.LONG) + + c, bc, ctx, cancel := connect(ctx) + defer cancel() + + bc.AllowVersionSkew = true + + done := make(chan struct{}) + + bc.SetNotifyCallback(func(n ipn.Notify) { + if n.ErrMessage != nil { + log.Fatal(*n.ErrMessage) + } + if n.Status != nil { + fmt.Printf("Daemon: %s\n", n.Version) + close(done) + } + }) + go pump(ctx, bc, c) + + bc.RequestStatus() + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +}