From 5b52b640944c72c57562287a7f0273bc54702227 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 14 May 2021 08:53:55 -0700 Subject: [PATCH 1/5] tsnet: add Tailscale-as-a-library package Signed-off-by: Brad Fitzpatrick --- tsnet/example/tshello/tshello.go | 43 +++++ tsnet/tsnet.go | 274 +++++++++++++++++++++++++++++++ wgengine/netstack/netstack.go | 14 +- 3 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 tsnet/example/tshello/tshello.go create mode 100644 tsnet/tsnet.go diff --git a/tsnet/example/tshello/tshello.go b/tsnet/example/tshello/tshello.go new file mode 100644 index 000000000..0153ac6ef --- /dev/null +++ b/tsnet/example/tshello/tshello.go @@ -0,0 +1,43 @@ +// Copyright (c) 2021 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. + +// The tshello server demonstrates how to use Tailscale as a library. +package main + +import ( + "fmt" + "html" + "log" + "net/http" + "strings" + + "tailscale.com/tsnet" +) + +func main() { + s := new(tsnet.Server) + ln, err := s.Listen("tcp", ":80") + if err != nil { + log.Fatal(err) + } + log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + who, ok := s.WhoIs(r.RemoteAddr) + if !ok { + http.Error(w, "WhoIs failed", 500) + return + } + fmt.Fprintf(w, "

Hello, world!

\n") + fmt.Fprintf(w, "

You are %s from %s (%s)

", + html.EscapeString(who.UserProfile.LoginName), + html.EscapeString(firstLabel(who.Node.ComputedName)), + r.RemoteAddr) + }))) +} + +func firstLabel(s string) string { + if i := strings.Index(s, "."); i != -1 { + return s[:i] + } + return s +} diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go new file mode 100644 index 000000000..4f8829a29 --- /dev/null +++ b/tsnet/tsnet.go @@ -0,0 +1,274 @@ +// Copyright (c) 2021 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 tsnet provides Tailscale as a library. +// +// It is an experimental work in progress. +package tsnet + +import ( + "errors" + "fmt" + "log" + "net" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "inet.af/netaddr" + "tailscale.com/client/tailscale/apitype" + "tailscale.com/control/controlclient" + "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" + "tailscale.com/smallzstd" + "tailscale.com/types/logger" + "tailscale.com/wgengine" + "tailscale.com/wgengine/monitor" + "tailscale.com/wgengine/netstack" +) + +// Server is an embedded Tailscale server. +// +// Its exported fields may be changed until the first call to Listen. +type Server struct { + // Dir specifies the name of the directory to use for + // state. If empty, a directory is selected automatically + // under os.UserConfigDir (https://golang.org/pkg/os/#UserConfigDir). + // based on the name of the binary. + Dir string + + // Hostname is the hostname to present to the control server. + // If empty, the binary name is used.l + Hostname string + + // Logf, if non-nil, specifies the logger to use. By default, + // log.Printf is used. + Logf logger.Logf + + initOnce sync.Once + initErr error + lb *ipnlocal.LocalBackend + // the state directory + dir string + hostname string + + mu sync.Mutex + listeners map[listenKey]*listener +} + +// WhoIs reports the node and user who owns the node with the given +// address. The addr may be an ip:port (as from an +// http.Request.RemoteAddr) or just an IP address. +func (s *Server) WhoIs(addr string) (w *apitype.WhoIsResponse, ok bool) { + ipp, err := netaddr.ParseIPPort(addr) + if err != nil { + ip, err := netaddr.ParseIP(addr) + if err != nil { + return nil, false + } + ipp.IP = ip + } + n, up, ok := s.lb.WhoIs(ipp) + if !ok { + return nil, false + } + return &apitype.WhoIsResponse{ + Node: n, + UserProfile: &up, + }, true +} + +func (s *Server) doInit() { + if err := s.start(); err != nil { + s.initErr = fmt.Errorf("tsnet: %w", err) + } +} + +func (s *Server) start() error { + if v, _ := strconv.ParseBool(os.Getenv("TAILSCALE_USE_WIP_CODE")); !v { + return errors.New("code disabled without environment variable TAILSCALE_USE_WIP_CODE set true") + } + + exe, err := os.Executable() + if err != nil { + return err + } + prog := strings.TrimSuffix(strings.ToLower(filepath.Base(exe)), ".exe") + + s.hostname = s.Hostname + if s.hostname == "" { + s.hostname = prog + } + + s.dir = s.Dir + if s.dir == "" { + confDir, err := os.UserConfigDir() + if err != nil { + return err + } + s.dir = filepath.Join(confDir, "tslib-"+prog) + if err := os.MkdirAll(s.dir, 0700); err != nil { + return err + } + } + if fi, err := os.Stat(s.dir); err != nil { + return err + } else if !fi.IsDir() { + return fmt.Errorf("%v is not a directory", s.dir) + } + + logf := s.Logf + if logf == nil { + logf = log.Printf + } + + // TODO(bradfitz): start logtail? don't use filch, perhaps? + // only upload plumbed Logf? + + linkMon, err := monitor.New(logf) + if err != nil { + return err + } + + eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ + ListenPort: 0, + LinkMonitor: linkMon, + }) + if err != nil { + return err + } + + tunDev, magicConn, ok := eng.(wgengine.InternalsGetter).GetInternals() + if !ok { + return fmt.Errorf("%T is not a wgengine.InternalsGetter", eng) + } + + ns, err := netstack.Create(logf, tunDev, eng, magicConn, false) + if err != nil { + return fmt.Errorf("netstack.Create: %w", err) + } + ns.ForwardTCPIn = s.forwardTCP + if err := ns.Start(); err != nil { + return fmt.Errorf("failed to start netstack: %w", err) + } + + statePath := filepath.Join(s.dir, "tailscaled.state") + store, err := ipn.NewFileStore(statePath) + if err != nil { + return err + } + logid := "tslib-TODO" + + lb, err := ipnlocal.NewLocalBackend(logf, logid, store, eng) + if err != nil { + return fmt.Errorf("NewLocalBackend: %v", err) + } + s.lb = lb + lb.SetDecompressor(func() (controlclient.Decompressor, error) { + return smallzstd.NewDecoder(nil) + }) + prefs := ipn.NewPrefs() + prefs.Hostname = s.hostname + prefs.WantRunning = true + err = lb.Start(ipn.Options{ + StateKey: ipn.GlobalDaemonStateKey, + UpdatePrefs: prefs, + }) + if err != nil { + return fmt.Errorf("starting backend: %w", err) + } + if os.Getenv("TS_LOGIN") == "1" { + s.lb.StartLoginInteractive() + } + return nil +} + +func (s *Server) forwardTCP(c net.Conn, port uint16) { + s.mu.Lock() + ln, ok := s.listeners[listenKey{"tcp", "", fmt.Sprint(port)}] + s.mu.Unlock() + if !ok { + c.Close() + return + } + t := time.NewTimer(time.Second) + defer t.Stop() + select { + case ln.conn <- c: + case <-t.C: + c.Close() + } +} + +func (s *Server) Listen(network, addr string) (net.Listener, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, fmt.Errorf("tsnet: %w", err) + } + + s.initOnce.Do(s.doInit) + if s.initErr != nil { + return nil, s.initErr + } + + key := listenKey{network, host, port} + ln := &listener{ + s: s, + key: key, + addr: addr, + + conn: make(chan net.Conn), + } + s.mu.Lock() + if s.listeners == nil { + s.listeners = map[listenKey]*listener{} + } + if _, ok := s.listeners[key]; ok { + s.mu.Unlock() + return nil, fmt.Errorf("tsnet: listener already open for %s, %s", network, addr) + } + s.listeners[key] = ln + s.mu.Unlock() + return ln, nil +} + +type listenKey struct { + network string + host string + port string +} + +type listener struct { + s *Server + key listenKey + addr string + conn chan net.Conn +} + +func (ln *listener) Accept() (net.Conn, error) { + c, ok := <-ln.conn + if !ok { + return nil, fmt.Errorf("tsnet: %w", net.ErrClosed) + } + return c, nil +} + +func (ln *listener) Addr() net.Addr { return addr{ln} } +func (ln *listener) Close() error { + ln.s.mu.Lock() + defer ln.s.mu.Unlock() + if v, ok := ln.s.listeners[ln.key]; ok && v == ln { + delete(ln.s.listeners, ln.key) + close(ln.conn) + } + return nil +} + +type addr struct{ ln *listener } + +func (a addr) Network() string { return a.ln.key.network } +func (a addr) String() string { return a.ln.addr } diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index a8c9aa6bd..045e0d6d6 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -48,6 +48,12 @@ // and implements wgengine.FakeImpl to act as a userspace network // stack when Tailscale is running in fake mode. type Impl struct { + // ForwardTCPIn, if non-nil, handles forwarding an inbound TCP + // connection. + // TODO(bradfitz): provide mechanism for tsnet to reject a + // port other than accepting it and closing it. + ForwardTCPIn func(c net.Conn, port uint16) + ipstack *stack.Stack linkEP *channel.Endpoint tundev *tstun.Wrapper @@ -441,11 +447,15 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) { r.Complete(true) return } + r.Complete(false) + c := gonet.NewTCPConn(&wq, ep) + if ns.ForwardTCPIn != nil { + ns.ForwardTCPIn(c, reqDetails.LocalPort) + return + } if isTailscaleIP { dialAddr = tcpip.Address(net.ParseIP("127.0.0.1")).To4() } - r.Complete(false) - c := gonet.NewTCPConn(&wq, ep) ns.forwardTCP(c, &wq, dialAddr, reqDetails.LocalPort) } From 01a359cec93318ecbe4c5c59880bcc2048aca961 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Thu, 13 May 2021 13:07:44 -0700 Subject: [PATCH 2/5] scripts: add an install script. The script detects one of the supported OS/version combos, and issues the right install instructions for it. Co-authored-by: Christine Dodrill Signed-off-by: David Anderson --- scripts/installer.sh | 384 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100755 scripts/installer.sh diff --git a/scripts/installer.sh b/scripts/installer.sh new file mode 100755 index 000000000..488690873 --- /dev/null +++ b/scripts/installer.sh @@ -0,0 +1,384 @@ +#!/bin/sh +# Copyright (c) 2021 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. +# +# This script detects the current operating system, and installs +# Tailscale according to that OS's conventions. + +set -eu + +# All the code is wrapped in a main function that gets called at the +# bottom of the file, so that a truncated partial download doesn't end +# up executing half a script. +main() { + # Step 1: detect the current linux distro, version, and packaging system. + # + # We rely on a combination of 'uname' and /etc/os-release to find + # an OS name and version, and from there work out what + # installation method we should be using. + # + # The end result of this step is that the following three + # variables are populated, if detection was successful. + OS="" + VERSION="" + PACKAGETYPE="" + + if [ -f /etc/os-release ]; then + # /etc/os-release populates a number of shell variables. We care about the following: + # - ID: the short name of the OS (e.g. "debian", "freebsd") + # - VERSION_ID: the numeric release version for the OS, if any (e.g. "18.04") + # - VERSION_CODENAME: the codename of the OS release, if any (e.g. "buster") + . /etc/os-release + case "$ID" in + ubuntu) + OS="$ID" + VERSION="$VERSION_ID" + PACKAGETYPE="apt" + ;; + debian) + OS="$ID" + VERSION="$VERSION_CODENAME" + PACKAGETYPE="apt" + ;; + raspbian) + OS="$ID" + VERSION="$VERSION_CODENAME" + PACKAGETYPE="apt" + ;; + centos) + OS="$ID" + VERSION="$VERSION_ID" + PACKAGETYPE="dnf" + if [ "$VERSION" = "7" ]; then + PACKAGETYPE="yum" + fi + ;; + rhel) + OS="$ID" + VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)" + PACKAGETYPE="dnf" + ;; + fedora) + OS="$ID" + VERSION="" + PACKAGETYPE="dnf" + ;; + amzn) + OS="amazon-linux" + VERSION="$VERSION_ID" + PACKAGETYPE="yum" + ;; + opensuse-leap) + OS="opensuse" + VERSION="leap/$VERSION_ID" + PACKAGETYPE="zypper" + ;; + opensuse-tumbleweed) + OS="opensuse" + VERSION="tumbleweed" + PACKAGETYPE="zypper" + ;; + arch) + OS="$ID" + VERSION="" # rolling release + PACKAGETYPE="pacman" + ;; + manjaro) + OS="$ID" + VERSION="" # rolling release + PACKAGETYPE="pacman" + ;; + alpine) + OS="$ID" + VERSION="$VERSION_ID" + PACKAGETYPE="apk" + ;; + nixos) + echo "Please add Tailscale to your NixOS configuration directly:" + echo + echo "services.tailscale.enable = true;" + exit 1 + ;; + void) + OS="$ID" + VERSION="" # rolling release + PACKAGETYPE="xbps" + ;; + gentoo) + OS="$ID" + VERSION="" # rolling release + PACKAGETYPE="emerge" + ;; + freebsd) + OS="$ID" + VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)" + PACKAGETYPE="pkg" + ;; + # TODO: wsl? + # TODO: synology? qnap? + esac + fi + + # If we failed to detect something through os-release, consult + # uname and try to infer things from that. + if [ -z "$OS" ]; then + if type uname >/dev/null 2>&1; then + case "$(uname)" in + FreeBSD) + # FreeBSD before 12.2 doesn't have + # /etc/os-release, so we wouldn't have found it in + # the os-release probing above. + OS="freebsd" + VERSION="$(freebsd-version | cut -f1 -d.)" + PACKAGETYPE="pkg" + ;; + OpenBSD) + OS="openbsd" + VERSION="$(uname -r)" + PACKAGETYPE="" + ;; + Darwin) + OS="macos" + VERSION="$(sw_vers -productVersion | cut -f1-2 -d.)" + PACKAGETYPE="appstore" + ;; + Linux) + OS="other-linux" + VERSION="" + PACKAGETYPE="" + ;; + esac + fi + fi + + # Step 2: having detected an OS we support, is it one of the + # versions we support? + OS_UNSUPPORTED= + case "$OS" in + ubuntu) + if [ "$VERSION" != "16.04" ] && \ + [ "$VERSION" != "18.04" ] && \ + [ "$VERSION" != "19.10" ] && \ + [ "$VERSION" != "20.04" ] && \ + [ "$VERSION" != "20.10" ] && \ + [ "$VERSION" != "21.04" ] + then + OS_UNSUPPORTED=1 + fi + ;; + debian) + if [ "$VERSION" != "stretch" ] && \ + [ "$VERSION" != "buster" ] && \ + [ "$VERSION" != "bullseye" ] && \ + [ "$VERSION" != "sid" ] + then + OS_UNSUPPORTED=1 + fi + ;; + raspbian) + if [ "$VERSION" != "buster" ] + then + OS_UNSUPPORTED=1 + fi + ;; + centos) + if [ "$VERSION" != "7" ] && \ + [ "$VERSION" != "8" ] + then + OS_UNSUPPORTED=1 + fi + ;; + rhel) + if [ "$VERSION" != "8" ] + then + OS_UNSUPPORTED=1 + fi + ;; + amazon-linux) + if [ "$VERSION" != "2" ] + then + OS_UNSUPPORTED=1 + fi + ;; + opensuse) + if [ "$VERSION" != "leap/15.1" ] && \ + [ "$VERSION" != "leap/15.2" ] && \ + [ "$VERSION" != "tumbleweed" ] + then + OS_UNSUPPORTED=1 + fi + ;; + arch) + # Rolling release, no version checking needed. + ;; + manjaro) + # Rolling release, no version checking needed. + ;; + alpine) + # All versions supported, no version checking needed. + # TODO: is that true? When was tailscale packaged? + ;; + void) + # Rolling release, no version checking needed. + ;; + gentoo) + # Rolling release, no version checking needed. + ;; + freebsd) + if [ "$VERSION" != "12" ] && \ + [ "$VERSION" != "13" ] + then + OS_UNSUPPORTED=1 + fi + ;; + openbsd) + OS_UNSUPPORTED=1 + ;; + macos) + # We delegate macOS installation to the app store, it will + # perform version checks for us. + ;; + other-linux) + OS_UNSUPPORTED=1 + ;; + *) + OS_UNSUPPORTED=1 + ;; + esac + if [ "$OS_UNSUPPORTED" = "1" ]; then + case "$OS" in + other-linux) + echo "Couldn't determine what kind of Linux is running." + echo "You could try the static binaries at:" + echo "https://pkgs.tailscale.com/stable/#static" + ;; + "") + echo "Couldn't determine what operating system you're running." + ;; + *) + echo "$OS $VERSION isn't supported by this script yet." + ;; + esac + echo + echo "If you'd like us to support your system better, please email support@tailscale.com" + echo "and tell us what OS you're running." + echo + echo "Please include the following information we gathered from your system:" + echo + echo "OS=$OS" + echo "VERSION=$VERSION" + echo "PACKAGETYPE=$PACKAGETYPE" + if type uname >/dev/null 2>&1; then + echo "UNAME=$(uname -a)" + else + echo "UNAME=" + fi + echo + if [ -f /etc/os-release ]; then + cat /etc/os-release + else + echo "No /etc/os-release" + fi + exit 1 + fi + + # Step 3: work out if we can run privileged commands, and if so, + # how. + CAN_ROOT= + SUDO= + if [ "$(id -u)" = 0 ]; then + CAN_ROOT=1 + SUDO="" + elif type sudo >/dev/null; then + CAN_ROOT=1 + SUDO="sudo" + elif type doas >/dev/null; then + CAN_ROOT=1 + SUDO="doas" + fi + if [ "$CAN_ROOT" != "1" ]; then + echo "This installer needs to run commands as root." + echo "We tried looking for 'sudo' and 'doas', but couldn't find them." + echo "Either re-run this script as root, or set up sudo/doas." + exit 1 + fi + + # Step 3: run the installation. + echo "Installing Tailscale for $OS $VERSION, using method $PACKAGETYPE" + case "$PACKAGETYPE" in + apt) + # TODO: use newfangled per-repo signature scheme + set -x + curl -fsSL "https://pkgs.tailscale.com/stable/$OS/$VERSION.gpg" | $SUDO apt-key add - + curl -fsSL "https://pkgs.tailscale.com/stable/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list + $SUDO apt-get update + $SUDO apt-get install tailscale + set +x + ;; + yum) + set -x + $SUDO yum install yum-utils + $SUDO yum-config-manager --add-repo "https://pkgs.tailscale.com/stable/$OS/$VERSION/tailscale.repo" + $SUDO yum install tailscale + $SUDO systemctl enable --now tailscaled + set +x + ;; + dnf) + set -x + $SUDO dnf config-manager --add-repo "https://pkgs.tailscale.com/stable/$OS/$VERSION/tailscale.repo" + $SUDO dnf install tailscale + $SUDO systemctl enable --now tailscaled + set +x + ;; + zypper) + set -x + $SUDO zypper ar -g -r "https://pkgs.tailscale.com/stable/$OS/$VERSION/tailscale.repo" + $SUDO zypper ref + $SUDO zypper in tailscale + $SUDO systemctl enable --now tailscaled + set +x + ;; + pacman) + set -x + $SUDO pacman -S tailscale + $SUDO systemctl enable --now tailscaled + set +x + ;; + apk) + set -x + $SUDO apk add tailscale + $SUDO rc-update add tailscale + set +x + ;; + xbps) + set -x + $SUDO xbps-install tailscale + set +x + ;; + emerge) + set -x + $SUDO emerge net-vpn/tailscale + set +x + ;; + appstore) + set -x + open "https://apps.apple.com/us/app/tailscale/id1475387142" + set +x + ;; + *) + echo "unexpected: unknown package type $PACKAGETYPE" + exit 1 + ;; + esac + + echo "Installation complete! Log in to start using Tailscale by running:" + echo + if [ -z "$SUDO" ]; then + echo "tailscale up" + else + echo "$SUDO tailscale up" + fi +} + +main From 783f125003766c11e45589c3873fc5ed53b06d48 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 14 May 2021 14:11:35 -0700 Subject: [PATCH 3/5] scripts: use codenames for ubuntu, since that's what our repo uses. Signed-off-by: David Anderson --- scripts/installer.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/installer.sh b/scripts/installer.sh index 488690873..3b1a460d8 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -33,7 +33,7 @@ main() { case "$ID" in ubuntu) OS="$ID" - VERSION="$VERSION_ID" + VERSION="$VERSION_CODENAME" PACKAGETYPE="apt" ;; debian) @@ -157,12 +157,12 @@ main() { OS_UNSUPPORTED= case "$OS" in ubuntu) - if [ "$VERSION" != "16.04" ] && \ - [ "$VERSION" != "18.04" ] && \ - [ "$VERSION" != "19.10" ] && \ - [ "$VERSION" != "20.04" ] && \ - [ "$VERSION" != "20.10" ] && \ - [ "$VERSION" != "21.04" ] + if [ "$VERSION" != "xenial" ] && \ + [ "$VERSION" != "bionic" ] && \ + [ "$VERSION" != "eoan" ] && \ + [ "$VERSION" != "focal" ] && \ + [ "$VERSION" != "groovy" ] && \ + [ "$VERSION" != "hirsute" ] then OS_UNSUPPORTED=1 fi From 0e9ea9f7794748018ef724df2f26645e9c619b4f Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 14 May 2021 14:11:50 -0700 Subject: [PATCH 4/5] scripts: detect curl vs. wget and use the right one. Signed-off-by: David Anderson --- scripts/installer.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/installer.sh b/scripts/installer.sh index 3b1a460d8..0249d0456 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -304,14 +304,27 @@ main() { exit 1 fi + # Step 3: run the installation. echo "Installing Tailscale for $OS $VERSION, using method $PACKAGETYPE" case "$PACKAGETYPE" in apt) + CURL= + if type curl >/dev/null; then + CURL="curl -fsSL" + elif type wget >/dev/null; then + CURL="wget -q -O-" + fi + if [ -z "$CURL" ]; then + echo "The installer needs either curl or wget to download files." + echo "Please install either curl or wget to proceed." + exit 1 + fi + # TODO: use newfangled per-repo signature scheme set -x - curl -fsSL "https://pkgs.tailscale.com/stable/$OS/$VERSION.gpg" | $SUDO apt-key add - - curl -fsSL "https://pkgs.tailscale.com/stable/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list + $CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.gpg" | $SUDO apt-key add - + $CURL "https://pkgs.tailscale.com/stable/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list $SUDO apt-get update $SUDO apt-get install tailscale set +x From 4f92f405eeaf2c6d9dffab74a0e38095213b69b7 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 14 May 2021 14:13:31 -0700 Subject: [PATCH 5/5] scripts: fix up installer script comments. Signed-off-by: David Anderson --- scripts/installer.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/installer.sh b/scripts/installer.sh index 0249d0456..dbeb4aa92 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -305,10 +305,12 @@ main() { fi - # Step 3: run the installation. + # Step 4: run the installation. echo "Installing Tailscale for $OS $VERSION, using method $PACKAGETYPE" case "$PACKAGETYPE" in apt) + # Ideally we want to use curl, but on some installs we + # only have wget. Detect and use what's available. CURL= if type curl >/dev/null; then CURL="curl -fsSL"