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:
@@ -45,6 +45,31 @@ http {
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
location = /api/auth/check {
|
||||
internal;
|
||||
proxy_pass http://127.0.0.1:5180;
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
location /term/ {
|
||||
auth_request /api/auth/check;
|
||||
error_page 401 = @term_login;
|
||||
proxy_pass http://127.0.0.1:7681;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 6h;
|
||||
proxy_send_timeout 6h;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
}
|
||||
|
||||
location @term_login {
|
||||
return 302 /login;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:5180;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
13
deploy/systemd/local-kanban-ttyd.service
Normal file
13
deploy/systemd/local-kanban-ttyd.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Local Kanban ttyd web terminal (tmux attach)
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/home/gahow/projects/kanban
|
||||
ExecStart=/home/gahow/projects/kanban/scripts/start-ttyd.sh
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -7,11 +7,14 @@ USER_UNIT_DIR="${HOME}/.config/systemd/user"
|
||||
mkdir -p "${USER_UNIT_DIR}"
|
||||
install -m 0644 "${ROOT_DIR}/deploy/systemd/local-kanban.service" "${USER_UNIT_DIR}/local-kanban.service"
|
||||
install -m 0644 "${ROOT_DIR}/deploy/systemd/local-kanban-nginx-8443.service" "${USER_UNIT_DIR}/local-kanban-nginx-8443.service"
|
||||
install -m 0644 "${ROOT_DIR}/deploy/systemd/local-kanban-ttyd.service" "${USER_UNIT_DIR}/local-kanban-ttyd.service"
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now local-kanban.service
|
||||
systemctl --user enable --now local-kanban-nginx-8443.service
|
||||
systemctl --user enable --now local-kanban-ttyd.service
|
||||
|
||||
echo "Local Kanban is managed by user systemd:"
|
||||
echo " systemctl --user status local-kanban.service"
|
||||
echo " systemctl --user status local-kanban-nginx-8443.service"
|
||||
echo " systemctl --user status local-kanban-ttyd.service"
|
||||
|
||||
23
scripts/start-ttyd.sh
Executable file
23
scripts/start-ttyd.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# ttyd web terminal for tmux attach.
|
||||
# Theme: Tokyo Night; font: system monospace stack (fonts render client-side,
|
||||
# so we rely on whatever mono font the viewing device ships).
|
||||
# xterm.js client options: https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
exec /usr/bin/ttyd \
|
||||
-p 7681 \
|
||||
-i 127.0.0.1 \
|
||||
-W \
|
||||
-a \
|
||||
-b /term \
|
||||
-T xterm-256color \
|
||||
-t titleFixed='Kanban Terminal' \
|
||||
-t fontSize=14 \
|
||||
-t lineHeight=1.2 \
|
||||
-t 'fontFamily=ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace' \
|
||||
-t cursorBlink=true \
|
||||
-t 'theme={"background":"#1a1b26","foreground":"#c0caf5","cursor":"#c0caf5","cursorAccent":"#1a1b26","selectionBackground":"#283457","black":"#15161e","red":"#f7768e","green":"#9ece6a","yellow":"#e0af68","blue":"#7aa2f7","magenta":"#bb9af7","cyan":"#7dcfff","white":"#a9b1d6","brightBlack":"#414868","brightRed":"#f7768e","brightGreen":"#9ece6a","brightYellow":"#e0af68","brightBlue":"#7aa2f7","brightMagenta":"#bb9af7","brightCyan":"#7dcfff","brightWhite":"#c0caf5"}' \
|
||||
"${ROOT_DIR}/scripts/tmux-web-attach.sh"
|
||||
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