cmd/tailscale/cli: prefix all --help usages with "tailscale ...", some tidying

Also capitalises the start of all ShortHelp, allows subcommands to be hidden
with a "HIDDEN: " prefix in their ShortHelp, and adds a TS_DUMP_HELP envknob
to look at all --help messages together.

Fixes #11664

Signed-off-by: Paul Scott <paul@tailscale.com>
This commit is contained in:
Paul Scott 2024-04-04 17:06:09 +01:00 committed by Paul Scott
parent 9da135dd64
commit da4e92bf01
33 changed files with 344 additions and 262 deletions

View File

@ -17,7 +17,7 @@
Name: "bugreport", Name: "bugreport",
Exec: runBugReport, Exec: runBugReport,
ShortHelp: "Print a shareable identifier to help diagnose issues", ShortHelp: "Print a shareable identifier to help diagnose issues",
ShortUsage: "bugreport [note]", ShortUsage: "tailscale bugreport [note]",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("bugreport") fs := newFlagSet("bugreport")
fs.BoolVar(&bugReportArgs.diagnose, "diagnose", false, "run additional in-depth checks") fs.BoolVar(&bugReportArgs.diagnose, "diagnose", false, "run additional in-depth checks")

View File

@ -28,7 +28,7 @@
Name: "cert", Name: "cert",
Exec: runCert, Exec: runCert,
ShortHelp: "Get TLS certs", ShortHelp: "Get TLS certs",
ShortUsage: "cert [flags] <domain>", ShortUsage: "tailscale cert [flags] <domain>",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("cert") fs := newFlagSet("cert")
fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset") fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset")

View File

@ -14,7 +14,6 @@
"log" "log"
"os" "os"
"runtime" "runtime"
"slices"
"strings" "strings"
"sync" "sync"
"text/tabwriter" "text/tabwriter"
@ -95,6 +94,49 @@ func Run(args []string) (err error) {
}) })
}) })
rootCmd := newRootCmd()
if err := rootCmd.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
if envknob.Bool("TS_DUMP_HELP") {
walkCommands(rootCmd, func(c *ffcli.Command) {
fmt.Println("===")
// UsageFuncs are typically called during Command.Run which ensures
// FlagSet is not nil.
if c.FlagSet == nil {
c.FlagSet = flag.NewFlagSet(c.Name, flag.ContinueOnError)
}
if c.UsageFunc != nil {
fmt.Println(c.UsageFunc(c))
} else {
fmt.Println(ffcli.DefaultUsageFunc(c))
}
})
return
}
localClient.Socket = rootArgs.socket
rootCmd.FlagSet.Visit(func(f *flag.Flag) {
if f.Name == "socket" {
localClient.UseSocketOnly = true
}
})
err = rootCmd.Run(context.Background())
if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" {
return fmt.Errorf("%v\n\nUse 'sudo tailscale %s' or 'tailscale up --operator=$USER' to not require root.", err, strings.Join(args, " "))
}
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
func newRootCmd() *ffcli.Command {
rootfs := newFlagSet("tailscale") rootfs := newFlagSet("tailscale")
rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled socket") rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled socket")
@ -134,10 +176,11 @@ func Run(args []string) (err error) {
exitNodeCmd(), exitNodeCmd(),
updateCmd, updateCmd,
whoisCmd, whoisCmd,
debugCmd,
driveCmd,
}, },
FlagSet: rootfs, FlagSet: rootfs,
Exec: func(context.Context, []string) error { return flag.ErrHelp }, Exec: func(context.Context, []string) error { return flag.ErrHelp },
UsageFunc: usageFunc,
} }
if envknob.UseWIPCode() { if envknob.UseWIPCode() {
rootCmd.Subcommands = append(rootCmd.Subcommands, rootCmd.Subcommands = append(rootCmd.Subcommands,
@ -145,45 +188,16 @@ func Run(args []string) (err error) {
) )
} }
// Don't advertise these commands, but they're still explicitly available.
switch {
case slices.Contains(args, "debug"):
rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd)
case slices.Contains(args, "drive"):
rootCmd.Subcommands = append(rootCmd.Subcommands, driveCmd)
}
if runtime.GOOS == "linux" && distro.Get() == distro.Synology { if runtime.GOOS == "linux" && distro.Get() == distro.Synology {
rootCmd.Subcommands = append(rootCmd.Subcommands, configureHostCmd) rootCmd.Subcommands = append(rootCmd.Subcommands, configureHostCmd)
} }
for _, c := range rootCmd.Subcommands { walkCommands(rootCmd, func(c *ffcli.Command) {
if c.UsageFunc == nil { if c.UsageFunc == nil {
c.UsageFunc = usageFunc c.UsageFunc = usageFunc
} }
}
if err := rootCmd.Parse(args); err != nil {
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
}
localClient.Socket = rootArgs.socket
rootfs.Visit(func(f *flag.Flag) {
if f.Name == "socket" {
localClient.UseSocketOnly = true
}
}) })
return rootCmd
err = rootCmd.Run(context.Background())
if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" {
return fmt.Errorf("%v\n\nUse 'sudo tailscale %s' or 'tailscale up --operator=$USER' to not require root.", err, strings.Join(args, " "))
}
if errors.Is(err, flag.ErrHelp) {
return nil
}
return err
} }
func fatalf(format string, a ...any) { func fatalf(format string, a ...any) {
@ -202,6 +216,13 @@ func fatalf(format string, a ...any) {
socket string socket string
} }
func walkCommands(cmd *ffcli.Command, f func(*ffcli.Command)) {
f(cmd)
for _, sub := range cmd.Subcommands {
walkCommands(sub, f)
}
}
// usageFuncNoDefaultValues is like usageFunc but doesn't print default values. // usageFuncNoDefaultValues is like usageFunc but doesn't print default values.
func usageFuncNoDefaultValues(c *ffcli.Command) string { func usageFuncNoDefaultValues(c *ffcli.Command) string {
return usageFuncOpt(c, false) return usageFuncOpt(c, false)
@ -213,23 +234,32 @@ func usageFunc(c *ffcli.Command) string {
func usageFuncOpt(c *ffcli.Command, withDefaults bool) string { func usageFuncOpt(c *ffcli.Command, withDefaults bool) string {
var b strings.Builder var b strings.Builder
const hiddenPrefix = "HIDDEN: "
if c.ShortHelp != "" {
fmt.Fprintf(&b, "%s\n\n", c.ShortHelp)
}
fmt.Fprintf(&b, "USAGE\n") fmt.Fprintf(&b, "USAGE\n")
if c.ShortUsage != "" { if c.ShortUsage != "" {
fmt.Fprintf(&b, " %s\n", c.ShortUsage) fmt.Fprintf(&b, " %s\n", strings.ReplaceAll(c.ShortUsage, "\n", "\n "))
} else { } else {
fmt.Fprintf(&b, " %s\n", c.Name) fmt.Fprintf(&b, " %s\n", c.Name)
} }
fmt.Fprintf(&b, "\n") fmt.Fprintf(&b, "\n")
if c.LongHelp != "" { if c.LongHelp != "" {
fmt.Fprintf(&b, "%s\n\n", c.LongHelp) help, _ := strings.CutPrefix(c.LongHelp, hiddenPrefix)
fmt.Fprintf(&b, "%s\n\n", help)
} }
if len(c.Subcommands) > 0 { if len(c.Subcommands) > 0 {
fmt.Fprintf(&b, "SUBCOMMANDS\n") fmt.Fprintf(&b, "SUBCOMMANDS\n")
tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0) tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
for _, subcommand := range c.Subcommands { for _, subcommand := range c.Subcommands {
if strings.HasPrefix(subcommand.LongHelp, hiddenPrefix) {
continue
}
fmt.Fprintf(tw, " %s\t%s\n", subcommand.Name, subcommand.ShortHelp) fmt.Fprintf(tw, " %s\t%s\n", subcommand.Name, subcommand.ShortHelp)
} }
tw.Flush() tw.Flush()
@ -242,7 +272,7 @@ func usageFuncOpt(c *ffcli.Command, withDefaults bool) string {
c.FlagSet.VisitAll(func(f *flag.Flag) { c.FlagSet.VisitAll(func(f *flag.Flag) {
var s string var s string
name, usage := flag.UnquoteUsage(f) name, usage := flag.UnquoteUsage(f)
if strings.HasPrefix(usage, "HIDDEN: ") { if strings.HasPrefix(usage, hiddenPrefix) {
return return
} }
if isBoolFlag(f) { if isBoolFlag(f) {

View File

@ -16,6 +16,7 @@
qt "github.com/frankban/quicktest" qt "github.com/frankban/quicktest"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/envknob" "tailscale.com/envknob"
"tailscale.com/health/healthmsg" "tailscale.com/health/healthmsg"
"tailscale.com/ipn" "tailscale.com/ipn"
@ -29,15 +30,37 @@
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
func TestPanicIfAnyEnvCheckedInInit(t *testing.T) {
envknob.PanicIfAnyEnvCheckedInInit()
}
func TestShortUsage_FullCmd(t *testing.T) {
t.Setenv("TAILSCALE_USE_WIP_CODE", "1")
if !envknob.UseWIPCode() {
t.Fatal("expected envknob.UseWIPCode() to be true")
}
// Some commands have more than one path from the root, so investigate all
// paths before we report errors.
ok := make(map[*ffcli.Command]bool)
root := newRootCmd()
walkCommands(root, func(c *ffcli.Command) {
if !ok[c] {
ok[c] = strings.HasPrefix(c.ShortUsage, "tailscale ") && (c.Name == "tailscale" || strings.Contains(c.ShortUsage, " "+c.Name+" ") || strings.HasSuffix(c.ShortUsage, " "+c.Name))
}
})
walkCommands(root, func(c *ffcli.Command) {
if !ok[c] {
t.Errorf("subcommand %s should show full usage ('tailscale ... %s ...') in ShortUsage (%q)", c.Name, c.Name, c.ShortUsage)
}
})
}
// geese is a collection of gooses. It need not be complete. // geese is a collection of gooses. It need not be complete.
// But it should include anything handled specially (e.g. linux, windows) // But it should include anything handled specially (e.g. linux, windows)
// and at least one thing that's not (darwin, freebsd). // and at least one thing that's not (darwin, freebsd).
var geese = []string{"linux", "darwin", "windows", "freebsd"} var geese = []string{"linux", "darwin", "windows", "freebsd"}
func TestPanicIfAnyEnvCheckedInInit(t *testing.T) {
envknob.PanicIfAnyEnvCheckedInInit()
}
// Test that checkForAccidentalSettingReverts's updateMaskedPrefsFromUpFlag can handle // Test that checkForAccidentalSettingReverts's updateMaskedPrefsFromUpFlag can handle
// all flags. This will panic if a new flag creeps in that's unhandled. // all flags. This will panic if a new flag creeps in that's unhandled.
// //

View File

@ -27,7 +27,7 @@ func init() {
var configureKubeconfigCmd = &ffcli.Command{ var configureKubeconfigCmd = &ffcli.Command{
Name: "kubeconfig", Name: "kubeconfig",
ShortHelp: "[ALPHA] Connect to a Kubernetes cluster using a Tailscale Auth Proxy", ShortHelp: "[ALPHA] Connect to a Kubernetes cluster using a Tailscale Auth Proxy",
ShortUsage: "kubeconfig <hostname-or-fqdn>", ShortUsage: "tailscale configure kubeconfig <hostname-or-fqdn>",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
Run this command to configure kubectl to connect to a Kubernetes cluster over Tailscale. Run this command to configure kubectl to connect to a Kubernetes cluster over Tailscale.

View File

@ -22,10 +22,11 @@
// used to configure Synology devices, but is now a compatibility alias to // used to configure Synology devices, but is now a compatibility alias to
// "tailscale configure synology". // "tailscale configure synology".
var configureHostCmd = &ffcli.Command{ var configureHostCmd = &ffcli.Command{
Name: "configure-host", Name: "configure-host",
Exec: runConfigureSynology, Exec: runConfigureSynology,
ShortHelp: synologyConfigureCmd.ShortHelp, ShortUsage: "tailscale configure-host",
LongHelp: synologyConfigureCmd.LongHelp, ShortHelp: synologyConfigureCmd.ShortHelp,
LongHelp: synologyConfigureCmd.LongHelp,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure-host") fs := newFlagSet("configure-host")
return fs return fs
@ -33,9 +34,10 @@
} }
var synologyConfigureCmd = &ffcli.Command{ var synologyConfigureCmd = &ffcli.Command{
Name: "synology", Name: "synology",
Exec: runConfigureSynology, Exec: runConfigureSynology,
ShortHelp: "Configure Synology to enable outbound connections", ShortUsage: "tailscale configure synology",
ShortHelp: "Configure Synology to enable outbound connections",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
This command is intended to run at boot as root on a Synology device to This command is intended to run at boot as root on a Synology device to
create the /dev/net/tun device and give the tailscaled binary permission create the /dev/net/tun device and give the tailscaled binary permission

View File

@ -14,8 +14,9 @@
) )
var configureCmd = &ffcli.Command{ var configureCmd = &ffcli.Command{
Name: "configure", Name: "configure",
ShortHelp: "[ALPHA] Configure the host to enable more Tailscale features", ShortUsage: "tailscale configure <subcommand>",
ShortHelp: "[ALPHA] Configure the host to enable more Tailscale features",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
The 'configure' set of commands are intended to provide a way to enable different The 'configure' set of commands are intended to provide a way to enable different
services on the host to use Tailscale in more ways. services on the host to use Tailscale in more ways.

View File

@ -45,9 +45,10 @@
) )
var debugCmd = &ffcli.Command{ var debugCmd = &ffcli.Command{
Name: "debug", Name: "debug",
Exec: runDebug, Exec: runDebug,
LongHelp: `"tailscale debug" contains misc debug facilities; it is not a stable interface.`, ShortUsage: "tailscale debug <debug-flags | subcommand>",
LongHelp: `HIDDEN: "tailscale debug" contains misc debug facilities; it is not a stable interface.`,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("debug") fs := newFlagSet("debug")
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME") fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
@ -58,15 +59,16 @@
})(), })(),
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
{ {
Name: "derp-map", Name: "derp-map",
Exec: runDERPMap, ShortUsage: "tailscale debug derp-map",
ShortHelp: "print DERP map", Exec: runDERPMap,
ShortHelp: "Print DERP map",
}, },
{ {
Name: "component-logs", Name: "component-logs",
Exec: runDebugComponentLogs,
ShortHelp: "enable/disable debug logs for a component",
ShortUsage: "tailscale debug component-logs [" + strings.Join(ipn.DebuggableComponents, "|") + "]", ShortUsage: "tailscale debug component-logs [" + strings.Join(ipn.DebuggableComponents, "|") + "]",
Exec: runDebugComponentLogs,
ShortHelp: "Enable/disable debug logs for a component",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("component-logs") fs := newFlagSet("component-logs")
fs.DurationVar(&debugComponentLogsArgs.forDur, "for", time.Hour, "how long to enable debug logs for; zero or negative means to disable") fs.DurationVar(&debugComponentLogsArgs.forDur, "for", time.Hour, "how long to enable debug logs for; zero or negative means to disable")
@ -74,14 +76,16 @@
})(), })(),
}, },
{ {
Name: "daemon-goroutines", Name: "daemon-goroutines",
Exec: runDaemonGoroutines, ShortUsage: "tailscale debug daemon-goroutines",
ShortHelp: "print tailscaled's goroutines", Exec: runDaemonGoroutines,
ShortHelp: "Print tailscaled's goroutines",
}, },
{ {
Name: "daemon-logs", Name: "daemon-logs",
Exec: runDaemonLogs, ShortUsage: "tailscale debug daemon-logs",
ShortHelp: "watch tailscaled's server logs", Exec: runDaemonLogs,
ShortHelp: "Watch tailscaled's server logs",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("daemon-logs") fs := newFlagSet("daemon-logs")
fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level") fs.IntVar(&daemonLogsArgs.verbose, "verbose", 0, "verbosity level")
@ -90,9 +94,10 @@
})(), })(),
}, },
{ {
Name: "metrics", Name: "metrics",
Exec: runDaemonMetrics, ShortUsage: "tailscale debug metrics",
ShortHelp: "print tailscaled's metrics", Exec: runDaemonMetrics,
ShortHelp: "Print tailscaled's metrics",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("metrics") fs := newFlagSet("metrics")
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values") fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
@ -100,80 +105,95 @@
})(), })(),
}, },
{ {
Name: "env", Name: "env",
Exec: runEnv, ShortUsage: "tailscale debug env",
ShortHelp: "print cmd/tailscale environment", Exec: runEnv,
ShortHelp: "Print cmd/tailscale environment",
}, },
{ {
Name: "stat", Name: "stat",
Exec: runStat, ShortUsage: "tailscale debug stat <files...>",
ShortHelp: "stat a file", Exec: runStat,
ShortHelp: "Stat a file",
}, },
{ {
Name: "hostinfo", Name: "hostinfo",
Exec: runHostinfo, ShortUsage: "tailscale debug hostinfo",
ShortHelp: "print hostinfo", Exec: runHostinfo,
ShortHelp: "Print hostinfo",
}, },
{ {
Name: "local-creds", Name: "local-creds",
Exec: runLocalCreds, ShortUsage: "tailscale debug local-creds",
ShortHelp: "print how to access Tailscale LocalAPI", Exec: runLocalCreds,
ShortHelp: "Print how to access Tailscale LocalAPI",
}, },
{ {
Name: "restun", Name: "restun",
Exec: localAPIAction("restun"), ShortUsage: "tailscale debug restun",
ShortHelp: "force a magicsock restun", Exec: localAPIAction("restun"),
ShortHelp: "Force a magicsock restun",
}, },
{ {
Name: "rebind", Name: "rebind",
Exec: localAPIAction("rebind"), ShortUsage: "tailscale debug rebind",
ShortHelp: "force a magicsock rebind", Exec: localAPIAction("rebind"),
ShortHelp: "Force a magicsock rebind",
}, },
{ {
Name: "derp-set-on-demand", Name: "derp-set-on-demand",
Exec: localAPIAction("derp-set-homeless"), ShortUsage: "tailscale debug derp-set-on-demand",
ShortHelp: "enable DERP on-demand mode (breaks reachability)", Exec: localAPIAction("derp-set-homeless"),
ShortHelp: "Enable DERP on-demand mode (breaks reachability)",
}, },
{ {
Name: "derp-unset-on-demand", Name: "derp-unset-on-demand",
Exec: localAPIAction("derp-unset-homeless"), ShortUsage: "tailscale debug derp-unset-on-demand",
ShortHelp: "disable DERP on-demand mode", Exec: localAPIAction("derp-unset-homeless"),
ShortHelp: "Disable DERP on-demand mode",
}, },
{ {
Name: "break-tcp-conns", Name: "break-tcp-conns",
Exec: localAPIAction("break-tcp-conns"), ShortUsage: "tailscale debug break-tcp-conns",
ShortHelp: "break any open TCP connections from the daemon", Exec: localAPIAction("break-tcp-conns"),
ShortHelp: "Break any open TCP connections from the daemon",
}, },
{ {
Name: "break-derp-conns", Name: "break-derp-conns",
Exec: localAPIAction("break-derp-conns"), ShortUsage: "tailscale debug break-derp-conns",
ShortHelp: "break any open DERP connections from the daemon", Exec: localAPIAction("break-derp-conns"),
ShortHelp: "Break any open DERP connections from the daemon",
}, },
{ {
Name: "pick-new-derp", Name: "pick-new-derp",
Exec: localAPIAction("pick-new-derp"), ShortUsage: "tailscale debug pick-new-derp",
ShortHelp: "switch to some other random DERP home region for a short time", Exec: localAPIAction("pick-new-derp"),
ShortHelp: "Switch to some other random DERP home region for a short time",
}, },
{ {
Name: "force-netmap-update", Name: "force-netmap-update",
Exec: localAPIAction("force-netmap-update"), ShortUsage: "tailscale debug force-netmap-update",
ShortHelp: "force a full no-op netmap update (for load testing)", Exec: localAPIAction("force-netmap-update"),
ShortHelp: "Force a full no-op netmap update (for load testing)",
}, },
{ {
// TODO(bradfitz,maisem): eventually promote this out of debug // TODO(bradfitz,maisem): eventually promote this out of debug
Name: "reload-config", Name: "reload-config",
Exec: reloadConfig, ShortUsage: "tailscale debug reload-config",
ShortHelp: "reload config", Exec: reloadConfig,
ShortHelp: "Reload config",
}, },
{ {
Name: "control-knobs", Name: "control-knobs",
Exec: debugControlKnobs, ShortUsage: "tailscale debug control-knobs",
ShortHelp: "see current control knobs", Exec: debugControlKnobs,
ShortHelp: "See current control knobs",
}, },
{ {
Name: "prefs", Name: "prefs",
Exec: runPrefs, ShortUsage: "tailscale debug prefs",
ShortHelp: "print prefs", Exec: runPrefs,
ShortHelp: "Print prefs",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("prefs") fs := newFlagSet("prefs")
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output") fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
@ -181,9 +201,10 @@
})(), })(),
}, },
{ {
Name: "watch-ipn", Name: "watch-ipn",
Exec: runWatchIPN, ShortUsage: "tailscale debug watch-ipn",
ShortHelp: "subscribe to IPN message bus", Exec: runWatchIPN,
ShortHelp: "Subscribe to IPN message bus",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("watch-ipn") fs := newFlagSet("watch-ipn")
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages") fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
@ -194,9 +215,10 @@
})(), })(),
}, },
{ {
Name: "netmap", Name: "netmap",
Exec: runNetmap, ShortUsage: "tailscale debug netmap",
ShortHelp: "print the current network map", Exec: runNetmap,
ShortHelp: "Print the current network map",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("netmap") fs := newFlagSet("netmap")
fs.BoolVar(&netmapArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap") fs.BoolVar(&netmapArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap")
@ -204,14 +226,17 @@
})(), })(),
}, },
{ {
Name: "via", Name: "via",
ShortUsage: "tailscale via <site-id> <v4-cidr>\n" +
"tailscale via <v6-route>",
Exec: runVia, Exec: runVia,
ShortHelp: "convert between site-specific IPv4 CIDRs and IPv6 'via' routes", ShortHelp: "Convert between site-specific IPv4 CIDRs and IPv6 'via' routes",
}, },
{ {
Name: "ts2021", Name: "ts2021",
Exec: runTS2021, ShortUsage: "tailscale debug ts2021",
ShortHelp: "debug ts2021 protocol connectivity", Exec: runTS2021,
ShortHelp: "Debug ts2021 protocol connectivity",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ts2021") fs := newFlagSet("ts2021")
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane") fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
@ -221,9 +246,10 @@
})(), })(),
}, },
{ {
Name: "set-expire", Name: "set-expire",
Exec: runSetExpire, ShortUsage: "tailscale debug set-expire --in=1m",
ShortHelp: "manipulate node key expiry for testing", Exec: runSetExpire,
ShortHelp: "Manipulate node key expiry for testing",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("set-expire") fs := newFlagSet("set-expire")
fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now") fs.DurationVar(&setExpireArgs.in, "in", 0, "if non-zero, set node key to expire this duration from now")
@ -231,9 +257,10 @@
})(), })(),
}, },
{ {
Name: "dev-store-set", Name: "dev-store-set",
Exec: runDevStoreSet, ShortUsage: "tailscale debug dev-store-set",
ShortHelp: "set a key/value pair during development", Exec: runDevStoreSet,
ShortHelp: "Set a key/value pair during development",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("store-set") fs := newFlagSet("store-set")
fs.BoolVar(&devStoreSetArgs.danger, "danger", false, "accept danger") fs.BoolVar(&devStoreSetArgs.danger, "danger", false, "accept danger")
@ -241,14 +268,16 @@
})(), })(),
}, },
{ {
Name: "derp", Name: "derp",
Exec: runDebugDERP, ShortUsage: "tailscale debug derp",
ShortHelp: "test a DERP configuration", Exec: runDebugDERP,
ShortHelp: "Test a DERP configuration",
}, },
{ {
Name: "capture", Name: "capture",
Exec: runCapture, ShortUsage: "tailscale debug capture",
ShortHelp: "streams pcaps for debugging", Exec: runCapture,
ShortHelp: "Streams pcaps for debugging",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("capture") fs := newFlagSet("capture")
fs.StringVar(&captureArgs.outFile, "o", "", "path to stream the pcap (or - for stdout), leave empty to start wireshark") fs.StringVar(&captureArgs.outFile, "o", "", "path to stream the pcap (or - for stdout), leave empty to start wireshark")
@ -256,9 +285,10 @@
})(), })(),
}, },
{ {
Name: "portmap", Name: "portmap",
Exec: debugPortmap, ShortUsage: "tailscale debug portmap",
ShortHelp: "run portmap debugging", Exec: debugPortmap,
ShortHelp: "Run portmap debugging",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("portmap") fs := newFlagSet("portmap")
fs.DurationVar(&debugPortmapArgs.duration, "duration", 5*time.Second, "timeout for port mapping") fs.DurationVar(&debugPortmapArgs.duration, "duration", 5*time.Second, "timeout for port mapping")
@ -270,14 +300,16 @@
})(), })(),
}, },
{ {
Name: "peer-endpoint-changes", Name: "peer-endpoint-changes",
Exec: runPeerEndpointChanges, ShortUsage: "tailscale debug peer-endpoint-changes <hostname-or-IP>",
ShortHelp: "prints debug information about a peer's endpoint changes", Exec: runPeerEndpointChanges,
ShortHelp: "Prints debug information about a peer's endpoint changes",
}, },
{ {
Name: "dial-types", Name: "dial-types",
Exec: runDebugDialTypes, ShortUsage: "tailscale debug dial-types <hostname-or-IP> <port>",
ShortHelp: "prints debug information about connecting to a given host or IP", Exec: runDebugDialTypes,
ShortHelp: "Prints debug information about connecting to a given host or IP",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("dial-types") fs := newFlagSet("dial-types")
fs.StringVar(&debugDialTypesArgs.network, "network", "tcp", `network type to dial ("tcp", "udp", etc.)`) fs.StringVar(&debugDialTypesArgs.network, "network", "tcp", `network type to dial ("tcp", "udp", etc.)`)
@ -867,7 +899,7 @@ func runDebugDERP(ctx context.Context, args []string) error {
func runSetExpire(ctx context.Context, args []string) error { func runSetExpire(ctx context.Context, args []string) error {
if len(args) != 0 || setExpireArgs.in == 0 { if len(args) != 0 || setExpireArgs.in == 0 {
return errors.New("usage --in=<duration>") return errors.New("usage: tailscale debug set-expire --in=<duration>")
} }
return localClient.DebugSetExpireIn(ctx, setExpireArgs.in) return localClient.DebugSetExpireIn(ctx, setExpireArgs.in)
} }
@ -966,7 +998,7 @@ func runPeerEndpointChanges(ctx context.Context, args []string) error {
} }
if len(args) != 1 || args[0] == "" { if len(args) != 1 || args[0] == "" {
return errors.New("usage: peer-status <hostname-or-IP>") return errors.New("usage: tailscale debug peer-endpoint-changes <hostname-or-IP>")
} }
var ip string var ip string
@ -1042,7 +1074,7 @@ func runDebugDialTypes(ctx context.Context, args []string) error {
} }
if len(args) != 2 || args[0] == "" || args[1] == "" { if len(args) != 2 || args[0] == "" || args[1] == "" {
return errors.New("usage: dial-types <hostname-or-IP> <port>") return errors.New("usage: tailscale debug dial-types <hostname-or-IP> <port>")
} }
port, err := strconv.ParseUint(args[1], 10, 16) port, err := strconv.ParseUint(args[1], 10, 16)

View File

@ -14,7 +14,7 @@
var downCmd = &ffcli.Command{ var downCmd = &ffcli.Command{
Name: "down", Name: "down",
ShortUsage: "down", ShortUsage: "tailscale down",
ShortHelp: "Disconnect from Tailscale", ShortHelp: "Disconnect from Tailscale",
Exec: runDown, Exec: runDown,

View File

@ -14,10 +14,10 @@
) )
const ( const (
driveShareUsage = "drive share <name> <path>" driveShareUsage = "tailscale drive share <name> <path>"
driveRenameUsage = "drive rename <oldname> <newname>" driveRenameUsage = "tailscale drive rename <oldname> <newname>"
driveUnshareUsage = "drive unshare <name>" driveUnshareUsage = "tailscale drive unshare <name>"
driveListUsage = "drive list" driveListUsage = "tailscale drive list"
) )
var driveCmd = &ffcli.Command{ var driveCmd = &ffcli.Command{
@ -33,28 +33,32 @@
UsageFunc: usageFuncNoDefaultValues, UsageFunc: usageFuncNoDefaultValues,
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
{ {
Name: "share", Name: "share",
Exec: runDriveShare, ShortUsage: driveShareUsage,
ShortHelp: "[ALPHA] create or modify a share", Exec: runDriveShare,
UsageFunc: usageFunc, ShortHelp: "[ALPHA] create or modify a share",
UsageFunc: usageFunc,
}, },
{ {
Name: "rename", Name: "rename",
ShortHelp: "[ALPHA] rename a share", ShortUsage: driveRenameUsage,
Exec: runDriveRename, ShortHelp: "[ALPHA] rename a share",
UsageFunc: usageFunc, Exec: runDriveRename,
UsageFunc: usageFunc,
}, },
{ {
Name: "unshare", Name: "unshare",
ShortHelp: "[ALPHA] remove a share", ShortUsage: driveUnshareUsage,
Exec: runDriveUnshare, ShortHelp: "[ALPHA] remove a share",
UsageFunc: usageFunc, Exec: runDriveUnshare,
UsageFunc: usageFunc,
}, },
{ {
Name: "list", Name: "list",
ShortHelp: "[ALPHA] list current shares", ShortUsage: driveListUsage,
Exec: runDriveList, ShortHelp: "[ALPHA] list current shares",
UsageFunc: usageFunc, Exec: runDriveList,
UsageFunc: usageFunc,
}, },
}, },
Exec: func(context.Context, []string) error { Exec: func(context.Context, []string) error {
@ -237,8 +241,8 @@ func buildShareLongHelp() string {
$ tailscale drive list` $ tailscale drive list`
var shareLongHelpAs = ` const shareLongHelpAs = `
If you want a share to be accessed as a different user, you can use sudo to accomplish this. For example, to create the aforementioned share as "theuser", you could run: If you want a share to be accessed as a different user, you can use sudo to accomplish this. For example, to create the aforementioned share as "theuser", you could run:
$ sudo -u theuser tailscale drive share docs /Users/theuser/Documents` $ sudo -u theuser tailscale drive share docs /Users/theuser/Documents`

View File

@ -23,7 +23,7 @@
func exitNodeCmd() *ffcli.Command { func exitNodeCmd() *ffcli.Command {
return &ffcli.Command{ return &ffcli.Command{
Name: "exit-node", Name: "exit-node",
ShortUsage: "exit-node [flags]", ShortUsage: "tailscale exit-node [flags]",
ShortHelp: "Show machines on your tailnet configured as exit nodes", ShortHelp: "Show machines on your tailnet configured as exit nodes",
LongHelp: "Show machines on your tailnet configured as exit nodes", LongHelp: "Show machines on your tailnet configured as exit nodes",
Exec: func(context.Context, []string) error { Exec: func(context.Context, []string) error {
@ -32,7 +32,7 @@ func exitNodeCmd() *ffcli.Command {
Subcommands: append([]*ffcli.Command{ Subcommands: append([]*ffcli.Command{
{ {
Name: "list", Name: "list",
ShortUsage: "exit-node list [flags]", ShortUsage: "tailscale exit-node list [flags]",
ShortHelp: "Show exit nodes", ShortHelp: "Show exit nodes",
Exec: runExitNodeList, Exec: runExitNodeList,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
@ -48,13 +48,13 @@ func exitNodeCmd() *ffcli.Command {
return []*ffcli.Command{ return []*ffcli.Command{
{ {
Name: "connect", Name: "connect",
ShortUsage: "exit-node connect", ShortUsage: "tailscale exit-node connect",
ShortHelp: "connect to most recently used exit node", ShortHelp: "connect to most recently used exit node",
Exec: exitNodeSetUse(true), Exec: exitNodeSetUse(true),
}, },
{ {
Name: "disconnect", Name: "disconnect",
ShortUsage: "exit-node disconnect", ShortUsage: "tailscale exit-node disconnect",
ShortHelp: "disconnect from current exit node, if any", ShortHelp: "disconnect from current exit node, if any",
Exec: exitNodeSetUse(false), Exec: exitNodeSetUse(false),
}, },

View File

@ -38,7 +38,7 @@
var fileCmd = &ffcli.Command{ var fileCmd = &ffcli.Command{
Name: "file", Name: "file",
ShortUsage: "file <cp|get> ...", ShortUsage: "tailscale file <cp|get> ...",
ShortHelp: "Send or receive files", ShortHelp: "Send or receive files",
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
fileCpCmd, fileCpCmd,
@ -65,7 +65,7 @@ func (c *countingReader) Read(buf []byte) (int, error) {
var fileCpCmd = &ffcli.Command{ var fileCpCmd = &ffcli.Command{
Name: "cp", Name: "cp",
ShortUsage: "file cp <files...> <target>:", ShortUsage: "tailscale file cp <files...> <target>:",
ShortHelp: "Copy file(s) to a host", ShortHelp: "Copy file(s) to a host",
Exec: runCp, Exec: runCp,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
@ -412,7 +412,7 @@ func (v *onConflict) Set(s string) error {
var fileGetCmd = &ffcli.Command{ var fileGetCmd = &ffcli.Command{
Name: "get", Name: "get",
ShortUsage: "file get [--wait] [--verbose] [--conflict=(skip|overwrite|rename)] <target-directory>", ShortUsage: "tailscale file get [--wait] [--verbose] [--conflict=(skip|overwrite|rename)] <target-directory>",
ShortHelp: "Move files out of the Tailscale file inbox", ShortHelp: "Move files out of the Tailscale file inbox",
Exec: runFileGet, Exec: runFileGet,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
@ -420,7 +420,7 @@ func (v *onConflict) Set(s string) error {
fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty") fs.BoolVar(&getArgs.wait, "wait", false, "wait for a file to arrive if inbox is empty")
fs.BoolVar(&getArgs.loop, "loop", false, "run get in a loop, receiving files as they come in") fs.BoolVar(&getArgs.loop, "loop", false, "run get in a loop, receiving files as they come in")
fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output") fs.BoolVar(&getArgs.verbose, "verbose", false, "verbose output")
fs.Var(&getArgs.conflict, "conflict", `behavior when a conflicting (same-named) file already exists in the target directory. fs.Var(&getArgs.conflict, "conflict", "`behavior`"+` when a conflicting (same-named) file already exists in the target directory.
skip: skip conflicting files: leave them in the taildrop inbox and print an error. get any non-conflicting files skip: skip conflicting files: leave them in the taildrop inbox and print an error. get any non-conflicting files
overwrite: overwrite existing file overwrite: overwrite existing file
rename: write to a new number-suffixed filename`) rename: write to a new number-suffixed filename`)

View File

@ -36,9 +36,9 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
Name: "funnel", Name: "funnel",
ShortHelp: "Turn on/off Funnel service", ShortHelp: "Turn on/off Funnel service",
ShortUsage: strings.Join([]string{ ShortUsage: strings.Join([]string{
"funnel <serve-port> {on|off}", "tailscale funnel <serve-port> {on|off}",
"funnel status [--json]", "tailscale funnel status [--json]",
}, "\n "), }, "\n"),
LongHelp: strings.Join([]string{ LongHelp: strings.Join([]string{
"Funnel allows you to publish a 'tailscale serve'", "Funnel allows you to publish a 'tailscale serve'",
"server publicly, open to the entire internet.", "server publicly, open to the entire internet.",
@ -46,17 +46,16 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
"Turning off Funnel only turns off serving to the internet.", "Turning off Funnel only turns off serving to the internet.",
"It does not affect serving to your tailnet.", "It does not affect serving to your tailnet.",
}, "\n"), }, "\n"),
Exec: e.runFunnel, Exec: e.runFunnel,
UsageFunc: usageFunc,
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
{ {
Name: "status", Name: "status",
Exec: e.runServeStatus, Exec: e.runServeStatus,
ShortHelp: "show current serve/funnel status", ShortUsage: "tailscale funnel status [--json]",
ShortHelp: "Show current serve/funnel status",
FlagSet: e.newFlags("funnel-status", func(fs *flag.FlagSet) { FlagSet: e.newFlags("funnel-status", func(fs *flag.FlagSet) {
fs.BoolVar(&e.json, "json", false, "output JSON") fs.BoolVar(&e.json, "json", false, "output JSON")
}), }),
UsageFunc: usageFunc,
}, },
}, },
} }

View File

@ -12,8 +12,8 @@
var idTokenCmd = &ffcli.Command{ var idTokenCmd = &ffcli.Command{
Name: "id-token", Name: "id-token",
ShortUsage: "id-token <aud>", ShortUsage: "tailscale id-token <aud>",
ShortHelp: "fetch an OIDC id-token for the Tailscale machine", ShortHelp: "Fetch an OIDC id-token for the Tailscale machine",
Exec: runIDToken, Exec: runIDToken,
} }

View File

@ -16,7 +16,7 @@
var ipCmd = &ffcli.Command{ var ipCmd = &ffcli.Command{
Name: "ip", Name: "ip",
ShortUsage: "ip [-1] [-4] [-6] [peer hostname or ip address]", ShortUsage: "tailscale ip [-1] [-4] [-6] [peer hostname or ip address]",
ShortHelp: "Show Tailscale IP addresses", ShortHelp: "Show Tailscale IP addresses",
LongHelp: "Show Tailscale IP addresses for peer. Peer defaults to the current machine.", LongHelp: "Show Tailscale IP addresses for peer. Peer defaults to the current machine.",
Exec: runIP, Exec: runIP,

View File

@ -12,7 +12,7 @@
var licensesCmd = &ffcli.Command{ var licensesCmd = &ffcli.Command{
Name: "licenses", Name: "licenses",
ShortUsage: "licenses", ShortUsage: "tailscale licenses",
ShortHelp: "Get open source license information", ShortHelp: "Get open source license information",
LongHelp: "Get open source license information", LongHelp: "Get open source license information",
Exec: runLicenses, Exec: runLicenses,

View File

@ -14,11 +14,10 @@
var loginCmd = &ffcli.Command{ var loginCmd = &ffcli.Command{
Name: "login", Name: "login",
ShortUsage: "login [flags]", ShortUsage: "tailscale login [flags]",
ShortHelp: "Log in to a Tailscale account", ShortHelp: "Log in to a Tailscale account",
LongHelp: `"tailscale login" logs this machine in to your Tailscale network. LongHelp: `"tailscale login" logs this machine in to your Tailscale network.
This command is currently in alpha and may change in the future.`, This command is currently in alpha and may change in the future.`,
UsageFunc: usageFunc,
FlagSet: func() *flag.FlagSet { FlagSet: func() *flag.FlagSet {
return newUpFlagSet(effectiveGOOS(), &loginArgs, "login") return newUpFlagSet(effectiveGOOS(), &loginArgs, "login")
}(), }(),

View File

@ -13,7 +13,7 @@
var logoutCmd = &ffcli.Command{ var logoutCmd = &ffcli.Command{
Name: "logout", Name: "logout",
ShortUsage: "logout [flags]", ShortUsage: "tailscale logout",
ShortHelp: "Disconnect from Tailscale and expire current node key", ShortHelp: "Disconnect from Tailscale and expire current node key",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`

View File

@ -16,7 +16,7 @@
var ncCmd = &ffcli.Command{ var ncCmd = &ffcli.Command{
Name: "nc", Name: "nc",
ShortUsage: "nc <hostname-or-IP> <port>", ShortUsage: "tailscale nc <hostname-or-IP> <port>",
ShortHelp: "Connect to a port on a host, connected to stdin/stdout", ShortHelp: "Connect to a port on a host, connected to stdin/stdout",
Exec: runNC, Exec: runNC,
} }

View File

@ -28,7 +28,7 @@
var netcheckCmd = &ffcli.Command{ var netcheckCmd = &ffcli.Command{
Name: "netcheck", Name: "netcheck",
ShortUsage: "netcheck", ShortUsage: "tailscale netcheck",
ShortHelp: "Print an analysis of local network conditions", ShortHelp: "Print an analysis of local network conditions",
Exec: runNetcheck, Exec: runNetcheck,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {

View File

@ -26,7 +26,7 @@
var netlockCmd = &ffcli.Command{ var netlockCmd = &ffcli.Command{
Name: "lock", Name: "lock",
ShortUsage: "lock <sub-command> <arguments>", ShortUsage: "tailscale lock <sub-command> <arguments>",
ShortHelp: "Manage tailnet lock", ShortHelp: "Manage tailnet lock",
LongHelp: "Manage tailnet lock", LongHelp: "Manage tailnet lock",
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
@ -61,7 +61,7 @@ func runNetworkLockNoSubcommand(ctx context.Context, args []string) error {
var nlInitCmd = &ffcli.Command{ var nlInitCmd = &ffcli.Command{
Name: "init", Name: "init",
ShortUsage: "init [--gen-disablement-for-support] --gen-disablements N <trusted-key>...", ShortUsage: "tailscale lock init [--gen-disablement-for-support] --gen-disablements N <trusted-key>...",
ShortHelp: "Initialize tailnet lock", ShortHelp: "Initialize tailnet lock",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
@ -183,7 +183,7 @@ func runNetworkLockInit(ctx context.Context, args []string) error {
var nlStatusCmd = &ffcli.Command{ var nlStatusCmd = &ffcli.Command{
Name: "status", Name: "status",
ShortUsage: "status", ShortUsage: "tailscale lock status",
ShortHelp: "Outputs the state of tailnet lock", ShortHelp: "Outputs the state of tailnet lock",
LongHelp: "Outputs the state of tailnet lock", LongHelp: "Outputs the state of tailnet lock",
Exec: runNetworkLockStatus, Exec: runNetworkLockStatus,
@ -280,7 +280,7 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
var nlAddCmd = &ffcli.Command{ var nlAddCmd = &ffcli.Command{
Name: "add", Name: "add",
ShortUsage: "add <public-key>...", ShortUsage: "tailscale lock add <public-key>...",
ShortHelp: "Adds one or more trusted signing keys to tailnet lock", ShortHelp: "Adds one or more trusted signing keys to tailnet lock",
LongHelp: "Adds one or more trusted signing keys to tailnet lock", LongHelp: "Adds one or more trusted signing keys to tailnet lock",
Exec: func(ctx context.Context, args []string) error { Exec: func(ctx context.Context, args []string) error {
@ -294,7 +294,7 @@ func runNetworkLockStatus(ctx context.Context, args []string) error {
var nlRemoveCmd = &ffcli.Command{ var nlRemoveCmd = &ffcli.Command{
Name: "remove", Name: "remove",
ShortUsage: "remove [--re-sign=false] <public-key>...", ShortUsage: "tailscale lock remove [--re-sign=false] <public-key>...",
ShortHelp: "Removes one or more trusted signing keys from tailnet lock", ShortHelp: "Removes one or more trusted signing keys from tailnet lock",
LongHelp: "Removes one or more trusted signing keys from tailnet lock", LongHelp: "Removes one or more trusted signing keys from tailnet lock",
Exec: runNetworkLockRemove, Exec: runNetworkLockRemove,
@ -435,7 +435,7 @@ func runNetworkLockModify(ctx context.Context, addArgs, removeArgs []string) err
var nlSignCmd = &ffcli.Command{ var nlSignCmd = &ffcli.Command{
Name: "sign", Name: "sign",
ShortUsage: "sign <node-key> [<rotation-key>] or sign <auth-key>", ShortUsage: "tailscale lock sign <node-key> [<rotation-key>] or sign <auth-key>",
ShortHelp: "Signs a node or pre-approved auth key", ShortHelp: "Signs a node or pre-approved auth key",
LongHelp: `Either: LongHelp: `Either:
- signs a node key and transmits the signature to the coordination server, or - signs a node key and transmits the signature to the coordination server, or
@ -479,7 +479,7 @@ func runNetworkLockSign(ctx context.Context, args []string) error {
var nlDisableCmd = &ffcli.Command{ var nlDisableCmd = &ffcli.Command{
Name: "disable", Name: "disable",
ShortUsage: "disable <disablement-secret>", ShortUsage: "tailscale lock disable <disablement-secret>",
ShortHelp: "Consumes a disablement secret to shut down tailnet lock for the tailnet", ShortHelp: "Consumes a disablement secret to shut down tailnet lock for the tailnet",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
@ -508,7 +508,7 @@ func runNetworkLockDisable(ctx context.Context, args []string) error {
var nlLocalDisableCmd = &ffcli.Command{ var nlLocalDisableCmd = &ffcli.Command{
Name: "local-disable", Name: "local-disable",
ShortUsage: "local-disable", ShortUsage: "tailscale lock local-disable",
ShortHelp: "Disables tailnet lock for this node only", ShortHelp: "Disables tailnet lock for this node only",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
@ -530,7 +530,7 @@ func runNetworkLockLocalDisable(ctx context.Context, args []string) error {
var nlDisablementKDFCmd = &ffcli.Command{ var nlDisablementKDFCmd = &ffcli.Command{
Name: "disablement-kdf", Name: "disablement-kdf",
ShortUsage: "disablement-kdf <hex-encoded-disablement-secret>", ShortUsage: "tailscale lock disablement-kdf <hex-encoded-disablement-secret>",
ShortHelp: "Computes a disablement value from a disablement secret (advanced users only)", ShortHelp: "Computes a disablement value from a disablement secret (advanced users only)",
LongHelp: "Computes a disablement value from a disablement secret (advanced users only)", LongHelp: "Computes a disablement value from a disablement secret (advanced users only)",
Exec: runNetworkLockDisablementKDF, Exec: runNetworkLockDisablementKDF,
@ -555,7 +555,7 @@ func runNetworkLockDisablementKDF(ctx context.Context, args []string) error {
var nlLogCmd = &ffcli.Command{ var nlLogCmd = &ffcli.Command{
Name: "log", Name: "log",
ShortUsage: "log [--limit N]", ShortUsage: "tailscale lock log [--limit N]",
ShortHelp: "List changes applied to tailnet lock", ShortHelp: "List changes applied to tailnet lock",
LongHelp: "List changes applied to tailnet lock", LongHelp: "List changes applied to tailnet lock",
Exec: runNetworkLockLog, Exec: runNetworkLockLog,
@ -719,7 +719,7 @@ func wrapAuthKey(ctx context.Context, keyStr string, status *ipnstate.Status) er
var nlRevokeKeysCmd = &ffcli.Command{ var nlRevokeKeysCmd = &ffcli.Command{
Name: "revoke-keys", Name: "revoke-keys",
ShortUsage: "revoke-keys <tailnet-lock-key>...\n revoke-keys [--cosign] [--finish] <recovery-blob>", ShortUsage: "tailscale lock revoke-keys <tailnet-lock-key>...\n revoke-keys [--cosign] [--finish] <recovery-blob>",
ShortHelp: "Revoke compromised tailnet-lock keys", ShortHelp: "Revoke compromised tailnet-lock keys",
LongHelp: `Retroactively revoke the specified tailnet lock keys (tlpub:abc). LongHelp: `Retroactively revoke the specified tailnet lock keys (tlpub:abc).

View File

@ -23,7 +23,7 @@
var pingCmd = &ffcli.Command{ var pingCmd = &ffcli.Command{
Name: "ping", Name: "ping",
ShortUsage: "ping <hostname-or-IP>", ShortUsage: "tailscale ping <hostname-or-IP>",
ShortHelp: "Ping a host at the Tailscale layer, see how it routed", ShortHelp: "Ping a host at the Tailscale layer, see how it routed",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`

View File

@ -44,13 +44,13 @@ func newServeLegacyCommand(e *serveEnv) *ffcli.Command {
Name: "serve", Name: "serve",
ShortHelp: "Serve content and local servers", ShortHelp: "Serve content and local servers",
ShortUsage: strings.Join([]string{ ShortUsage: strings.Join([]string{
"serve http:<port> <mount-point> <source> [off]", "tailscale serve http:<port> <mount-point> <source> [off]",
"serve https:<port> <mount-point> <source> [off]", "tailscale serve https:<port> <mount-point> <source> [off]",
"serve tcp:<port> tcp://localhost:<local-port> [off]", "tailscale serve tcp:<port> tcp://localhost:<local-port> [off]",
"serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]", "tailscale serve tls-terminated-tcp:<port> tcp://localhost:<local-port> [off]",
"serve status [--json]", "tailscale serve status [--json]",
"serve reset", "tailscale serve reset",
}, "\n "), }, "\n"),
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
*** BETA; all of this is subject to change *** *** BETA; all of this is subject to change ***
@ -91,24 +91,21 @@ func newServeLegacyCommand(e *serveEnv) *ffcli.Command {
local plaintext server on port 80: local plaintext server on port 80:
$ tailscale serve tls-terminated-tcp:443 tcp://localhost:80 $ tailscale serve tls-terminated-tcp:443 tcp://localhost:80
`), `),
Exec: e.runServe, Exec: e.runServe,
UsageFunc: usageFunc,
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
{ {
Name: "status", Name: "status",
Exec: e.runServeStatus, Exec: e.runServeStatus,
ShortHelp: "show current serve/funnel status", ShortHelp: "Show current serve/funnel status",
FlagSet: e.newFlags("serve-status", func(fs *flag.FlagSet) { FlagSet: e.newFlags("serve-status", func(fs *flag.FlagSet) {
fs.BoolVar(&e.json, "json", false, "output JSON") fs.BoolVar(&e.json, "json", false, "output JSON")
}), }),
UsageFunc: usageFunc,
}, },
{ {
Name: "reset", Name: "reset",
Exec: e.runServeReset, Exec: e.runServeReset,
ShortHelp: "reset current serve/funnel config", ShortHelp: "Reset current serve/funnel config",
FlagSet: e.newFlags("serve-reset", nil), FlagSet: e.newFlags("serve-reset", nil),
UsageFunc: usageFunc,
}, },
}, },
} }

View File

@ -110,10 +110,10 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command {
Name: info.Name, Name: info.Name,
ShortHelp: info.ShortHelp, ShortHelp: info.ShortHelp,
ShortUsage: strings.Join([]string{ ShortUsage: strings.Join([]string{
fmt.Sprintf("%s <target>", info.Name), fmt.Sprintf("tailscale %s <target>", info.Name),
fmt.Sprintf("%s status [--json]", info.Name), fmt.Sprintf("tailscale %s status [--json]", info.Name),
fmt.Sprintf("%s reset", info.Name), fmt.Sprintf("tailscale %s reset", info.Name),
}, "\n "), }, "\n"),
LongHelp: info.LongHelp + fmt.Sprintf(strings.TrimSpace(serveHelpCommon), info.Name), LongHelp: info.LongHelp + fmt.Sprintf(strings.TrimSpace(serveHelpCommon), info.Name),
Exec: e.runServeCombined(subcmd), Exec: e.runServeCombined(subcmd),
@ -131,20 +131,20 @@ func newServeV2Command(e *serveEnv, subcmd serveMode) *ffcli.Command {
UsageFunc: usageFuncNoDefaultValues, UsageFunc: usageFuncNoDefaultValues,
Subcommands: []*ffcli.Command{ Subcommands: []*ffcli.Command{
{ {
Name: "status", Name: "status",
Exec: e.runServeStatus, ShortUsage: "tailscale " + info.Name + " status [--json]",
ShortHelp: "view current proxy configuration", Exec: e.runServeStatus,
ShortHelp: "View current " + info.Name + " configuration",
FlagSet: e.newFlags("serve-status", func(fs *flag.FlagSet) { FlagSet: e.newFlags("serve-status", func(fs *flag.FlagSet) {
fs.BoolVar(&e.json, "json", false, "output JSON") fs.BoolVar(&e.json, "json", false, "output JSON")
}), }),
UsageFunc: usageFunc,
}, },
{ {
Name: "reset", Name: "reset",
ShortHelp: "reset current serve/funnel config", ShortUsage: "tailscale " + info.Name + " reset",
Exec: e.runServeReset, ShortHelp: "Reset current " + info.Name + " config",
FlagSet: e.newFlags("serve-reset", nil), Exec: e.runServeReset,
UsageFunc: usageFunc, FlagSet: e.newFlags("serve-reset", nil),
}, },
}, },
} }

View File

@ -25,7 +25,7 @@
var setCmd = &ffcli.Command{ var setCmd = &ffcli.Command{
Name: "set", Name: "set",
ShortUsage: "set [flags]", ShortUsage: "tailscale set [flags]",
ShortHelp: "Change specified preferences", ShortHelp: "Change specified preferences",
LongHelp: `"tailscale set" allows changing specific preferences. LongHelp: `"tailscale set" allows changing specific preferences.

View File

@ -26,7 +26,7 @@
var sshCmd = &ffcli.Command{ var sshCmd = &ffcli.Command{
Name: "ssh", Name: "ssh",
ShortUsage: "ssh [user@]<host> [args...]", ShortUsage: "tailscale ssh [user@]<host> [args...]",
ShortHelp: "SSH to a Tailscale machine", ShortHelp: "SSH to a Tailscale machine",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`

View File

@ -29,7 +29,7 @@
var statusCmd = &ffcli.Command{ var statusCmd = &ffcli.Command{
Name: "status", Name: "status",
ShortUsage: "status [--active] [--web] [--json]", ShortUsage: "tailscale status [--active] [--web] [--json]",
ShortHelp: "Show state of tailscaled and its connections", ShortHelp: "Show state of tailscaled and its connections",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`

View File

@ -17,26 +17,22 @@
) )
var switchCmd = &ffcli.Command{ var switchCmd = &ffcli.Command{
Name: "switch", Name: "switch",
ShortHelp: "Switches to a different Tailscale account", ShortUsage: "tailscale switch <id>",
ShortHelp: "Switches to a different Tailscale account",
LongHelp: `"tailscale switch" switches between logged in accounts. You can
use the ID that's returned from 'tailnet switch -list'
to pick which profile you want to switch to. Alternatively, you
can use the Tailnet or the account names to switch as well.
This command is currently in alpha and may change in the future.`,
FlagSet: func() *flag.FlagSet { FlagSet: func() *flag.FlagSet {
fs := flag.NewFlagSet("switch", flag.ExitOnError) fs := flag.NewFlagSet("switch", flag.ExitOnError)
fs.BoolVar(&switchArgs.list, "list", false, "list available accounts") fs.BoolVar(&switchArgs.list, "list", false, "list available accounts")
return fs return fs
}(), }(),
Exec: switchProfile, Exec: switchProfile,
UsageFunc: func(*ffcli.Command) string {
return `USAGE
switch <id>
switch --list
"tailscale switch" switches between logged in accounts. You can
use the ID that's returned from 'tailnet switch -list'
to pick which profile you want to switch to. Alternatively, you
can use the Tailnet or the account names to switch as well.
This command is currently in alpha and may change in the future.`
},
} }
var switchArgs struct { var switchArgs struct {

View File

@ -44,7 +44,7 @@
var upCmd = &ffcli.Command{ var upCmd = &ffcli.Command{
Name: "up", Name: "up",
ShortUsage: "up [flags]", ShortUsage: "tailscale up [flags]",
ShortHelp: "Connect to Tailscale, logging in if needed", ShortHelp: "Connect to Tailscale, logging in if needed",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`

View File

@ -19,7 +19,7 @@
var updateCmd = &ffcli.Command{ var updateCmd = &ffcli.Command{
Name: "update", Name: "update",
ShortUsage: "update", ShortUsage: "tailscale update",
ShortHelp: "Update Tailscale to the latest/different version", ShortHelp: "Update Tailscale to the latest/different version",
Exec: runUpdate, Exec: runUpdate,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {

View File

@ -17,7 +17,7 @@
var versionCmd = &ffcli.Command{ var versionCmd = &ffcli.Command{
Name: "version", Name: "version",
ShortUsage: "version [flags]", ShortUsage: "tailscale version [flags]",
ShortHelp: "Print Tailscale version", ShortHelp: "Print Tailscale version",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("version") fs := newFlagSet("version")

View File

@ -26,7 +26,7 @@
var webCmd = &ffcli.Command{ var webCmd = &ffcli.Command{
Name: "web", Name: "web",
ShortUsage: "web [flags]", ShortUsage: "tailscale web [flags]",
ShortHelp: "Run a web server for controlling Tailscale", ShortHelp: "Run a web server for controlling Tailscale",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`

View File

@ -17,13 +17,12 @@
var whoisCmd = &ffcli.Command{ var whoisCmd = &ffcli.Command{
Name: "whois", Name: "whois",
ShortUsage: "whois [--json] ip[:port]", ShortUsage: "tailscale whois [--json] ip[:port]",
ShortHelp: "Show the machine and user associated with a Tailscale IP (v4 or v6)", ShortHelp: "Show the machine and user associated with a Tailscale IP (v4 or v6)",
LongHelp: strings.TrimSpace(` LongHelp: strings.TrimSpace(`
'tailscale whois' shows the machine and user associated with a Tailscale IP (v4 or v6). 'tailscale whois' shows the machine and user associated with a Tailscale IP (v4 or v6).
`), `),
UsageFunc: usageFunc, Exec: runWhoIs,
Exec: runWhoIs,
FlagSet: func() *flag.FlagSet { FlagSet: func() *flag.FlagSet {
fs := newFlagSet("whois") fs := newFlagSet("whois")
fs.BoolVar(&whoIsArgs.json, "json", false, "output in JSON format") fs.BoolVar(&whoIsArgs.json, "json", false, "output in JSON format")