Created
March 30, 2026 03:12
-
-
Save htlin222/620b3b38d313c80a247b178aa1949510 to your computer and use it in GitHub Desktop.
podcast-tmux.sh — Share a read-only tmux session over the web via ttyd + Tailscale Funnel. Students get a secret SHA-256 URL to watch your terminal live (read-only, no typing). Great for live coding demos and classroom teaching.
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 | |
| # podcast-tmux.sh — Share a read-only tmux session via ttyd + Tailscale Funnel | |
| # Students get a secret URL; they can watch but not type. | |
| # | |
| # Prerequisites (macOS): | |
| # brew install ttyd tmux tailscale | |
| # # Enable Tailscale Funnel: https://tailscale.com/kb/1223/funnel | |
| # | |
| # Install: | |
| # curl -fsSL https://gist.githubusercontent.com/htlin222/620b3b38d313c80a247b178aa1949510/raw/podcast-tmux.sh -o ~/bin/podcast-tmux.sh | |
| # chmod +x ~/bin/podcast-tmux.sh | |
| # | |
| # Usage: | |
| # podcast-tmux.sh [session-name] [--fontsize N] [--port N] | |
| # | |
| # Examples: | |
| # podcast-tmux.sh # default session "podcast", font 24 | |
| # podcast-tmux.sh myclass # custom session name | |
| # podcast-tmux.sh myclass --fontsize 36 # bigger font for projector | |
| # | |
| # To work in the shared session locally: | |
| # tmux -L podcast attach -t <session-name> | |
| set -euo pipefail | |
| # Defaults | |
| SESSION_NAME="podcast" | |
| TTYD_PORT=19999 | |
| FONT_SIZE=24 | |
| TMUX_SOCKET="podcast" | |
| SECRET_PATH="478f5ea6ea5a05937e320dbb5a2f67431a2c355f0f63fa0970287fbce3ffe0b3" | |
| # Parse arguments | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| --fontsize) FONT_SIZE="$2"; shift 2 ;; | |
| --port) TTYD_PORT="$2"; shift 2 ;; | |
| -h|--help) | |
| echo "Usage: podcast-tmux.sh [session-name] [--fontsize N] [--port N]" | |
| echo " session-name tmux session name (default: podcast)" | |
| echo " --fontsize N web terminal font size (default: 24)" | |
| echo " --port N ttyd port (default: 19999)" | |
| exit 0 ;; | |
| *) SESSION_NAME="$1"; shift ;; | |
| esac | |
| done | |
| # Dependency check | |
| for cmd in ttyd tmux tailscale python3; do | |
| command -v "$cmd" &>/dev/null || { echo "ERROR: '$cmd' not found. Install it first."; exit 1; } | |
| done | |
| tailscale status &>/dev/null || { echo "ERROR: Tailscale is not running."; exit 1; } | |
| # Colors | |
| GREEN='\033[0;32m' | |
| CYAN='\033[0;36m' | |
| YELLOW='\033[1;33m' | |
| NC='\033[0m' | |
| cleanup() { | |
| echo -e "\n${YELLOW}Shutting down...${NC}" | |
| # Kill ttyd | |
| pkill -f "ttyd.*${TTYD_PORT}" 2>/dev/null || true | |
| # Remove the funnel path | |
| tailscale funnel --set-path="/${SECRET_PATH}" off 2>/dev/null || true | |
| echo -e "${GREEN}Cleaned up. ttyd stopped, funnel path removed.${NC}" | |
| } | |
| trap cleanup EXIT INT TERM | |
| # 1. Start ttyd with separate tmux socket (-L) to avoid version skew | |
| echo -e "${CYAN}Starting ttyd (fontSize=${FONT_SIZE}) on port ${TTYD_PORT}...${NC}" | |
| ttyd \ | |
| --port "$TTYD_PORT" \ | |
| --interface 0.0.0.0 \ | |
| -t "fontSize=${FONT_SIZE}" \ | |
| tmux -L "$TMUX_SOCKET" new -A -s "$SESSION_NAME" & | |
| TTYD_PID=$! | |
| sleep 1 | |
| # Verify ttyd started | |
| if ! kill -0 "$TTYD_PID" 2>/dev/null; then | |
| echo "ERROR: ttyd failed to start. Is port ${TTYD_PORT} in use?" | |
| exit 1 | |
| fi | |
| # 3. Expose via Tailscale Funnel on the secret path (background mode) | |
| echo -e "${CYAN}Setting up Tailscale Funnel...${NC}" | |
| tailscale funnel --bg --set-path="/${SECRET_PATH}" "${TTYD_PORT}" | |
| # 4. Get the public URL | |
| TS_HOSTNAME=$(tailscale status --json | python3 -c "import sys,json; print(json.load(sys.stdin)['Self']['DNSName'].rstrip('.'))") | |
| echo "" | |
| echo -e "${GREEN}========================================${NC}" | |
| echo -e "${GREEN} Read-Only Terminal Podcast is LIVE${NC}" | |
| echo -e "${GREEN}========================================${NC}" | |
| echo "" | |
| echo -e " ${CYAN}Share this link with students:${NC}" | |
| echo "" | |
| echo -e " ${YELLOW}https://${TS_HOSTNAME}/${SECRET_PATH}/${NC}" | |
| echo "" | |
| echo -e " tmux session: ${SESSION_NAME}" | |
| echo -e " Mode: ${GREEN}READ-ONLY${NC} (students cannot type)" | |
| echo -e " Press ${YELLOW}Ctrl+C${NC} to stop sharing" | |
| echo "" | |
| echo -e "${GREEN}========================================${NC}" | |
| echo "" | |
| echo -e "To work in the session yourself, open another terminal and run:" | |
| echo -e " ${CYAN}tmux -L ${TMUX_SOCKET} attach -t ${SESSION_NAME}${NC}" | |
| echo "" | |
| # Keep running until interrupted | |
| wait "$TTYD_PID" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment