terminal: add multi-tmux-session lobby on term.viktorbarzin.me (additive)

New hostname term.viktorbarzin.me serves a session-picker UI that lists,
creates, and kills tmux sessions. Visiting ?arg=<name> attaches to that
session (auto-creates via tmux -A). Builds on a fresh ttyd instance
(7685) plus a tmux-api Go binary (7684) on the DevVM, both running as
User=wizard alongside (not replacing) the existing ttyd.service (7681),
ttyd-ro.service (7682), and clipboard-upload (7683). Cutover of
terminal.viktorbarzin.me to the multi-session setup is deferred.

Terraform diff is purely additive — terminal-multi/tmux-api Service +
Endpoints + ingress_multi (term.viktorbarzin.me, Authentik-gated) + an
IngressRoute that path-prefixes /api/sessions/* to tmux-api with the
matching strip-prefix Middleware.

DevVM-side units ship under files/devvm/ with a README — manual scp +
systemctl install (see files/devvm/README.md). ttyd 1.7.7 already
deployed there (≥1.7 needed for -a).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-13 15:24:04 +00:00
parent 5754cfb56f
commit a724169b0e
8 changed files with 869 additions and 0 deletions

View file

@ -0,0 +1,67 @@
# DevVM terminal-multi files
These files configure the multi-session terminal on the DevVM (`10.0.10.10`).
They install **alongside** the existing `ttyd.service` (port 7681) and
`ttyd-ro.service` (port 7682) — the existing units are **not** modified.
## Layout
| Source | Destination on DevVM |
|--------|----------------------|
| `tmux-attach.sh` | `/usr/local/bin/tmux-attach.sh` (chmod 0755) |
| `ttyd-multi.service` | `/etc/systemd/system/ttyd-multi.service` |
| `tmux-api.service` | `/etc/systemd/system/tmux-api.service` |
| `../index-multi.html` (one level up) | `/usr/local/share/ttyd/index-multi.html` |
| `../../tmux-api/` binary, built `GOOS=linux GOARCH=amd64` | `/usr/local/bin/tmux-api` (chmod 0755) |
## Apply
From the workstation (`infra/` repo root):
```bash
DEVVM=10.0.10.10 # SSH config provides the user
# 1. Build the tmux-api binary for linux/amd64
( cd infra/stacks/terminal/tmux-api && GOOS=linux GOARCH=amd64 go build -o /tmp/tmux-api . )
# 2. HTML page + wrapper script
scp infra/stacks/terminal/files/index-multi.html $DEVVM:/tmp/index-multi.html
scp infra/stacks/terminal/files/devvm/tmux-attach.sh $DEVVM:/tmp/tmux-attach.sh
ssh $DEVVM "sudo install -m 0644 /tmp/index-multi.html /usr/local/share/ttyd/index-multi.html && \
sudo install -m 0755 /tmp/tmux-attach.sh /usr/local/bin/tmux-attach.sh && \
rm /tmp/index-multi.html /tmp/tmux-attach.sh"
# 3. tmux-api binary
scp /tmp/tmux-api $DEVVM:/tmp/tmux-api
ssh $DEVVM "sudo install -m 0755 /tmp/tmux-api /usr/local/bin/tmux-api && rm /tmp/tmux-api"
# 4. systemd units
scp infra/stacks/terminal/files/devvm/ttyd-multi.service $DEVVM:/tmp/
scp infra/stacks/terminal/files/devvm/tmux-api.service $DEVVM:/tmp/
ssh $DEVVM "sudo mv /tmp/ttyd-multi.service /etc/systemd/system/ && \
sudo mv /tmp/tmux-api.service /etc/systemd/system/ && \
sudo systemctl daemon-reload && \
sudo systemctl enable --now ttyd-multi tmux-api"
# 5. Sanity checks
ssh $DEVVM "systemctl status ttyd-multi tmux-api --no-pager"
ssh $DEVVM "curl -sf localhost:7684/sessions"
ssh $DEVVM "curl -sf localhost:7685/ | head -5"
ssh $DEVVM "systemctl is-active ttyd ttyd-ro" # existing units untouched
```
## Notes
- **`User=wizard`** matches the existing `ttyd.service` so the new services
share the same tmux server (one socket per Unix user). Sessions created
via either `terminal.viktorbarzin.me` or `term.viktorbarzin.me` are
cross-visible. This is intentional.
- **ttyd version** is `1.7.7` on the DevVM — the `-a` flag (allow URL args
→ argv) requires ≥ 1.7.
- **Argv flow**: `?arg=foo` on the URL → ttyd appends `foo` as `$1` to
`tmux-attach.sh` → the wrapper regex-validates and runs
`tmux new-session -A -s "$name"`. ttyd uses argv (never a shell string),
so there is no injection path.
- **No external exposure of 7684/7685** — the DevVM is reachable only from
the cluster (`10.0.10.10` is on the internal VLAN). Authentik forward-auth
on the ingress is the access gate.

View file

@ -0,0 +1,11 @@
[Unit]
Description=tmux-api (port 7684) - REST API for listing and killing tmux sessions
After=network.target
[Service]
ExecStart=/usr/local/bin/tmux-api
Restart=always
User=wizard
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Invoked by ttyd-multi.service. ttyd's -a flag forwards ?arg=<value> as $1.
# Defence-in-depth: ttyd uses argv (never shell strings) and we re-validate
# here before handing the name to tmux as a quoted argv slot.
set -euo pipefail
name="${1:-main}"
if ! [[ "$name" =~ ^[a-zA-Z0-9_-]{1,32}$ ]]; then
name=main
fi
exec tmux new-session -A -s "$name" -c /home/wizard/code

View file

@ -0,0 +1,11 @@
[Unit]
Description=ttyd multi-session (port 7685) - tmux session lobby + per-session attach
After=network.target
[Service]
ExecStart=/usr/local/bin/ttyd -W -a -t enableClipboard=true -I /usr/local/share/ttyd/index-multi.html -p 7685 /usr/local/bin/tmux-attach.sh
Restart=always
User=wizard
[Install]
WantedBy=multi-user.target