#!/bin/sh set -eu ARCH="$(uname -m)" case "$ARCH" in x86_64|amd64) DOWNLOAD_URL="https://tunneltime.dev/downloads/tunneltime-linux-amd64" ;; aarch64|arm64) DOWNLOAD_URL="https://tunneltime.dev/downloads/tunneltime-linux-arm64" ;; *) echo "unsupported architecture: $ARCH" >&2 exit 1 ;; esac curl -fsSL "$DOWNLOAD_URL" -o tunneltime chmod +x tunneltime print_usage() { echo "Interactive install: sh <(curl -fsSL https://tunneltime.dev/install.sh)" echo "Compatibility mode: sh <(curl -fsSL https://tunneltime.dev/install.sh) [local-port] [http|tcp]" } print_next_steps() { echo echo "Saved your token. You can start a tunnel later with commands like:" echo " ./tunneltime http 3000" echo " ./tunneltime tcp 22" } prompt_nonempty() { prompt="$1" value="" while [ -z "$value" ]; do printf "%s" "$prompt" >&2 IFS= read -r value value=$(printf '%s' "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') done printf '%s' "$value" } prompt_yes_no() { prompt="$1" while :; do printf "%s" "$prompt" >&2 IFS= read -r answer answer=$(printf '%s' "$answer" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') case "$answer" in y|yes) return 0 ;; n|no) return 1 ;; esac echo "Please answer Y or N." >&2 done } prompt_tunnel_mode() { while :; do printf "Tunnel type [http/tcp]: " >&2 IFS= read -r mode mode=$(printf '%s' "$mode" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') case "$mode" in http|tcp) printf '%s' "$mode" return 0 ;; esac echo "Enter http or tcp." >&2 done } prompt_port() { while :; do printf "Local port to tunnel: " >&2 IFS= read -r port port=$(printf '%s' "$port" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') case "$port" in ''|*[!0-9]*) echo "Enter a numeric port between 1 and 65535." >&2 ;; *) if [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then printf '%s' "$port" return 0 fi echo "Enter a numeric port between 1 and 65535." >&2 ;; esac done } prompt_access_mode() { echo >&2 echo "Security options:" >&2 echo " public - anyone on the internet can connect" >&2 echo " allowlist - only specific IPs or CIDR ranges can connect" >&2 echo " dynamic - the first client IP that connects claims the tunnel" >&2 echo "Press Enter to keep the default: public." >&2 while :; do printf "Security mode [public/allowlist/dynamic]: " >&2 IFS= read -r mode mode=$(printf '%s' "$mode" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') case "$mode" in '') printf 'public' return 0 ;; public|allowlist|dynamic) printf '%s' "$mode" return 0 ;; esac echo "Choose public, allowlist, or dynamic." >&2 done } quote_arg() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\''/g")" } print_command() { first=1 for arg in "$@"; do if [ "$first" -eq 1 ]; then first=0 else printf " " fi quote_arg "$arg" done printf "\n" } TOKEN="${1:-}" PORT="${2:-}" MODE="${3:-}" SUBDOMAIN="" ACCESS_MODE="" ALLOWLIST_RAW="" if [ -n "$TOKEN" ]; then if [ "$TOKEN" = "--help" ] || [ "$TOKEN" = "-h" ]; then print_usage exit 0 fi PORT="${PORT:-3000}" MODE="${MODE:-http}" ACCESS_MODE="public" else echo "TunnelTime installer" echo "Downloaded ./tunneltime for $ARCH." echo TOKEN="$(prompt_nonempty 'Agent token: ')" fi ./tunneltime login "$TOKEN" if [ -z "${1:-}" ]; then echo if ! prompt_yes_no "Start a tunnel now? [Y/N]: "; then print_next_steps exit 0 fi echo echo "Let's set up your first tunnel." echo "Choose http for websites, webhooks, dashboards, and local dev servers." echo "Choose tcp for SSH, databases, and other raw TCP services." MODE="$(prompt_tunnel_mode)" echo echo "Enter the local port where your app or service is listening." PORT="$(prompt_port)" echo echo "If you leave the subdomain blank, TunnelTime will generate one for you." printf "Optional custom subdomain (press Enter to auto-generate): " IFS= read -r SUBDOMAIN SUBDOMAIN=$(printf '%s' "$SUBDOMAIN" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') ACCESS_MODE="$(prompt_access_mode)" if [ "$ACCESS_MODE" = "allowlist" ]; then echo echo "Enter one or more source IPs or CIDR ranges separated by commas." ALLOWLIST_RAW="$(prompt_nonempty 'Allowed IPs/CIDRs (comma-separated): ')" fi fi set -- ./tunneltime "$MODE" "$PORT" if [ -n "$SUBDOMAIN" ]; then set -- "$@" --subdomain "$SUBDOMAIN" fi if [ -n "$ACCESS_MODE" ]; then set -- "$@" --access "$ACCESS_MODE" fi if [ "$ACCESS_MODE" = "allowlist" ]; then OLD_IFS="$IFS" IFS=',' for raw in $ALLOWLIST_RAW; do entry=$(printf '%s' "$raw" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [ -n "$entry" ]; then set -- "$@" --allow "$entry" fi done IFS="$OLD_IFS" fi echo echo "Running command:" COMMAND_PREVIEW=$(print_command "$@") printf '%s ' "$COMMAND_PREVIEW" "$@"