Created
March 31, 2026 12:03
-
-
Save denisalevi/48034dc2eb7005b1b9ad7340af412434 to your computer and use it in GitHub Desktop.
Install user-space Tailscale SSH on any Linux machine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # ----------------------------------------------------------------------- | |
| # setup-user-tailscale-ssh: Install Tailscale static binaries for a user | |
| # and configure a user-space Tailscale SSH endpoint on the current machine. | |
| # ----------------------------------------------------------------------- | |
| # | |
| # What this does: | |
| # - Downloads the latest stable static Tailscale binaries from Tailscale | |
| # - Installs versioned binaries into $HOME/.local/bin | |
| # - Creates a reusable helper: $HOME/.local/bin/user-tailscale-ssh | |
| # - Prompts for a tailnet machine hostname (unless provided) | |
| # - Starts tailscaled in userspace-networking mode as the current user | |
| # - Enables Tailscale SSH on the node | |
| # | |
| # Typical one-shot usage: | |
| # bash setup-user-tailscale-ssh.sh | |
| # | |
| # Non-interactive examples: | |
| # bash setup-user-tailscale-ssh.sh --hostname gpu-box | |
| # TAILSCALE_AUTHKEY=tskey-... bash setup-user-tailscale-ssh.sh --hostname gpu-box | |
| # | |
| # After installation: | |
| # user-tailscale-ssh start | |
| # user-tailscale-ssh status | |
| # user-tailscale-ssh logs | |
| # user-tailscale-ssh stop | |
| # | |
| # Notes: | |
| # - This intentionally uses userspace networking, so it does not require root | |
| # or a working /dev/net/tun. | |
| # - SSH login itself may still require Tailscale browser approval depending on | |
| # your tailnet SSH policy. | |
| # ----------------------------------------------------------------------- | |
| set -euo pipefail | |
| INSTALL_DIR="${HOME}/.local/bin" | |
| CONFIG_DIR="${HOME}/.config/user-tailscale-ssh" | |
| TEMP_DIR="/tmp/tailscale-user-setup-${USER}" | |
| PKGS_URL="https://pkgs.tailscale.com/stable/" | |
| DEFAULT_HOSTNAME="$(hostname -s 2>/dev/null || hostname)" | |
| TAILSCALE_AUTHKEY="${TAILSCALE_AUTHKEY:-}" | |
| TS_HOSTNAME="${TS_HOSTNAME:-}" | |
| NO_START=0 | |
| NONINTERACTIVE=0 | |
| DOWNLOAD_TOOL="${DOWNLOAD_TOOL:-}" | |
| QUIET=0 | |
| usage() { | |
| cat <<'EOF' | |
| Usage: | |
| setup-user-tailscale-ssh.sh [options] | |
| Options: | |
| --hostname NAME Tailnet machine hostname to request | |
| --auth-key KEY Optional Tailscale auth key | |
| --no-start Install and configure, but do not start Tailscale | |
| --noninteractive Fail instead of prompting for input | |
| --quiet Reduce log noise | |
| -h, --help Show this help | |
| Environment: | |
| TAILSCALE_AUTHKEY Optional Tailscale auth key | |
| DOWNLOAD_TOOL Force curl or wget | |
| TS_HOSTNAME Same as --hostname | |
| Examples: | |
| bash setup-user-tailscale-ssh.sh | |
| bash setup-user-tailscale-ssh.sh --hostname head071 | |
| TAILSCALE_AUTHKEY=tskey-... bash setup-user-tailscale-ssh.sh --hostname gpu-box | |
| EOF | |
| } | |
| log() { | |
| if [[ "$QUIET" -eq 0 ]]; then | |
| printf '%s\n' "$*" | |
| fi | |
| } | |
| die() { | |
| printf 'Error: %s\n' "$*" >&2 | |
| exit 1 | |
| } | |
| need_cmd() { | |
| command -v "$1" >/dev/null 2>&1 || die "missing required command: $1" | |
| } | |
| pick_downloader() { | |
| if [[ -n "$DOWNLOAD_TOOL" ]]; then | |
| case "$DOWNLOAD_TOOL" in | |
| curl|wget) ;; | |
| *) die "DOWNLOAD_TOOL must be curl or wget" ;; | |
| esac | |
| elif command -v curl >/dev/null 2>&1; then | |
| DOWNLOAD_TOOL="curl" | |
| elif command -v wget >/dev/null 2>&1; then | |
| DOWNLOAD_TOOL="wget" | |
| else | |
| die "neither curl nor wget is installed" | |
| fi | |
| log "Using downloader: $DOWNLOAD_TOOL" | |
| } | |
| download_to() { | |
| local url=$1 | |
| local output=$2 | |
| case "$DOWNLOAD_TOOL" in | |
| curl) | |
| curl --fail --location --show-error --silent --output "$output" "$url" | |
| ;; | |
| wget) | |
| wget --quiet --output-document="$output" "$url" | |
| ;; | |
| esac | |
| } | |
| download_text() { | |
| local url=$1 | |
| case "$DOWNLOAD_TOOL" in | |
| curl) | |
| curl --fail --location --show-error --silent "$url" | |
| ;; | |
| wget) | |
| wget --quiet --output-document=- "$url" | |
| ;; | |
| esac | |
| } | |
| map_arch() { | |
| local machine | |
| machine="$(uname -m)" | |
| case "$machine" in | |
| x86_64|amd64) printf 'amd64\n' ;; | |
| aarch64|arm64) printf 'arm64\n' ;; | |
| armv7l|armv7) printf 'arm\n' ;; | |
| i386|i686) printf '386\n' ;; | |
| riscv64) printf 'riscv64\n' ;; | |
| *) die "unsupported architecture: $machine" ;; | |
| esac | |
| } | |
| latest_version_for_arch() { | |
| local arch=$1 | |
| local page version | |
| page="$(download_text "$PKGS_URL")" | |
| version="$(printf '%s\n' "$page" | sed -n "s/.*tailscale_\\([0-9][0-9.]*\\)_${arch}\\.tgz.*/\\1/p" | head -n1)" | |
| [[ -n "$version" ]] || die "could not determine latest Tailscale version for arch ${arch}" | |
| printf '%s\n' "$version" | |
| } | |
| prompt_hostname() { | |
| if [[ -n "$TS_HOSTNAME" ]]; then | |
| return | |
| fi | |
| if [[ "$NONINTERACTIVE" -eq 1 ]]; then | |
| die "--hostname or TS_HOSTNAME is required in noninteractive mode" | |
| fi | |
| printf 'Tailnet hostname to request [%s]: ' "$DEFAULT_HOSTNAME" | |
| read -r TS_HOSTNAME | |
| TS_HOSTNAME="${TS_HOSTNAME:-$DEFAULT_HOSTNAME}" | |
| } | |
| validate_hostname() { | |
| [[ -n "$TS_HOSTNAME" ]] || die "hostname must not be empty" | |
| if [[ ! "$TS_HOSTNAME" =~ ^[A-Za-z0-9][A-Za-z0-9-]*$ ]]; then | |
| die "hostname must match [A-Za-z0-9-] and start with an alphanumeric character" | |
| fi | |
| } | |
| install_binaries() { | |
| local arch version archive_url archive_path unpack_dir versioned_tailscale versioned_tailscaled | |
| local extracted_tailscale extracted_tailscaled | |
| arch="$(map_arch)" | |
| version="$(latest_version_for_arch "$arch")" | |
| archive_url="${PKGS_URL}tailscale_${version}_${arch}.tgz" | |
| archive_path="${TEMP_DIR}/tailscale_${version}_${arch}.tgz" | |
| unpack_dir="${TEMP_DIR}/tailscale_${version}_${arch}" | |
| versioned_tailscale="${INSTALL_DIR}/tailscale-${version}" | |
| versioned_tailscaled="${INSTALL_DIR}/tailscaled-${version}" | |
| log "Latest stable Tailscale for ${arch}: ${version}" | |
| mkdir -p "$INSTALL_DIR" "$TEMP_DIR" | |
| if [[ ! -x "$versioned_tailscale" || ! -x "$versioned_tailscaled" ]]; then | |
| log "Downloading ${archive_url}" | |
| rm -rf "$unpack_dir" | |
| mkdir -p "$unpack_dir" | |
| download_to "$archive_url" "$archive_path" | |
| tar -xzf "$archive_path" -C "$unpack_dir" | |
| extracted_tailscale="$(find "$unpack_dir" -type f -name tailscale | head -n1)" | |
| extracted_tailscaled="$(find "$unpack_dir" -type f -name tailscaled | head -n1)" | |
| [[ -n "$extracted_tailscale" ]] || die "downloaded archive did not contain tailscale" | |
| [[ -n "$extracted_tailscaled" ]] || die "downloaded archive did not contain tailscaled" | |
| install -m 0755 "$extracted_tailscale" "$versioned_tailscale" | |
| install -m 0755 "$extracted_tailscaled" "$versioned_tailscaled" | |
| else | |
| log "Version ${version} already installed in ${INSTALL_DIR}" | |
| fi | |
| ln -sfn "$versioned_tailscale" "${INSTALL_DIR}/tailscale" | |
| ln -sfn "$versioned_tailscaled" "${INSTALL_DIR}/tailscaled" | |
| log "Installed:" | |
| log " ${INSTALL_DIR}/tailscale -> tailscale-${version}" | |
| log " ${INSTALL_DIR}/tailscaled -> tailscaled-${version}" | |
| } | |
| write_config() { | |
| mkdir -p "$CONFIG_DIR" | |
| cat > "${CONFIG_DIR}/config" <<EOF | |
| TS_INSTALL_DIR="${INSTALL_DIR}" | |
| TS_HOSTNAME="${TS_HOSTNAME}" | |
| TS_STATE_DIR="\$HOME/.local/share/tailscale-${TS_HOSTNAME}" | |
| TS_RUNTIME_DIR="/tmp/\${USER}-tailscale-${TS_HOSTNAME}" | |
| TS_ENABLE_SSH="true" | |
| TS_ACCEPT_ROUTES="false" | |
| TS_ACCEPT_DNS="false" | |
| EOF | |
| } | |
| write_helper() { | |
| local helper_path="${INSTALL_DIR}/user-tailscale-ssh" | |
| cat > "$helper_path" <<'EOF' | |
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| CONFIG_FILE="${HOME}/.config/user-tailscale-ssh/config" | |
| [[ -f "$CONFIG_FILE" ]] || { | |
| echo "Missing config: $CONFIG_FILE" >&2 | |
| exit 1 | |
| } | |
| source "$CONFIG_FILE" | |
| tailscale_bin="${TS_INSTALL_DIR}/tailscale" | |
| tailscaled_bin="${TS_INSTALL_DIR}/tailscaled" | |
| socket_path="${TS_RUNTIME_DIR}/tailscaled.sock" | |
| pid_path="${TS_RUNTIME_DIR}/tailscaled.pid" | |
| log_path="${TS_RUNTIME_DIR}/tailscaled.log" | |
| usage() { | |
| cat <<'USAGE' | |
| Usage: | |
| user-tailscale-ssh start | |
| user-tailscale-ssh stop | |
| user-tailscale-ssh restart | |
| user-tailscale-ssh status | |
| user-tailscale-ssh logs | |
| user-tailscale-ssh doctor | |
| user-tailscale-ssh ssh-config | |
| USAGE | |
| } | |
| print_auth_key_help() { | |
| cat <<'HELP' | |
| To use an auth key instead of browser login: | |
| 1. Open the Tailscale admin console. | |
| 2. Go to the Keys page. | |
| 3. Generate an auth key. | |
| 4. Re-run with: | |
| TAILSCALE_AUTHKEY=tskey-... user-tailscale-ssh restart | |
| If your tailnet uses device approval and this machine is meant to behave like a server, | |
| prefer a pre-approved auth key. If you want the node to carry server-style permissions, | |
| you may also want a tagged auth key. | |
| Reference: | |
| https://tailscale.com/kb/1085/auth-keys/ | |
| HELP | |
| } | |
| print_policy_help() { | |
| cat <<'HELP' | |
| If Tailscale SSH is enabled on this machine but SSH access still fails, check your tailnet policy. | |
| For Tailscale SSH to work on a tailnet with custom policy, you typically need both: | |
| 1. A network access rule allowing the source device/user to reach the destination on port 22. | |
| 2. An SSH rule allowing the source to SSH to the destination as the desired Unix user. | |
| Minimal example with placeholders: | |
| { | |
| "grants": [ | |
| { | |
| "src": ["<source-user-or-group>"], | |
| "dst": ["<destination-selector>"], | |
| "ip": ["*"] | |
| } | |
| ], | |
| "ssh": [ | |
| { | |
| "action": "check", | |
| "src": ["<source-user-or-group>"], | |
| "dst": ["<destination-selector>"], | |
| "users": ["<unix-user>"] | |
| } | |
| ] | |
| } | |
| Examples of selectors: | |
| - source user: "user@example.com" | |
| - own devices only: "autogroup:self" | |
| - a tag: "tag:servers" | |
| - Unix user on the machine: "alice" | |
| Notes: | |
| - If you have never customized your tailnet policy, the default policy may already allow SSH to your own devices. | |
| - If your tailnet uses device approval, the machine may also need approval in the Machines page even after login. | |
| - Be careful with "autogroup:nonroot" on shared machines; it can allow SSH as any non-root user on the destination. | |
| References: | |
| https://tailscale.com/docs/features/tailscale-ssh | |
| https://tailscale.com/docs/reference/syntax/policy-file | |
| HELP | |
| } | |
| is_running() { | |
| [[ -f "$pid_path" ]] || return 1 | |
| kill -0 "$(cat "$pid_path")" 2>/dev/null | |
| } | |
| ensure_dirs() { | |
| mkdir -p "$TS_STATE_DIR" "$TS_RUNTIME_DIR" | |
| } | |
| start_daemon() { | |
| ensure_dirs | |
| if is_running; then | |
| echo "tailscaled already running (pid $(cat "$pid_path"))" | |
| return 0 | |
| fi | |
| rm -f "$socket_path" "$pid_path" | |
| "$tailscaled_bin" \ | |
| --state="${TS_STATE_DIR}/tailscaled.state" \ | |
| --socket="$socket_path" \ | |
| --statedir="$TS_STATE_DIR" \ | |
| --tun=userspace-networking \ | |
| >"$log_path" 2>&1 & | |
| echo "$!" > "$pid_path" | |
| for _ in $(seq 1 20); do | |
| [[ -S "$socket_path" ]] && return 0 | |
| sleep 1 | |
| done | |
| echo "tailscaled did not create $socket_path" >&2 | |
| [[ -f "$log_path" ]] && sed -n '1,160p' "$log_path" >&2 | |
| exit 1 | |
| } | |
| run_up() { | |
| local up_output rc up_output_file | |
| local -a up_args | |
| up_args=( | |
| --socket="$socket_path" | |
| up | |
| --hostname="$TS_HOSTNAME" | |
| --accept-routes="$TS_ACCEPT_ROUTES" | |
| --accept-dns="$TS_ACCEPT_DNS" | |
| ) | |
| if [[ "${TS_ENABLE_SSH}" == "true" ]]; then | |
| up_args+=(--ssh=true) | |
| fi | |
| if [[ -n "${TAILSCALE_AUTHKEY:-}" ]]; then | |
| up_args+=(--auth-key="${TAILSCALE_AUTHKEY}") | |
| fi | |
| up_output_file="$(mktemp)" | |
| echo "Running tailscale up for hostname '${TS_HOSTNAME}'..." | |
| echo "If this is a fresh machine, watch for a https://login.tailscale.com/... URL below." | |
| echo | |
| set +e | |
| "$tailscale_bin" "${up_args[@]}" 2>&1 | tee "$up_output_file" | |
| rc=$? | |
| set -e | |
| up_output="$(cat "$up_output_file")" | |
| rm -f "$up_output_file" | |
| if printf '%s\n' "$up_output" | grep -q 'https://login.tailscale.com/'; then | |
| echo | |
| echo "Complete the Tailscale browser auth above, then run:" | |
| echo " user-tailscale-ssh status" | |
| echo "If the node has joined your tailnet, SSH to:" | |
| echo " ssh ${USER}@${TS_HOSTNAME}.<your-tailnet>.ts.net" | |
| fi | |
| return "$rc" | |
| } | |
| backend_state() { | |
| "$tailscale_bin" --socket="$socket_path" status --json 2>/dev/null \ | |
| | sed -n 's/.*"BackendState":"\([^"]*\)".*/\1/p' \ | |
| | head -n1 | |
| } | |
| node_fqdn() { | |
| "$tailscale_bin" --socket="$socket_path" status --self=false 2>/dev/null \ | |
| | awk 'NR==1 {print $3}' | |
| } | |
| show_join_guidance() { | |
| local state fqdn ip4 | |
| state="$(backend_state || true)" | |
| case "$state" in | |
| Running) | |
| fqdn="$(node_fqdn || true)" | |
| ip4="$("$tailscale_bin" --socket="$socket_path" ip -4 2>/dev/null | head -n1 || true)" | |
| echo | |
| echo "This machine has joined the tailnet." | |
| [[ -n "$fqdn" ]] && echo "MagicDNS name: $fqdn" | |
| [[ -n "$ip4" ]] && echo "Tailscale IPv4: $ip4" | |
| echo "Next step:" | |
| if [[ -n "$fqdn" ]]; then | |
| echo " ssh ${USER}@${fqdn}" | |
| else | |
| echo " user-tailscale-ssh status" | |
| fi | |
| echo | |
| print_policy_help | |
| ;; | |
| NeedsLogin|"") | |
| echo | |
| echo "This machine has not joined a tailnet yet." | |
| echo "Next step:" | |
| echo " rerun: user-tailscale-ssh start" | |
| echo " and open the https://login.tailscale.com/... URL if one is shown." | |
| echo | |
| print_auth_key_help | |
| echo | |
| print_policy_help | |
| ;; | |
| NeedsMachineAuth) | |
| echo | |
| echo "This machine authenticated, but it still needs approval to join the tailnet." | |
| echo "Next step:" | |
| echo " approve the device in the Tailscale admin console Machines page" | |
| echo " or use a pre-approved auth key and rerun:" | |
| echo " TAILSCALE_AUTHKEY=tskey-... user-tailscale-ssh restart" | |
| echo | |
| print_auth_key_help | |
| echo | |
| print_policy_help | |
| ;; | |
| *) | |
| echo | |
| echo "Tailscale backend state: $state" | |
| echo "If the node is not visible in your tailnet yet, run:" | |
| echo " user-tailscale-ssh status" | |
| echo " user-tailscale-ssh logs" | |
| echo " user-tailscale-ssh doctor" | |
| echo | |
| print_policy_help | |
| ;; | |
| esac | |
| } | |
| status_cmd() { | |
| if ! is_running; then | |
| echo "tailscaled is not running" | |
| exit 1 | |
| fi | |
| "$tailscale_bin" --socket="$socket_path" status --self || true | |
| "$tailscale_bin" --socket="$socket_path" ip -4 || true | |
| } | |
| stop_cmd() { | |
| if ! is_running; then | |
| echo "tailscaled is not running" | |
| rm -f "$pid_path" "$socket_path" | |
| return 0 | |
| fi | |
| kill "$(cat "$pid_path")" 2>/dev/null || true | |
| rm -f "$pid_path" "$socket_path" | |
| echo "Stopped tailscaled" | |
| } | |
| doctor_cmd() { | |
| echo "== Binary checks ==" | |
| command -v "$tailscale_bin" >/dev/null 2>&1 && "$tailscale_bin" version | sed -n '1,2p' | |
| command -v "$tailscaled_bin" >/dev/null 2>&1 && "$tailscaled_bin" --version | sed -n '1p' | |
| echo | |
| echo "== Runtime paths ==" | |
| printf 'Config: %s\n' "$CONFIG_FILE" | |
| printf 'State dir: %s\n' "$TS_STATE_DIR" | |
| printf 'Runtime dir: %s\n' "$TS_RUNTIME_DIR" | |
| printf 'Socket: %s\n' "$socket_path" | |
| printf 'Log: %s\n' "$log_path" | |
| echo | |
| echo "== Network prerequisites ==" | |
| if command -v curl >/dev/null 2>&1; then | |
| curl -I --max-time 8 https://controlplane.tailscale.com 2>/dev/null | sed -n '1,3p' || echo "Failed to reach controlplane.tailscale.com" | |
| elif command -v wget >/dev/null 2>&1; then | |
| wget --server-response --spider --timeout=8 https://controlplane.tailscale.com 2>&1 | sed -n '1,6p' || echo "Failed to reach controlplane.tailscale.com" | |
| else | |
| echo "No curl/wget available for HTTPS checks" | |
| fi | |
| if [[ -e /dev/net/tun ]]; then | |
| ls -l /dev/net/tun | |
| echo "Note: this setup uses userspace networking even if /dev/net/tun exists." | |
| else | |
| echo "/dev/net/tun is missing. Userspace networking is the right mode here." | |
| fi | |
| echo | |
| echo "== Tailscale status ==" | |
| if is_running; then | |
| "$tailscale_bin" --socket="$socket_path" status || true | |
| else | |
| echo "tailscaled is not running" | |
| fi | |
| } | |
| ssh_config_cmd() { | |
| local fqdn | |
| fqdn="$("$tailscale_bin" --socket="$socket_path" status --self 2>/dev/null | awk 'NR==1 {print $3}')" | |
| if [[ -z "$fqdn" ]]; then | |
| fqdn="${TS_HOSTNAME}" | |
| fi | |
| cat <<EOF2 | |
| Host ${TS_HOSTNAME}-ts | |
| HostName ${fqdn} | |
| User ${USER} | |
| EOF2 | |
| } | |
| cmd="${1:-start}" | |
| case "$cmd" in | |
| start) | |
| start_daemon | |
| run_up || true | |
| echo | |
| echo "Current status:" | |
| status_cmd || true | |
| show_join_guidance | |
| ;; | |
| stop) | |
| stop_cmd | |
| ;; | |
| restart) | |
| stop_cmd || true | |
| start_daemon | |
| run_up || true | |
| status_cmd || true | |
| show_join_guidance | |
| ;; | |
| status) | |
| status_cmd | |
| ;; | |
| logs) | |
| [[ -f "$log_path" ]] && tail -n 100 "$log_path" || echo "No log file yet: $log_path" | |
| ;; | |
| doctor) | |
| doctor_cmd | |
| ;; | |
| ssh-config) | |
| ssh_config_cmd | |
| ;; | |
| -h|--help|help) | |
| usage | |
| ;; | |
| *) | |
| echo "Unknown command: $cmd" >&2 | |
| usage >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| EOF | |
| chmod +x "$helper_path" | |
| } | |
| run_prereq_checks() { | |
| need_cmd tar | |
| need_cmd install | |
| need_cmd sed | |
| need_cmd awk | |
| pick_downloader | |
| log "Checking outbound HTTPS to Tailscale control plane..." | |
| if ! download_text "https://controlplane.tailscale.com" >/dev/null 2>&1; then | |
| die "cannot reach https://controlplane.tailscale.com; outbound HTTPS may be blocked" | |
| fi | |
| if [[ -e /dev/net/tun ]]; then | |
| log "/dev/net/tun exists. This installer still uses userspace networking." | |
| else | |
| log "/dev/net/tun is missing. Userspace networking is expected." | |
| fi | |
| } | |
| start_now() { | |
| local helper_path="${INSTALL_DIR}/user-tailscale-ssh" | |
| log | |
| log "Starting user-space Tailscale SSH..." | |
| "$helper_path" start || true | |
| } | |
| print_next_steps() { | |
| local helper_path="${INSTALL_DIR}/user-tailscale-ssh" | |
| cat <<EOF | |
| Setup complete. | |
| Installed binaries: | |
| ${INSTALL_DIR}/tailscale | |
| ${INSTALL_DIR}/tailscaled | |
| Helper: | |
| ${helper_path} | |
| Do not rerun this installer for normal daily use. | |
| Next time you want to bring the node back online, run: | |
| ${helper_path} start | |
| To stop it: | |
| ${helper_path} stop | |
| Useful commands: | |
| ${helper_path} start | |
| ${helper_path} status | |
| ${helper_path} logs | |
| ${helper_path} doctor | |
| ${helper_path} ssh-config | |
| Expected SSH destination after auth: | |
| ${TS_HOSTNAME}.<your-tailnet>.ts.net | |
| If SSH later says "requires an additional check", open the Tailscale URL it prints. | |
| If SSH says access is denied by policy, add or adjust a Tailscale SSH rule for this node. | |
| Policy reference: | |
| https://tailscale.com/docs/features/tailscale-ssh | |
| https://tailscale.com/docs/reference/syntax/policy-file | |
| EOF | |
| } | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --hostname) | |
| [[ $# -ge 2 ]] || die "--hostname requires a value" | |
| TS_HOSTNAME=$2 | |
| shift 2 | |
| ;; | |
| --auth-key) | |
| [[ $# -ge 2 ]] || die "--auth-key requires a value" | |
| TAILSCALE_AUTHKEY=$2 | |
| shift 2 | |
| ;; | |
| --no-start) | |
| NO_START=1 | |
| shift | |
| ;; | |
| --noninteractive) | |
| NONINTERACTIVE=1 | |
| shift | |
| ;; | |
| --quiet) | |
| QUIET=1 | |
| shift | |
| ;; | |
| -h|--help) | |
| usage | |
| exit 0 | |
| ;; | |
| *) | |
| die "unknown argument: $1" | |
| ;; | |
| esac | |
| done | |
| mkdir -p "$TEMP_DIR" | |
| trap 'rm -rf "$TEMP_DIR"' EXIT | |
| prompt_hostname | |
| validate_hostname | |
| run_prereq_checks | |
| install_binaries | |
| write_config | |
| write_helper | |
| if [[ "$NO_START" -eq 0 ]]; then | |
| start_now | |
| fi | |
| print_next_steps |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment