cmd/tailscale/cli: remove special-casing of JS/WASM

We're no longer compiling the entire CLI for WASM, so we can remove
some wrapper functions and GOOS checks.

Effectively reverts 75de4e9cc2.
5df7ac70d6, eda647cb47
and other changes.

Fixes #5146

Signed-off-by: Mihai Parparita <mihai@tailscale.com>
This commit is contained in:
Mihai Parparita 2022-08-02 11:26:27 -07:00
parent 5d0e3d379c
commit db9962d63b
18 changed files with 93 additions and 140 deletions

View File

@ -7,6 +7,7 @@
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
) )
@ -31,6 +32,6 @@ func runBugReport(ctx context.Context, args []string) error {
if err != nil { if err != nil {
return err return err
} }
outln(logMarker) fmt.Println(logMarker)
return nil return nil
} }

View File

@ -27,7 +27,7 @@
ShortHelp: "get TLS certs", ShortHelp: "get TLS certs",
ShortUsage: "cert [flags] <domain>", ShortUsage: "cert [flags] <domain>",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("cert") fs := flag.NewFlagSet("cert", flag.ExitOnError)
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")
fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset") fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk") fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk")
@ -79,7 +79,7 @@ func runCert(ctx context.Context, args []string) error {
domain := args[0] domain := args[0]
printf := func(format string, a ...any) { printf := func(format string, a ...any) {
printf(format, a...) fmt.Printf(format, a...)
} }
if certArgs.certFile == "-" || certArgs.keyFile == "-" { if certArgs.certFile == "-" || certArgs.keyFile == "-" {
printf = log.Printf printf = log.Printf
@ -138,7 +138,7 @@ func runCert(ctx context.Context, args []string) error {
func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed bool, err error) { func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed bool, err error) {
if filename == "-" { if filename == "-" {
Stdout.Write(contents) os.Stdout.Write(contents)
return false, nil return false, nil
} }
if old, err := os.ReadFile(filename); err == nil && bytes.Equal(contents, old) { if old, err := os.ReadFile(filename); err == nil && bytes.Equal(contents, old) {

View File

@ -33,22 +33,6 @@
"tailscale.com/version/distro" "tailscale.com/version/distro"
) )
var Stderr io.Writer = os.Stderr
var Stdout io.Writer = os.Stdout
func printf(format string, a ...any) {
fmt.Fprintf(Stdout, format, a...)
}
// outln is like fmt.Println in the common case, except when Stdout is
// changed (as in js/wasm).
//
// It's not named println because that looks like the Go built-in
// which goes to stderr and formats slightly differently.
func outln(a ...any) {
fmt.Fprintln(Stdout, a...)
}
// ActLikeCLI reports whether a GUI application should act like the // ActLikeCLI reports whether a GUI application should act like the
// CLI based on os.Args, GOOS, the context the process is running in // CLI based on os.Args, GOOS, the context the process is running in
// (pty, parent PID), etc. // (pty, parent PID), etc.
@ -95,16 +79,6 @@ func ActLikeCLI() bool {
return false return false
} }
func newFlagSet(name string) *flag.FlagSet {
onError := flag.ExitOnError
if runtime.GOOS == "js" {
onError = flag.ContinueOnError
}
fs := flag.NewFlagSet(name, onError)
fs.SetOutput(Stderr)
return fs
}
// CleanUpArgs rewrites command line arguments for simplicity and backwards compatibility. // CleanUpArgs rewrites command line arguments for simplicity and backwards compatibility.
// In particular, it rewrites --authkey to --auth-key. // In particular, it rewrites --authkey to --auth-key.
func CleanUpArgs(args []string) []string { func CleanUpArgs(args []string) []string {
@ -138,11 +112,11 @@ func Run(args []string) (err error) {
var warnOnce sync.Once var warnOnce sync.Once
tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) { tailscale.SetVersionMismatchHandler(func(clientVer, serverVer string) {
warnOnce.Do(func() { warnOnce.Do(func() {
fmt.Fprintf(Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer) fmt.Fprintf(os.Stderr, "Warning: client version %q != tailscaled server version %q\n", clientVer, serverVer)
}) })
}) })
rootfs := newFlagSet("tailscale") rootfs := flag.NewFlagSet("tailscale", flag.ExitOnError)
rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled's unix socket") rootfs.StringVar(&rootArgs.socket, "socket", paths.DefaultTailscaledSocket(), "path to tailscaled's unix socket")
rootCmd := &ffcli.Command{ rootCmd := &ffcli.Command{

View File

@ -31,7 +31,7 @@
See: https://tailscale.com/kb/1152/synology-outbound/ See: https://tailscale.com/kb/1152/synology-outbound/
`), `),
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("configure-host") fs := flag.NewFlagSet("configure-host", flag.ExitOnError)
return fs return fs
})(), })(),
} }

View File

@ -40,7 +40,7 @@
Exec: runDebug, Exec: runDebug,
LongHelp: `"tailscale debug" contains misc debug facilities; it is not a stable interface.`, LongHelp: `"tailscale debug" contains misc debug facilities; it is not a stable interface.`,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("debug") fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME") fs.StringVar(&debugArgs.file, "file", "", "get, delete:NAME, or NAME")
fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-sec seconds and write it to this file; - for stdout") fs.StringVar(&debugArgs.cpuFile, "cpu-profile", "", "if non-empty, grab a CPU profile for --profile-sec seconds and write it to this file; - for stdout")
fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout") fs.StringVar(&debugArgs.memFile, "mem-profile", "", "if non-empty, grab a memory profile and write it to this file; - for stdout")
@ -63,7 +63,7 @@
Exec: runDaemonMetrics, Exec: runDaemonMetrics,
ShortHelp: "print tailscaled's metrics", ShortHelp: "print tailscaled's metrics",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("metrics") fs := flag.NewFlagSet("metrics", flag.ExitOnError)
fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values") fs.BoolVar(&metricsArgs.watch, "watch", false, "print JSON dump of delta values")
return fs return fs
})(), })(),
@ -103,7 +103,7 @@
Exec: runPrefs, Exec: runPrefs,
ShortHelp: "print prefs", ShortHelp: "print prefs",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("prefs") fs := flag.NewFlagSet("prefs", flag.ExitOnError)
fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output") fs.BoolVar(&prefsArgs.pretty, "pretty", false, "If true, pretty-print output")
return fs return fs
})(), })(),
@ -113,7 +113,7 @@
Exec: runWatchIPN, Exec: runWatchIPN,
ShortHelp: "subscribe to IPN message bus", ShortHelp: "subscribe to IPN message bus",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("watch-ipn") fs := flag.NewFlagSet("watch-ipn", flag.ExitOnError)
fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages") fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages")
return fs return fs
})(), })(),
@ -128,7 +128,7 @@
Exec: runTS2021, Exec: runTS2021,
ShortHelp: "debug ts2021 protocol connectivity", ShortHelp: "debug ts2021 protocol connectivity",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ts2021") fs := flag.NewFlagSet("ts2021", flag.ExitOnError)
fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane") fs.StringVar(&ts2021Args.host, "host", "controlplane.tailscale.com", "hostname of control plane")
fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version") fs.IntVar(&ts2021Args.version, "version", int(tailcfg.CurrentCapabilityVersion), "protocol version")
return fs return fs
@ -146,7 +146,7 @@
func writeProfile(dst string, v []byte) error { func writeProfile(dst string, v []byte) error {
if dst == "-" { if dst == "-" {
_, err := Stdout.Write(v) _, err := os.Stdout.Write(v)
return err return err
} }
return os.WriteFile(dst, v, 0600) return os.WriteFile(dst, v, 0600)
@ -198,7 +198,7 @@ func runDebug(ctx context.Context, args []string) error {
if err != nil { if err != nil {
fatalf("%v\n", err) fatalf("%v\n", err)
} }
e := json.NewEncoder(Stdout) e := json.NewEncoder(os.Stdout)
e.SetIndent("", "\t") e.SetIndent("", "\t")
e.Encode(wfs) e.Encode(wfs)
return nil return nil
@ -212,7 +212,7 @@ func runDebug(ctx context.Context, args []string) error {
return err return err
} }
log.Printf("Size: %v\n", size) log.Printf("Size: %v\n", size)
io.Copy(Stdout, rc) io.Copy(os.Stdout, rc)
return nil return nil
} }
if usedFlag { if usedFlag {
@ -226,14 +226,14 @@ func runDebug(ctx context.Context, args []string) error {
func runLocalCreds(ctx context.Context, args []string) error { func runLocalCreds(ctx context.Context, args []string) error {
port, token, err := safesocket.LocalTCPPortAndToken() port, token, err := safesocket.LocalTCPPortAndToken()
if err == nil { if err == nil {
printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port) fmt.Printf("curl -u:%s http://localhost:%d/localapi/v0/status\n", token, port)
return nil return nil
} }
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort) fmt.Printf("curl http://localhost:%v/localapi/v0/status\n", safesocket.WindowsLocalPort)
return nil return nil
} }
printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket()) fmt.Printf("curl --unix-socket %s http://foo/localapi/v0/status\n", paths.DefaultTailscaledSocket())
return nil return nil
} }
@ -247,10 +247,10 @@ func runPrefs(ctx context.Context, args []string) error {
return err return err
} }
if prefsArgs.pretty { if prefsArgs.pretty {
outln(prefs.Pretty()) fmt.Println(prefs.Pretty())
} else { } else {
j, _ := json.MarshalIndent(prefs, "", "\t") j, _ := json.MarshalIndent(prefs, "", "\t")
outln(string(j)) fmt.Println(string(j))
} }
return nil return nil
} }
@ -268,7 +268,7 @@ func runWatchIPN(ctx context.Context, args []string) error {
n.NetMap = nil n.NetMap = nil
} }
j, _ := json.MarshalIndent(n, "", "\t") j, _ := json.MarshalIndent(n, "", "\t")
printf("%s\n", j) fmt.Printf("%s\n", j)
}) })
bc.RequestEngineStatus() bc.RequestEngineStatus()
pump(ctx, bc, c) pump(ctx, bc, c)
@ -282,7 +282,7 @@ func runDERPMap(ctx context.Context, args []string) error {
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err, "failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
) )
} }
enc := json.NewEncoder(Stdout) enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", "\t") enc.SetIndent("", "\t")
enc.Encode(dm) enc.Encode(dm)
return nil return nil
@ -299,7 +299,7 @@ func localAPIAction(action string) func(context.Context, []string) error {
func runEnv(ctx context.Context, args []string) error { func runEnv(ctx context.Context, args []string) error {
for _, e := range os.Environ() { for _, e := range os.Environ() {
outln(e) fmt.Println(e)
} }
return nil return nil
} }
@ -338,7 +338,7 @@ func runDaemonGoroutines(ctx context.Context, args []string) error {
if err != nil { if err != nil {
return err return err
} }
Stdout.Write(goroutines) os.Stdout.Write(goroutines)
return nil return nil
} }
@ -354,7 +354,7 @@ func runDaemonMetrics(ctx context.Context, args []string) error {
return err return err
} }
if !metricsArgs.watch { if !metricsArgs.watch {
Stdout.Write(out) os.Stdout.Write(out)
return nil return nil
} }
bs := bufio.NewScanner(bytes.NewReader(out)) bs := bufio.NewScanner(bytes.NewReader(out))
@ -391,9 +391,9 @@ type change struct {
if len(changes) > 0 { if len(changes) > 0 {
format := fmt.Sprintf("%%-%ds %%+5d => %%v\n", maxNameLen) format := fmt.Sprintf("%%-%ds %%+5d => %%v\n", maxNameLen)
for _, c := range changes { for _, c := range changes {
fmt.Fprintf(Stdout, format, c.name, c.to-c.from, c.to) fmt.Fprintf(os.Stdout, format, c.name, c.to-c.from, c.to)
} }
io.WriteString(Stdout, "\n") io.WriteString(os.Stdout, "\n")
} }
time.Sleep(time.Second) time.Sleep(time.Second)
} }

View File

@ -8,6 +8,7 @@
"context" "context"
"flag" "flag"
"fmt" "fmt"
"os"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/ipn" "tailscale.com/ipn"
@ -23,7 +24,7 @@
} }
func newDownFlagSet() *flag.FlagSet { func newDownFlagSet() *flag.FlagSet {
downf := newFlagSet("down") downf := flag.NewFlagSet("down", flag.ExitOnError)
registerAcceptRiskFlag(downf) registerAcceptRiskFlag(downf)
return downf return downf
} }
@ -44,7 +45,7 @@ func runDown(ctx context.Context, args []string) error {
return fmt.Errorf("error fetching current status: %w", err) return fmt.Errorf("error fetching current status: %w", err)
} }
if st.BackendState == "Stopped" { if st.BackendState == "Stopped" {
fmt.Fprintf(Stderr, "Tailscale was already stopped.\n") fmt.Fprintf(os.Stderr, "Tailscale was already stopped.\n")
return nil return nil
} }
_, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ _, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{

View File

@ -54,7 +54,7 @@
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 {
fs := newFlagSet("cp") fs := flag.NewFlagSet("cp", flag.ExitOnError)
fs.StringVar(&cpArgs.name, "name", "", "alternate filename to use, especially useful when <file> is \"-\" (stdin)") fs.StringVar(&cpArgs.name, "name", "", "alternate filename to use, especially useful when <file> is \"-\" (stdin)")
fs.BoolVar(&cpArgs.verbose, "verbose", false, "verbose output") fs.BoolVar(&cpArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&cpArgs.targets, "targets", false, "list possible file cp targets") fs.BoolVar(&cpArgs.targets, "targets", false, "list possible file cp targets")
@ -100,7 +100,7 @@ func runCp(ctx context.Context, args []string) error {
return fmt.Errorf("can't send to %s: %v", target, err) return fmt.Errorf("can't send to %s: %v", target, err)
} }
if isOffline { if isOffline {
fmt.Fprintf(Stderr, "# warning: %s is offline\n", target) fmt.Fprintf(os.Stderr, "# warning: %s is offline\n", target)
} }
if len(files) > 1 { if len(files) > 1 {
@ -281,7 +281,7 @@ func runCpTargets(ctx context.Context, args []string) error {
if detail != "" { if detail != "" {
detail = "\t" + detail detail = "\t" + detail
} }
printf("%s\t%s%s\n", n.Addresses[0].Addr(), n.ComputedName, detail) fmt.Printf("%s\t%s%s\n", n.Addresses[0].Addr(), n.ComputedName, detail)
} }
return nil return nil
} }
@ -315,7 +315,7 @@ func (v *onConflict) Set(s string) error {
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 {
fs := newFlagSet("get") fs := flag.NewFlagSet("get", flag.ExitOnError)
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")
@ -415,7 +415,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
break break
} }
if getArgs.verbose { if getArgs.verbose {
printf("waiting for file...") fmt.Printf("waiting for file...")
} }
if err := waitForFile(ctx); err != nil { if err := waitForFile(ctx); err != nil {
errs = append(errs, err) errs = append(errs, err)
@ -436,7 +436,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
continue continue
} }
if getArgs.verbose { if getArgs.verbose {
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size) fmt.Printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
} }
if err = localClient.DeleteWaitingFile(ctx, wf.Name); err != nil { if err = localClient.DeleteWaitingFile(ctx, wf.Name); err != nil {
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err)) errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
@ -448,7 +448,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
// persistently stuck files are basically an error // persistently stuck files are basically an error
errs = append(errs, fmt.Errorf("moved %d/%d files", deleted, len(wfs))) errs = append(errs, fmt.Errorf("moved %d/%d files", deleted, len(wfs)))
} else if getArgs.verbose { } else if getArgs.verbose {
printf("moved %d/%d files\n", deleted, len(wfs)) fmt.Printf("moved %d/%d files\n", deleted, len(wfs))
} }
return errs return errs
} }
@ -471,7 +471,7 @@ func runFileGet(ctx context.Context, args []string) error {
for { for {
errs := runFileGetOneBatch(ctx, dir) errs := runFileGetOneBatch(ctx, dir)
for _, err := range errs { for _, err := range errs {
outln(err) fmt.Println(err)
} }
if len(errs) > 0 { if len(errs) > 0 {
// It's possible whatever caused the error(s) (e.g. conflicting target file, // It's possible whatever caused the error(s) (e.g. conflicting target file,
@ -493,7 +493,7 @@ func runFileGet(ctx context.Context, args []string) error {
return nil return nil
} }
for _, err := range errs[:len(errs)-1] { for _, err := range errs[:len(errs)-1] {
outln(err) fmt.Println(err)
} }
return errs[len(errs)-1] return errs[len(errs)-1]
} }

View File

@ -22,7 +22,7 @@
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,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ip") fs := flag.NewFlagSet("ip", flag.ExitOnError)
fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address") fs.BoolVar(&ipArgs.want1, "1", false, "only print one IP address")
fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address") fs.BoolVar(&ipArgs.want4, "4", false, "only print IPv4 address")
fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address") fs.BoolVar(&ipArgs.want6, "6", false, "only print IPv6 address")
@ -85,7 +85,7 @@ func runIP(ctx context.Context, args []string) error {
for _, ip := range ips { for _, ip := range ips {
if ip.Is4() && v4 || ip.Is6() && v6 { if ip.Is4() && v4 || ip.Is6() && v6 {
match = true match = true
outln(ip) fmt.Println(ip)
} }
} }
if !match { if !match {

View File

@ -29,7 +29,7 @@ func runNC(ctx context.Context, args []string) error {
} }
description, ok := isRunningOrStarting(st) description, ok := isRunningOrStarting(st)
if !ok { if !ok {
printf("%s\n", description) fmt.Printf("%s\n", description)
os.Exit(1) os.Exit(1)
} }

View File

@ -13,6 +13,7 @@
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"os"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -32,7 +33,7 @@
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 {
fs := newFlagSet("netcheck") fs := flag.NewFlagSet("netcheck", flag.ExitOnError)
fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`) fs.StringVar(&netcheckArgs.format, "format", "", `output format; empty (for human-readable), "json" or "json-line"`)
fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency") fs.DurationVar(&netcheckArgs.every, "every", 0, "if non-zero, do an incremental report with the given frequency")
fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs") fs.BoolVar(&netcheckArgs.verbose, "verbose", false, "verbose logs")
@ -59,7 +60,7 @@ func runNetcheck(ctx context.Context, args []string) error {
} }
if strings.HasPrefix(netcheckArgs.format, "json") { if strings.HasPrefix(netcheckArgs.format, "json") {
fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface") fmt.Fprintln(os.Stderr, "# Warning: this JSON format is not yet considered a stable interface")
} }
dm, err := localClient.CurrentDERPMap(ctx) dm, err := localClient.CurrentDERPMap(ctx)
@ -111,38 +112,38 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
} }
if j != nil { if j != nil {
j = append(j, '\n') j = append(j, '\n')
Stdout.Write(j) os.Stdout.Write(j)
return nil return nil
} }
printf("\nReport:\n") fmt.Printf("\nReport:\n")
printf("\t* UDP: %v\n", report.UDP) fmt.Printf("\t* UDP: %v\n", report.UDP)
if report.GlobalV4 != "" { if report.GlobalV4 != "" {
printf("\t* IPv4: yes, %v\n", report.GlobalV4) fmt.Printf("\t* IPv4: yes, %v\n", report.GlobalV4)
} else { } else {
printf("\t* IPv4: (no addr found)\n") fmt.Printf("\t* IPv4: (no addr found)\n")
} }
if report.GlobalV6 != "" { if report.GlobalV6 != "" {
printf("\t* IPv6: yes, %v\n", report.GlobalV6) fmt.Printf("\t* IPv6: yes, %v\n", report.GlobalV6)
} else if report.IPv6 { } else if report.IPv6 {
printf("\t* IPv6: (no addr found)\n") fmt.Printf("\t* IPv6: (no addr found)\n")
} else if report.OSHasIPv6 { } else if report.OSHasIPv6 {
printf("\t* IPv6: no, but OS has support\n") fmt.Printf("\t* IPv6: no, but OS has support\n")
} else { } else {
printf("\t* IPv6: no, unavailable in OS\n") fmt.Printf("\t* IPv6: no, unavailable in OS\n")
} }
printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP) fmt.Printf("\t* MappingVariesByDestIP: %v\n", report.MappingVariesByDestIP)
printf("\t* HairPinning: %v\n", report.HairPinning) fmt.Printf("\t* HairPinning: %v\n", report.HairPinning)
printf("\t* PortMapping: %v\n", portMapping(report)) fmt.Printf("\t* PortMapping: %v\n", portMapping(report))
// When DERP latency checking failed, // When DERP latency checking failed,
// magicsock will try to pick the DERP server that // magicsock will try to pick the DERP server that
// most of your other nodes are also using // most of your other nodes are also using
if len(report.RegionLatency) == 0 { if len(report.RegionLatency) == 0 {
printf("\t* Nearest DERP: unknown (no response to latency probes)\n") fmt.Printf("\t* Nearest DERP: unknown (no response to latency probes)\n")
} else { } else {
printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName) fmt.Printf("\t* Nearest DERP: %v\n", dm.Regions[report.PreferredDERP].RegionName)
printf("\t* DERP latency:\n") fmt.Printf("\t* DERP latency:\n")
var rids []int var rids []int
for rid := range dm.Regions { for rid := range dm.Regions {
rids = append(rids, rid) rids = append(rids, rid)
@ -169,7 +170,7 @@ func printReport(dm *tailcfg.DERPMap, report *netcheck.Report) error {
if netcheckArgs.verbose { if netcheckArgs.verbose {
derpNum = fmt.Sprintf("derp%d, ", rid) derpNum = fmt.Sprintf("derp%d, ", rid)
} }
printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName) fmt.Printf("\t\t- %3s: %-7s (%s%s)\n", r.RegionCode, latency, derpNum, r.RegionName)
} }
} }
return nil return nil

View File

@ -46,7 +46,7 @@
`), `),
Exec: runPing, Exec: runPing,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("ping") fs := flag.NewFlagSet("ping", flag.ExitOnError)
fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output") fs.BoolVar(&pingArgs.verbose, "verbose", false, "verbose output")
fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established") fs.BoolVar(&pingArgs.untilDirect, "until-direct", true, "stop once a direct path is established")
fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)") fs.BoolVar(&pingArgs.tsmp, "tsmp", false, "do a TSMP-level ping (through WireGuard, but not either host OS stack)")
@ -88,7 +88,7 @@ func runPing(ctx context.Context, args []string) error {
} }
description, ok := isRunningOrStarting(st) description, ok := isRunningOrStarting(st)
if !ok { if !ok {
printf("%s\n", description) fmt.Printf("%s\n", description)
os.Exit(1) os.Exit(1)
} }
@ -103,7 +103,7 @@ func runPing(ctx context.Context, args []string) error {
return err return err
} }
if self { if self {
printf("%v is local Tailscale IP\n", ip) fmt.Printf("%v is local Tailscale IP\n", ip)
return nil return nil
} }
@ -120,7 +120,7 @@ func runPing(ctx context.Context, args []string) error {
cancel() cancel()
if err != nil { if err != nil {
if errors.Is(err, context.DeadlineExceeded) { if errors.Is(err, context.DeadlineExceeded) {
printf("ping %q timed out\n", ip) fmt.Printf("ping %q timed out\n", ip)
if n == pingArgs.num { if n == pingArgs.num {
if !anyPong { if !anyPong {
return errors.New("no reply") return errors.New("no reply")
@ -133,7 +133,7 @@ func runPing(ctx context.Context, args []string) error {
} }
if pr.Err != "" { if pr.Err != "" {
if pr.IsLocalIP { if pr.IsLocalIP {
outln(pr.Err) fmt.Println(pr.Err)
return nil return nil
} }
return errors.New(pr.Err) return errors.New(pr.Err)
@ -149,7 +149,7 @@ func runPing(ctx context.Context, args []string) error {
via = string(pingType()) via = string(pingType())
} }
if pingArgs.peerAPI { if pingArgs.peerAPI {
printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency) fmt.Printf("hit peerapi of %s (%s) at %s in %s\n", pr.NodeIP, pr.NodeName, pr.PeerAPIURL, latency)
return nil return nil
} }
anyPong = true anyPong = true
@ -157,7 +157,7 @@ func runPing(ctx context.Context, args []string) error {
if pr.PeerAPIPort != 0 { if pr.PeerAPIPort != 0 {
extra = fmt.Sprintf(", %d", pr.PeerAPIPort) extra = fmt.Sprintf(", %d", pr.PeerAPIPort)
} }
printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency) fmt.Printf("pong from %s (%s%s) via %v in %v\n", pr.NodeName, pr.NodeIP, extra, via, latency)
if pingArgs.tsmp || pingArgs.icmp { if pingArgs.tsmp || pingArgs.icmp {
return nil return nil
} }

View File

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !js && !windows //go:build !windows
// +build !js,!windows // +build !windows
package cli package cli

View File

@ -1,17 +0,0 @@
// Copyright (c) 2022 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 (
"errors"
)
func findSSH() (string, error) {
return "", errors.New("Not implemented")
}
func execSSH(ssh string, argv []string) error {
return errors.New("Not implemented")
}

View File

@ -46,7 +46,7 @@
`), `),
Exec: runStatus, Exec: runStatus,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("status") fs := flag.NewFlagSet("status", flag.ExitOnError)
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)") fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status") fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)") fs.BoolVar(&statusArgs.active, "active", false, "filter output to only peers with active sessions (not applicable to web mode)")
@ -92,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error {
if err != nil { if err != nil {
return err return err
} }
printf("%s", j) fmt.Printf("%s", j)
return nil return nil
} }
if statusArgs.web { if statusArgs.web {
@ -101,7 +101,7 @@ func runStatus(ctx context.Context, args []string) error {
return err return err
} }
statusURL := interfaces.HTTPOfListener(ln) statusURL := interfaces.HTTPOfListener(ln)
printf("Serving Tailscale status at %v ...\n", statusURL) fmt.Printf("Serving Tailscale status at %v ...\n", statusURL)
go func() { go func() {
<-ctx.Done() <-ctx.Done()
ln.Close() ln.Close()
@ -131,16 +131,16 @@ func runStatus(ctx context.Context, args []string) error {
// print health check information prior to checking LocalBackend state as // print health check information prior to checking LocalBackend state as
// it may provide an explanation to the user if we choose to exit early // it may provide an explanation to the user if we choose to exit early
if len(st.Health) > 0 { if len(st.Health) > 0 {
printf("# Health check:\n") fmt.Printf("# Health check:\n")
for _, m := range st.Health { for _, m := range st.Health {
printf("# - %s\n", m) fmt.Printf("# - %s\n", m)
} }
outln() fmt.Println()
} }
description, ok := isRunningOrStarting(st) description, ok := isRunningOrStarting(st)
if !ok { if !ok {
outln(description) fmt.Println(description)
os.Exit(1) os.Exit(1)
} }
@ -213,7 +213,7 @@ func runStatus(ctx context.Context, args []string) error {
printPS(ps) printPS(ps)
} }
} }
Stdout.Write(buf.Bytes()) os.Stdout.Write(buf.Bytes())
return nil return nil
} }

View File

@ -85,7 +85,7 @@ func acceptRouteDefault(goos string) bool {
func inTest() bool { return flag.Lookup("test.v") != nil } func inTest() bool { return flag.Lookup("test.v") != nil }
func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet { func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf := newFlagSet("up") upf := flag.NewFlagSet("up", flag.ExitOnError)
upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs") upf.BoolVar(&upArgs.qr, "qr", false, "show QR code for login URLs")
upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)") upf.BoolVar(&upArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
@ -195,7 +195,7 @@ type upOutputJSON struct {
} }
func warnf(format string, args ...any) { func warnf(format string, args ...any) {
printf("Warning: "+format+"\n", args...) fmt.Printf("Warning: "+format+"\n", args...)
} }
var ( var (
@ -538,7 +538,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
if env.upArgs.json { if env.upArgs.json {
printUpDoneJSON(ipn.NeedsMachineAuth, "") printUpDoneJSON(ipn.NeedsMachineAuth, "")
} else { } else {
fmt.Fprintf(Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL()) fmt.Fprintf(os.Stderr, "\nTo authorize your machine, visit (as admin):\n\n\t%s\n\n", prefs.AdminPageURL())
} }
case ipn.Running: case ipn.Running:
// Done full authentication process // Done full authentication process
@ -546,7 +546,7 @@ func runUp(ctx context.Context, args []string) (retErr error) {
printUpDoneJSON(ipn.Running, "") printUpDoneJSON(ipn.Running, "")
} else if printed { } else if printed {
// Only need to print an update if we printed the "please click" message earlier. // Only need to print an update if we printed the "please click" message earlier.
fmt.Fprintf(Stderr, "Success.\n") fmt.Fprintf(os.Stderr, "Success.\n")
} }
select { select {
case running <- true: case running <- true:
@ -575,13 +575,13 @@ func runUp(ctx context.Context, args []string) (retErr error) {
fmt.Println(string(data)) fmt.Println(string(data))
} }
} else { } else {
fmt.Fprintf(Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url) fmt.Fprintf(os.Stderr, "\nTo authenticate, visit:\n\n\t%s\n\n", *url)
if upArgs.qr { if upArgs.qr {
q, err := qrcode.New(*url, qrcode.Medium) q, err := qrcode.New(*url, qrcode.Medium)
if err != nil { if err != nil {
log.Printf("QR code error: %v", err) log.Printf("QR code error: %v", err)
} else { } else {
fmt.Fprintf(Stderr, "%s\n", q.ToString(false)) fmt.Fprintf(os.Stderr, "%s\n", q.ToString(false))
} }
} }
} }
@ -695,12 +695,12 @@ func checkSSHUpWarnings(ctx context.Context) {
return return
} }
if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") { if len(st.Health) == 1 && strings.Contains(st.Health[0], "SSH") {
printf("%s\n", st.Health[0]) fmt.Printf("%s\n", st.Health[0])
return return
} }
printf("# Health check:\n") fmt.Printf("# Health check:\n")
for _, m := range st.Health { for _, m := range st.Health {
printf(" - %s\n", m) fmt.Printf(" - %s\n", m)
} }
} }

View File

@ -18,7 +18,7 @@
ShortUsage: "version [flags]", ShortUsage: "version [flags]",
ShortHelp: "Print Tailscale version", ShortHelp: "Print Tailscale version",
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := newFlagSet("version") fs := flag.NewFlagSet("version", flag.ExitOnError)
fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version") fs.BoolVar(&versionArgs.daemon, "daemon", false, "also print local node's daemon version")
return fs return fs
})(), })(),
@ -34,16 +34,16 @@ func runVersion(ctx context.Context, args []string) error {
return fmt.Errorf("too many non-flag arguments: %q", args) return fmt.Errorf("too many non-flag arguments: %q", args)
} }
if !versionArgs.daemon { if !versionArgs.daemon {
outln(version.String()) fmt.Println(version.String())
return nil return nil
} }
printf("Client: %s\n", version.String()) fmt.Printf("Client: %s\n", version.String())
st, err := localClient.StatusWithoutPeers(ctx) st, err := localClient.StatusWithoutPeers(ctx)
if err != nil { if err != nil {
return err return err
} }
printf("Daemon: %s\n", st.Version) fmt.Printf("Daemon: %s\n", st.Version)
return nil return nil
} }

View File

@ -75,7 +75,7 @@ type tmplData struct {
`), `),
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
webf := newFlagSet("web") webf := flag.NewFlagSet("web", flag.ExitOnError)
webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic") webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script") webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
return webf return webf

View File

@ -274,13 +274,6 @@ func ipnServerOpts() (o ipnserver.Options) {
} }
switch goos { switch goos {
case "js":
// The js/wasm client has no state storage so for now
// treat all interactive logins as ephemeral.
// TODO(bradfitz): if we start using browser LocalStorage
// or something, then rethink this.
o.LoginFlags = controlclient.LoginEphemeral
fallthrough
default: default:
o.SurviveDisconnects = true o.SurviveDisconnects = true
o.AutostartStateKey = ipn.GlobalDaemonStateKey o.AutostartStateKey = ipn.GlobalDaemonStateKey