Skip to content

Instantly share code, notes, and snippets.

@htlin222
Created March 30, 2026 03:12
Show Gist options
  • Select an option

  • Save htlin222/620b3b38d313c80a247b178aa1949510 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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