Tailscale Installation Script Security Audits

Reading through the Tailscale install script
tailscale
security
bash
code audit
Author

Matt Triano

Published

January 29, 2025

Modified

January 29, 2025

Tailscale Installation Script Security Audits

The corresponding notebook/post includes directions to download and run installation scripts, but I’m always a little wary of running shell scripts I haven’t looked over, even if they’re from a reliable-seeming source. So I’ll scan over install scripts

Tailscale Client Installation on a Ubuntu/Debian Linux Machine

curl -fsSL https://tailscale.com/install.sh | sh
imports
from IPython.display import display, HTML
import requests
Getting the install script
url = "https://tailscale.com/install.sh"
resp = requests.get(url)
resp.raise_for_status()
linux_script_lines = resp.text
lines_in_script = len(linux_script_lines.split('\n'))
print(f"Lines in install script: {lines_in_script}")
Lines in install script: 627

The install script defines and invokes one function, main().

The main() function starts off by defining variables for the invoking-machine’s: * OS, * OS version, * OS package manager type, * package signing key type (for distros using the apt package manager, e.g. Debian/Ubuntu; options: “legacy” or “keyring”), * a boolean flag so that systemctl commands can be run to enable tailscale to start on boot (only necessary on the Kali linux distro), and * a TRACK variable that only accepts values “stable” or “unstable” (it defaults to “stable” if there’s not already a TRACK env-var set, so I assume this is just to make it more ergonomic for devs to install nightly releases).

The first four are assigned empty-string placeholder values.

The install script’s preamble
print("\n".join(linux_script_lines.split("\n")[12:36]))
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=""
    APT_KEY_TYPE="" # Only for apt-based distros
    APT_SYSTEMCTL_START=false # Only needs to be true for Kali
    TRACK="${TRACK:-stable}"

    case "$TRACK" in
        stable|unstable)
            ;;
        *)
            echo "unsupported track $TRACK"
            exit 1
            ;;
    esac

The next block of code checks the invoking-machine for an /etc/os-release file, and if present, it “runs” the file, adding all of the file’s variables to the shell’s present context and updates the above placeholder variables based on the OS indicated by the file’s ID variable.

If no `/etc/os-release is found, this block is skipped.

\[ f_{\text{OS}}(\text{ID}) = \begin{cases} \text{Ubuntu, if ID} \in \{\text{ubuntu, pop, neon, zorin, tuxedo, elementary, galliumos} \} \\ \text{Ubuntu, if ID} = \text{linuxmint } \& \text{ } [\text{UBUNTU\_CODENAME} \neq \text{""} \lor \text{ DEBIAN\_CODENAME} = \text{""}] \\ \text{Ubuntu, if ID} = \text{pika } \& \text{ VERSION\_ID} \lt 4 \\ \text{Debian, if ID} \in \{\text{debian, parrot, mendel, pureos, kaisen, kali, Deepin, deepin, osmc} \} \\ \text{Debian, if ID} = \text{linuxmint } \& \text{ DEBIAN\_CODENAME} \neq \text{""} \\ \text{Debian, if ID} = \text{pika } \& \text{ VERSION\_ID} \geq 4 \\ \text{Raspbian, if ID} \in \{\text{raspbian} \} \\ \text{CentOS, if ID} \in \{\text{centos} \} \\ \text{Oracle, if ID} \in \{\text{ol} \} \\ \text{Red Hat, if ID} \in \{\text{rhel} \} \\ \text{Fedora, if ID} \in \{\text{fedora, rocky, almalinux, nobara, openmandriva, sangoma, risios, cloudlinux, alinux, fedora-asahi-remix} \} \\ \text{Amazon-Linux, if ID} \in \{\text{amzn} \} \\ \text{CentOS, if ID} \in \{\text{xenenterprise} \} \\ \text{OpenSUSE, if ID} \in \{\text{opensuse-leap, sles, opensuse-tumbleweed, sle-micro-rancher} \} \\ \text{Arch, if ID} \in \{\text{arch, archarm, endeavouros, blendos, garuda, archcraft, cachyos} \} \\ \text{Manjaro, if ID} \in \{\text{manjaro, manjaro-arm} \} \\ \text{Alpine, if ID} \in \{\text{alpine, postmarketos} \} \\ \text{Void, if ID} \in \{\text{void} \} \\ \text{Gentoo, if ID} \in \{\text{gentoo} \} \\ \text{FreeBSD, if ID} \in \{\text{freebsd} \} \\ \text{Photon, if ID} \in \{\text{photon} \} \\ \end{cases} \]

If \(\text{ID} = \text{nixos}\), it prints a message directing the user to manually add Tailscale to their NixOS config and terminates the script early.

\[ f_{\text{PACKAGETYPE}}(\text{OS, ID}) = \begin{cases} \text{apt, if OS} \in \{ \text{Ubuntu, Debian} \} \\ \text{dnf, if OS} \in \{ \text{Fedora} \} \\ \text{dnf, if OS} \in \{ \text{CentOS, Oracle, Red Hat} \} \text{ } \& \text{ } [\lfloor \text{VERSION\_ID} \rfloor \neq 7] \text{ } \& \text{ ID} \neq \text{xenenterprise} \\ \text{yum, if OS} \in \{ \text{CentOS, Oracle, Red Hat} \} \text{ } \& \lfloor \text{VERSION\_ID} \rfloor = 7] \\ \text{yum, if OS} \in \{ \text{Amazon-Linux} \} \lor \text{ID} = \text{xenenterprise} \\ \text{yum, if OS} \in \{ \text{OpenSUSE} \} \\ \text{pacman, if OS} \in \{ \text{Arch, Manjaro} \} \\ \text{apk, if OS} \in \{ \text{Alpine} \} \\ \text{xbps, if OS} \in \{ \text{Void} \} \\ \text{emerge, if OS} \in \{ \text{Gentoo} \} \\ \text{pkg, if OS} \in \{ \text{FreeBSD} \} \\ \text{tdnf, if OS} \in \{ \text{Photon} \} \\ \end{cases} \]

It also unpacks VERSION_IDs and sets the APT_KEY_TYPE for Ubuntu/Debian based distros. I’ll use python for representing the APT_KEY_TYPE logic; \(\LaTeX\) is rather cumbersome and not as pretty as I used to believe.

APT_KEY_TYPE = "keyring"

if (
    (ID in ["ubuntu", "pop", "neon", "zorin", "tuxedo"] and not str(VERSION_ID).startswith("2"))
    or (ID in ["debian", "raspbian"] and VERSION_ID < 11)
    or (ID in ["linuxmint", "parrot", "mendel"] and VERSION_ID < 5)
    or (ID in ["elementary"] and VERSION_ID < 6)
    or (ID in ["kali"] and int(str(VERSION_ID)[0:4]) < 2021)
    or (ID in ["Deepin", "deepin"] and VERSION_ID < 20)
    or (ID in ["galliumos"])
):
    APT_KEY_TYPE = "legacy"

Ignoring the wild variety of Linux distros, everything so far seems straightforward.

Showing my /etc/os-release file’s contents
!cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.5 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
The /etc/os-release parsing conditional block
print("\n".join(linux_script_lines.split("\n")[37:304]))
    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")
        #  - UBUNTU_CODENAME: if it exists, use instead of VERSION_CODENAME
        . /etc/os-release
        case "$ID" in
            ubuntu|pop|neon|zorin|tuxedo)
                OS="ubuntu"
                if [ "${UBUNTU_CODENAME:-}" != "" ]; then
                    VERSION="$UBUNTU_CODENAME"
                else
                    VERSION="$VERSION_CODENAME"
                fi
                PACKAGETYPE="apt"
                # Third-party keyrings became the preferred method of
                # installation in Ubuntu 20.04.
                if expr "$VERSION_ID" : "2.*" >/dev/null; then
                    APT_KEY_TYPE="keyring"
                else
                    APT_KEY_TYPE="legacy"
                fi
                ;;
            debian)
                OS="$ID"
                VERSION="$VERSION_CODENAME"
                PACKAGETYPE="apt"
                # Third-party keyrings became the preferred method of
                # installation in Debian 11 (Bullseye).
                if [ -z "${VERSION_ID:-}" ]; then
                    # rolling release. If you haven't kept current, that's on you.
                    APT_KEY_TYPE="keyring"
                # Parrot Security is a special case that uses ID=debian
                elif [ "$NAME" = "Parrot Security" ]; then
                    # All versions new enough to have this behaviour prefer keyring
                    # and their VERSION_ID is not consistent with Debian.
                    APT_KEY_TYPE="keyring"
                    # They don't specify the Debian version they're based off in os-release
                    # but Parrot 6 is based on Debian 12 Bookworm.
                    VERSION=bookworm
                elif [ "$VERSION_ID" -lt 11 ]; then
                    APT_KEY_TYPE="legacy"
                else
                    APT_KEY_TYPE="keyring"
                fi
                ;;
            linuxmint)
                if [ "${UBUNTU_CODENAME:-}" != "" ]; then
                    OS="ubuntu"
                    VERSION="$UBUNTU_CODENAME"
                elif [ "${DEBIAN_CODENAME:-}" != "" ]; then
                    OS="debian"
                    VERSION="$DEBIAN_CODENAME"
                else
                    OS="ubuntu"
                    VERSION="$VERSION_CODENAME"
                fi
                PACKAGETYPE="apt"
                if [ "$VERSION_ID" -lt 5 ]; then
                    APT_KEY_TYPE="legacy"
                else
                    APT_KEY_TYPE="keyring"
                fi
                ;;
            elementary)
                OS="ubuntu"
                VERSION="$UBUNTU_CODENAME"
                PACKAGETYPE="apt"
                if [ "$VERSION_ID" -lt 6 ]; then
                    APT_KEY_TYPE="legacy"
                else
                    APT_KEY_TYPE="keyring"
                fi
                ;;
            parrot|mendel)
                OS="debian"
                PACKAGETYPE="apt"
                if [ "$VERSION_ID" -lt 5 ]; then
                    VERSION="buster"
                    APT_KEY_TYPE="legacy"
                else
                    VERSION="bullseye"
                    APT_KEY_TYPE="keyring"
                fi
                ;;
            galliumos)
                OS="ubuntu"
                PACKAGETYPE="apt"
                VERSION="bionic"
                APT_KEY_TYPE="legacy"
                ;;
            pureos|kaisen)
                OS="debian"
                PACKAGETYPE="apt"
                VERSION="bullseye"
                APT_KEY_TYPE="keyring"
                ;;
            raspbian)
                OS="$ID"
                VERSION="$VERSION_CODENAME"
                PACKAGETYPE="apt"
                # Third-party keyrings became the preferred method of
                # installation in Raspbian 11 (Bullseye).
                if [ "$VERSION_ID" -lt 11 ]; then
                    APT_KEY_TYPE="legacy"
                else
                    APT_KEY_TYPE="keyring"
                fi
                ;;
            kali)
                OS="debian"
                PACKAGETYPE="apt"
                YEAR="$(echo "$VERSION_ID" | cut -f1 -d.)"
                APT_SYSTEMCTL_START=true
                # Third-party keyrings became the preferred method of
                # installation in Debian 11 (Bullseye), which Kali switched
                # to in roughly 2021.x releases
                if [ "$YEAR" -lt 2021 ]; then
                    # Kali VERSION_ID is "kali-rolling", which isn't distinguishing
                    VERSION="buster"
                    APT_KEY_TYPE="legacy"
                else
                    VERSION="bullseye"
                    APT_KEY_TYPE="keyring"
                fi
                ;;
            Deepin|deepin)  # https://github.com/tailscale/tailscale/issues/7862
                OS="debian"
                PACKAGETYPE="apt"
                if [ "$VERSION_ID" -lt 20 ]; then
                    APT_KEY_TYPE="legacy"
                    VERSION="buster"
                else
                    APT_KEY_TYPE="keyring"
                    VERSION="bullseye"
                fi
                ;;
            pika)
                PACKAGETYPE="apt"
                # All versions of PikaOS are new enough to prefer keyring
                APT_KEY_TYPE="keyring"
                # Older versions of PikaOS are based on Ubuntu rather than Debian
                if [ "$VERSION_ID" -lt 4 ]; then
                    OS="ubuntu"
                    VERSION="$UBUNTU_CODENAME"
                else
                    OS="debian"
                    VERSION="$DEBIAN_CODENAME"
                fi
                ;;
            centos)
                OS="$ID"
                VERSION="$VERSION_ID"
                PACKAGETYPE="dnf"
                if [ "$VERSION" = "7" ]; then
                    PACKAGETYPE="yum"
                fi
                ;;
            ol)
                OS="oracle"
                VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)"
                PACKAGETYPE="dnf"
                if [ "$VERSION" = "7" ]; then
                    PACKAGETYPE="yum"
                fi
                ;;
            rhel)
                OS="$ID"
                VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)"
                PACKAGETYPE="dnf"
                if [ "$VERSION" = "7" ]; then
                    PACKAGETYPE="yum"
                fi
                ;;
            fedora)
                OS="$ID"
                VERSION=""
                PACKAGETYPE="dnf"
                ;;
            rocky|almalinux|nobara|openmandriva|sangoma|risios|cloudlinux|alinux|fedora-asahi-remix)
                OS="fedora"
                VERSION=""
                PACKAGETYPE="dnf"
                ;;
            amzn)
                OS="amazon-linux"
                VERSION="$VERSION_ID"
                PACKAGETYPE="yum"
                ;;
            xenenterprise)
                OS="centos"
                VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)"
                PACKAGETYPE="yum"
                ;;
            opensuse-leap|sles)
                OS="opensuse"
                VERSION="leap/$VERSION_ID"
                PACKAGETYPE="zypper"
                ;;
            opensuse-tumbleweed)
                OS="opensuse"
                VERSION="tumbleweed"
                PACKAGETYPE="zypper"
                ;;
            sle-micro-rancher)
                OS="opensuse"
                VERSION="leap/15.4"
                PACKAGETYPE="zypper"
                ;;
            arch|archarm|endeavouros|blendos|garuda|archcraft|cachyos)
                OS="arch"
                VERSION="" # rolling release
                PACKAGETYPE="pacman"
                ;;
            manjaro|manjaro-arm|biglinux)
                OS="manjaro"
                VERSION="" # rolling release
                PACKAGETYPE="pacman"
                ;;
            alpine)
                OS="$ID"
                VERSION="$VERSION_ID"
                PACKAGETYPE="apk"
                ;;
            postmarketos)
                OS="alpine"
                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"
                ;;
            osmc)
                OS="debian"
                PACKAGETYPE="apt"
                VERSION="bullseye"
                APT_KEY_TYPE="keyring"
                ;;
            photon)
                OS="photon"
                VERSION="$(echo "$VERSION_ID" | cut -f1 -d.)"
                PACKAGETYPE="tdnf"
                ;;

            # TODO: wsl?
            # TODO: synology? qnap?
        esac
    fi

If there wasn’t a /etc/os-release file and the conditional block above was skipped, the OS variable will be empty and the shell will enter this block (unless the running context’s environment already set an OS variable).

In the block, it checks if the invoking-shell has a command named uname (ie checking if the invoking-machine is running Linux/Unix or if it’s a Windows machine), and sets the OS, VERSION, and PACKAGETYPE variables based on the string uname returns.

Straightforward.

Showing the output of the uname command
!uname
Linux
Showing the location of the uname program
!type uname
uname is /usr/bin/uname
The conditional block that determines OS when /etc/os-release doesn’t exist
print("\n".join(linux_script_lines.split("\n")[305:336]))
    # 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

Internet access check

This block determines whether curl or wget are available on the invoking-machine, configures the prefix of web-request-making commands (in the CURL variable) based on whether the system has curl or not, and then uses that web-request tool to check a test URL, pkgs.tailscale.com, to make sure that the machine has access to the internet. If the test fails, the output of $($CURL "$TEST_URL" 2>&1) gets saved to the RC variable and an error message is displayed.

The code block that checks if the running machine can access the internet
print("\n".join(linux_script_lines.split("\n")[337:361]))
    # 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"
    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

    TEST_URL="https://pkgs.tailscale.com/"
    RC=0
    TEST_OUT=$($CURL "$TEST_URL" 2>&1) || RC=$?
    if [ $RC != 0 ]; then
        echo "The installer cannot reach $TEST_URL"
        echo "Please make sure that your machine has internet access."
        echo "Test output:"
        echo $TEST_OUT
        exit 1
    fi
Inspecting the TEST_URL site
TEST_URL="https://pkgs.tailscale.com/"
test_url_resp = requests.get(TEST_URL)
test_url_resp.raise_for_status()
display(HTML(test_url_resp.text))

Tailscale™ Packages

This server distributes Tailscale packages for various operating systems.

Looking for the source code? It's on Github.

Notice: Use of this package server is governed by Tailscale's Terms of Service. By using this server to install Tailscale, you acknowledge that you have read and accepted those terms.

Tailscale packages come in different release tracks. Most users should use the "stable" track. Other tracks are available, but may not function as well. Currently available tracks are:

  • Stable: The latest supported and tested version that is considered the most reliable.
  • Release Candidates: The current release candidate version actively being tested in preparation for the stable track.
  • Unstable: The very latest version available for testing. This is updated very frequently and is not recommended for production environments.

Tailscale is a trademark of Tailscale Inc.

OS_UNSUPPORTED check

Then, the script checks to see if the detected OS is supported, saving that in the variable OS_UNSUPPORTED, which is initialized to be empty and reassigned the value 1 only if case-specific conditions are detected. If OS_UNSUPPORTED == 1, then the script prints out a message indicating tailscale isn’t available for the detected OS-version pair, and a copyable blurb users could email to tailscale support to potentially support future tailscale development.

The code block that checks if the invoking OS is supported
print("\n".join(linux_script_lines.split("\n")[362:447]))
    # Step 2: having detected an OS we support, is it one of the
    # versions we support?
    OS_UNSUPPORTED=
    case "$OS" in
        ubuntu|debian|raspbian|centos|oracle|rhel|amazon-linux|opensuse|photon)
            # Check with the package server whether a given version is supported.
            URL="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/installer-supported"
            $CURL "$URL" 2> /dev/null | grep -q OK || OS_UNSUPPORTED=1
            ;;
        fedora)
            # All versions supported, no version checking required.
            ;;
        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" ] && \
               [ "$VERSION" != "14" ]
            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/$TRACK/#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

Root-privilege detection

The script then checks to see if the running shell can run privileged commands (ie if it is the root user or if it can use the sudo or doas command).

If the running shell can’t run privileged commands, the script outputs a message and exits.

The code block that checks for ability to run privileged commands
print("\n".join(linux_script_lines.split("\n")[448:468]))

    # 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
If the shell is running as root, the uid will be 0
!id -u
1000
Checks if the shell can access the sudo command
!type sudo
sudo is /usr/bin/sudo
Checks if the shell can access the doas command
!type doas
/bin/bash: line 1: type: doas: not found

Running the installation

This block of code actually does the work. I’ll dig into the Ubuntu/Debian case, as that’s my situation (and Ubuntu/Debian are the most popularly used linux distros by a wide margin).

For machines using the apt package manager, this script: 1. sets an env-var DEBIAN_FRONTEND to noninteractive, 2. ensures gpg is usable on the system (well, that gnupg is installed), 3. turns on command displaying (via set -x), 4. ensures the keyring dir exists, root can read/write to it, and all other groups and users can only read from that dir, 5. downloads the appropriate tailscale public key file and adds the appropriate tailscale package repo to the invoking-machine’s local keyring and package-source list, 6. updates package version intel and installs tailscale, and 7. starts tailscaled, the tailscale daemon.

The conditional block that installs tailscale
print("\n".join(linux_script_lines.split("\n")[470:614]))

    # Step 4: run the installation.
    OSVERSION="$OS"
    [ "$VERSION" != "" ] && OSVERSION="$OSVERSION $VERSION"
    echo "Installing Tailscale for $OSVERSION, using method $PACKAGETYPE"
    case "$PACKAGETYPE" in
        apt)
            export DEBIAN_FRONTEND=noninteractive
            if [ "$APT_KEY_TYPE" = "legacy" ] && ! type gpg >/dev/null; then
                $SUDO apt-get update
                $SUDO apt-get install -y gnupg
            fi

            set -x
            $SUDO mkdir -p --mode=0755 /usr/share/keyrings
            case "$APT_KEY_TYPE" in
                legacy)
                    $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.asc" | $SUDO apt-key add -
                    $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
                ;;
                keyring)
                    $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.noarmor.gpg" | $SUDO tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
                    $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.tailscale-keyring.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
                ;;
            esac
            $SUDO apt-get update
            $SUDO apt-get install -y tailscale tailscale-archive-keyring
            if [ "$APT_SYSTEMCTL_START" = "true" ]; then
                $SUDO systemctl enable --now tailscaled
                $SUDO systemctl start tailscaled
            fi
            set +x
        ;;
        yum)
            set -x
            $SUDO yum install yum-utils -y
            $SUDO yum-config-manager -y --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
            $SUDO yum install tailscale -y
            $SUDO systemctl enable --now tailscaled
            set +x
        ;;
        dnf)
            # DNF 5 has a different argument format; determine which one we have.
            DNF_VERSION="3"
            if dnf --version | grep -q '^dnf5 version'; then
                DNF_VERSION="5"
            fi

            # The 'config-manager' plugin wasn't implemented when
            # DNF5 was released; detect that and use the old
            # version if necessary.
            if [ "$DNF_VERSION" = "5" ]; then
                set -x
                $SUDO dnf install -y 'dnf-command(config-manager)' && DNF_HAVE_CONFIG_MANAGER=1 || DNF_HAVE_CONFIG_MANAGER=0
                set +x

                if [ "$DNF_HAVE_CONFIG_MANAGER" != "1" ]; then
                    if type dnf-3 >/dev/null; then
                        DNF_VERSION="3"
                    else
                        echo "dnf 5 detected, but 'dnf-command(config-manager)' not available and dnf-3 not found"
                        exit 1
                    fi
                fi
            fi

            set -x
            if [ "$DNF_VERSION" = "3" ]; then
                $SUDO dnf install -y 'dnf-command(config-manager)'
                $SUDO dnf config-manager --add-repo "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
            elif [ "$DNF_VERSION" = "5" ]; then
                # Already installed config-manager, above.
                $SUDO dnf config-manager addrepo --from-repofile="https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
            else
                echo "unexpected: unknown dnf version $DNF_VERSION"
                exit 1
            fi
            $SUDO dnf install -y tailscale
            $SUDO systemctl enable --now tailscaled
            set +x
        ;;
        tdnf)
            set -x
            curl -fsSL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo" > /etc/yum.repos.d/tailscale.repo
            $SUDO tdnf install -y tailscale
            $SUDO systemctl enable --now tailscaled
            set +x
        ;;
        zypper)
            set -x
            $SUDO rpm --import "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/repo.gpg"
            $SUDO zypper --non-interactive ar -g -r "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION/tailscale.repo"
            $SUDO zypper --non-interactive --gpg-auto-import-keys refresh
            $SUDO zypper --non-interactive install tailscale
            $SUDO systemctl enable --now tailscaled
            set +x
            ;;
        pacman)
            set -x
            $SUDO pacman -S tailscale --noconfirm
            $SUDO systemctl enable --now tailscaled
            set +x
            ;;
        pkg)
            set -x
            $SUDO pkg install --yes tailscale
            $SUDO service tailscaled enable
            $SUDO service tailscaled start
            set +x
            ;;
        apk)
            set -x
            if ! grep -Eq '^http.*/community$' /etc/apk/repositories; then
                if type setup-apkrepos >/dev/null; then
                    $SUDO setup-apkrepos -c -1
                else
                    echo "installing tailscale requires the community repo to be enabled in /etc/apk/repositories"
                    exit 1
                fi
            fi
            $SUDO apk add tailscale
            $SUDO rc-update add tailscale
            $SUDO rc-service tailscale start
            set +x
            ;;
        xbps)
            set -x
            $SUDO xbps-install tailscale -y
            set +x
            ;;
        emerge)
            set -x
            $SUDO emerge --ask=n 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
            ;;
Checking the keys and repos
track = "stable"
os = "ubuntu"
version = "jammy"

# $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.asc" | $SUDO apt-key add -
legacy_key_url = f"https://pkgs.tailscale.com/{track}/{os}/{version}.asc"
legacy_key_resp = requests.get(legacy_key_url)
legacy_key_resp.raise_for_status()
print("Legacy gpg key")
print(legacy_key_resp.text)

# $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
legacy_pkg_repo_url = f"https://pkgs.tailscale.com/{track}/{os}/{version}.list"
legacy_pkg_repo_resp = requests.get(legacy_pkg_repo_url)
legacy_pkg_repo_resp.raise_for_status()
print(f"\n{legacy_pkg_repo_resp.text}")

# $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.noarmor.gpg" | $SUDO tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
keyring_keyfile_url = f"https://pkgs.tailscale.com/{track}/{os}/{version}.noarmor.gpg"
keyring_keyfile_resp = requests.get(keyring_keyfile_url)
keyring_keyfile_resp.raise_for_status()
print(f"\n\nModern keyring gpg key (compressed, binary format)")
print("Omitted due to being unparsable binary")
# print(keyring_keyfile_resp.text)

# $CURL "https://pkgs.tailscale.com/$TRACK/$OS/$VERSION.tailscale-keyring.list" | $SUDO tee /etc/apt/sources.list.d/tailscale.list
keyring_pkg_repo_url = f"https://pkgs.tailscale.com/{track}/{os}/{version}.tailscale-keyring.list"
keyring_pkg_repo_resp = requests.get(keyring_pkg_repo_url)
keyring_pkg_repo_resp.raise_for_status()
print(f"\n{keyring_pkg_repo_resp.text}")
Legacy gpg key
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
=AIVM
-----END PGP PUBLIC KEY BLOCK-----


# Tailscale packages for ubuntu jammy
deb https://pkgs.tailscale.com/stable/ubuntu jammy main



Modern keyring gpg key (compressed, binary format)
Omitted due to being unparsable binary

# Tailscale packages for ubuntu jammy
deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/ubuntu jammy main

Conclusion

Looks fine and straightforward. There’s nothing sneaky in this script.