ops: ttyd web terminal sidecar behind nginx auth_request
- tmux-web-attach.sh: attach browser to one window via link-window into a private temp session (no session-group pollution); window-size smallest while attached so the browser never gets cut off; mouse on for natural wheel/touch scrolling; status bar off; destroy-unattached cleanup - start-ttyd.sh: ttyd on 127.0.0.1:7681, base path /term, url-arg enabled, Tokyo Night theme, system mono stack, fontSize 14 - systemd user unit + install script registration - nginx: /term/ proxied with websocket upgrade, gated by auth_request to the kanban cookie check; unauthenticated -> 302 /login Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
60
scripts/tmux-web-attach.sh
Executable file
60
scripts/tmux-web-attach.sh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# Attach a browser (via ttyd) to a single tmux window by linking it into a
|
||||
# private temporary session. Unlike grouped sessions, link-window leaves no
|
||||
# permanent group label on the source session, and each browser tab gets an
|
||||
# independent view (own active window, own status setting).
|
||||
# Usage: tmux-web-attach.sh <session> [window-index]
|
||||
set -euo pipefail
|
||||
|
||||
sess="${1:-}"
|
||||
win="${2:-}"
|
||||
|
||||
if [ -z "$sess" ]; then
|
||||
echo "missing session. open this page from the kanban Live Agents panel."
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only accept existing session names; never interpret arbitrary input.
|
||||
if ! tmux list-sessions -F '#{session_name}' 2>/dev/null | grep -Fxq -- "$sess"; then
|
||||
echo "unknown session: $sess"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Default to the session's active window; validate the index either way.
|
||||
if [ -z "$win" ]; then
|
||||
win="$(tmux display-message -p -t "$sess" '#{window_index}')"
|
||||
fi
|
||||
if ! tmux list-windows -t "$sess" -F '#{window_index}' | grep -Fxq -- "$win"; then
|
||||
echo "unknown window: $sess:$win"
|
||||
sleep 2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
src="$sess:$win"
|
||||
tmp="web-$$-$RANDOM"
|
||||
|
||||
tmux new-session -d -s "$tmp" -x 220 -y 60
|
||||
tmux set-option -t "$tmp" status off
|
||||
# mouse on (session-scoped, local clients unaffected): wheel / touch scrolling
|
||||
# from the browser enters copy-mode and scrolls history naturally, no prefix keys.
|
||||
tmux set-option -t "$tmp" mouse on
|
||||
# Replace the placeholder window with a link to the target window (-k kills the placeholder).
|
||||
first="$(tmux display-message -p -t "$tmp" '#{window_index}')"
|
||||
tmux link-window -k -s "$src" -t "$tmp:$first"
|
||||
|
||||
# A tmux window has a single size shared by every viewer. With the global
|
||||
# "latest" policy a bigger local client cuts the browser view off, so pin this
|
||||
# window to "smallest" while a web viewer is attached: the browser always sees
|
||||
# the full content; a larger local client sees dotted padding instead.
|
||||
tmux set-window-option -t "$src" window-size smallest
|
||||
|
||||
cleanup() {
|
||||
tmux set-window-option -u -t "$src" window-size 2>/dev/null || true
|
||||
tmux kill-session -t "$tmp" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT HUP TERM INT
|
||||
|
||||
# destroy-unattached must be set only after attaching (it kills detached sessions instantly).
|
||||
tmux attach -t "$tmp" \; set-option destroy-unattached on
|
||||
Reference in New Issue
Block a user