refactor: strip tmux/ttyd artifacts, kitty-web is now purely xpra+kitty

tmux web terminal moved to sethmux (git.sethpc.xyz/Seth/sethmux)
This commit is contained in:
Mortdecai
2026-03-26 19:00:07 -04:00
parent dcf9a53309
commit 51e8932dde
9 changed files with 32 additions and 295 deletions
+31 -79
View File
@@ -1,129 +1,81 @@
# kitty-web
Run the real [kitty terminal](https://sw.kovidgoyal.net/kitty/) in your browser. Mobile-friendly, GPU-accelerated, with full tab and split support.
Run the real [kitty terminal](https://sw.kovidgoyal.net/kitty/) in your browser via [Xpra](https://xpra.org/) HTML5 streaming.
Powered by [Xpra](https://xpra.org/) — serves kitty as an HTML5 application via its built-in web client. This is not a terminal emulator in JavaScript; it's the actual kitty running on your server, streamed to your browser.
This is not a JavaScript terminal emulator it's the actual kitty with GPU rendering, image protocol, ligatures, and native tabs, streamed to your browser.
## Features
- **Real kitty** — GPU rendering, ligatures, image protocol, all of it
- **Kitty tabs and splits** — native `ctrl+shift+t`, splits, layouts
- **Persistent session** — close the browser, reconnect later, everything is still there
- **Multi-client** — multiple browsers can view/interact with the same session
- **Mobile-friendly** — Xpra's HTML5 client handles touch input, keyboard, and scaling
- **Push notifications** — optional notification API for long-running commands
- **Real kitty** — GPU rendering, kitty image protocol, all features intact
- **Native kitty tabs/splits** — `Ctrl+Shift+T`, layouts, everything works
- **Persistent session** — close browser, reconnect later, session is still there
- **Multi-client** — multiple browsers can connect simultaneously
- **Mobile-friendly** — Xpra HTML5 client handles touch, scaling, on-screen keyboard
## Architecture
```
Browser -> Caddy (HTTPS + Auth) -> Xpra HTML5 (port 7681) -> kitty
Browser -> Caddy (HTTPS + Auth) -> Xpra HTML5 (port 7681) -> kitty (Xvfb)
```
Xpra runs kitty inside a virtual X display (Xvfb) and streams the rendered output to browsers via WebSocket. The HTML5 client handles input, clipboard, and display scaling.
Xpra runs kitty in a virtual X display and streams the rendered pixels to browsers via WebSocket.
## Quick Start
## Setup
### Prerequisites
- Debian/Ubuntu (tested on Debian 13 Trixie)
- kitty (`apt install kitty`)
- Xpra (`https://xpra.org/` — add their repo for latest version)
### Install
```bash
# Add Xpra repo (Debian example)
# Add Xpra repo
curl -sL https://xpra.org/xpra.asc | sudo tee /usr/share/keyrings/xpra.asc
echo "deb [signed-by=/usr/share/keyrings/xpra.asc] https://xpra.org/ $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/xpra.list
sudo apt update && sudo apt install -y xpra kitty
```
# Create a service user (optional)
sudo useradd -m -s /bin/bash rdp
### Install
# Install systemd service
```bash
sudo cp systemd/kitty-web.service /etc/systemd/system/
sudo cp config/kitty.conf /home/YOUR_USER/.config/kitty/kitty.conf
sudo cp config/xpra-html5-settings.txt /usr/share/xpra/www/default-settings.txt
sudo systemctl daemon-reload
sudo systemctl enable --now kitty-web
```
### Manual Start
```bash
xpra start --bind-ws=0.0.0.0:7681 \
--start="kitty" \
--html=on \
--sharing=yes \
--no-daemon
```
Then open `http://YOUR_IP:7681` in a browser.
### Configuration
Edit `systemd/kitty-web.service` to customize:
| Option | Description |
|--------|-------------|
| `--bind-ws=HOST:PORT` | WebSocket listen address |
| `--start="CMD"` | Application to launch (default: `kitty`) |
| `--sharing=yes` | Allow multiple clients to connect |
| `--readonly=no` | Allow keyboard/mouse input |
Open `http://YOUR_IP:7681` in a browser.
### Reverse Proxy (Caddy)
```
terminal.example.com {
# Add your auth here (OAuth2 Proxy, Authentik, etc.)
kitty.example.com {
# your auth here
reverse_proxy YOUR_SERVER:7681
}
```
WebSocket support is required. Caddy handles this automatically. See `caddy-example.conf` for a full example with authentication options.
## Mobile Tips
### Kitty Config
Place your kitty config at `~/.config/kitty/kitty.conf` for the service user. See `config/kitty.conf` for a dark-themed example.
## Optional: Push Notifications
The `notify-server.py` and `kitty-notify` command provide a simple browser notification system:
```bash
# Install
sudo cp notify-server.py /opt/kitty-web/
sudo cp kitty-notify /usr/local/bin/
sudo cp systemd/kitty-notify.service /etc/systemd/system/
sudo systemctl enable --now kitty-notify
# Usage
kitty-notify "Build complete!"
echo "done" | kitty-notify
```
Requires proxying `/api/*` to port 7682 — see `caddy-example.conf`.
- **Floating menu** (circle in corner) — on-screen keyboard, fullscreen, clipboard, scaling
- **Pinch to zoom** works
- **Kitty tab bar** at bottom — touch to switch tabs
- DPI set to 144 for readable text on mobile
## Files
```
kitty-web/
README.md
LICENSE
install.sh # Automated installer
caddy-example.conf # Reverse proxy config template
notify-server.py # Push notification HTTP API (optional)
kitty-notify # CLI notification command (optional)
manifest.json # PWA manifest
icon-192.png # PWA icon
icon-512.png # PWA icon
config/
tmux.conf # tmux config (for optional tmux-inside-kitty usage)
kitty.conf # Kitty terminal config (dark theme)
kitty.conf # Kitty config (dark theme, 16pt, mobile-optimized)
xpra-html5-settings.txt # Xpra HTML5 client defaults
systemd/
kitty-web.service # Xpra + kitty systemd unit
kitty-notify.service # Notification API systemd unit
caddy-example.conf # Reverse proxy template
```
## See Also
- [sethmux](https://git.sethpc.xyz/Seth/sethmux) — lightweight web terminal (ttyd + tmux) at `mux.sethpc.xyz`
## License
MIT
-53
View File
@@ -1,53 +0,0 @@
# Sethian tmux config
# Remap prefix to Ctrl-a (easier on mobile)
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# Easy tab management
bind -n M-t new-window
bind -n M-w kill-window
bind -n M-1 select-window -t 0
bind -n M-2 select-window -t 1
bind -n M-3 select-window -t 2
bind -n M-4 select-window -t 3
bind -n M-5 select-window -t 4
bind -n M-Left previous-window
bind -n M-Right next-window
# Mouse support (critical for mobile/touch)
set -g mouse on
# Scrollback
set -g history-limit 50000
# Start numbering at 1
set -g base-index 1
setw -g pane-base-index 1
# Renumber windows on close
set -g renumber-windows on
# Status bar - Sethian dark + orange
set -g status-style "bg=#1a1a1a,fg=#e0e0e0"
set -g status-left "#[bg=#D35400,fg=#0a0a0a,bold] #S #[bg=#1a1a1a] "
set -g status-right "#[fg=#D35400]%H:%M #[fg=#666666]| #[fg=#e0e0e0]%b %d"
set -g status-left-length 20
set -g status-right-length 30
# Window status
setw -g window-status-format " #[fg=#888888]#I:#W "
setw -g window-status-current-format "#[bg=#D35400,fg=#0a0a0a,bold] #I:#W "
setw -g window-status-separator ""
# Pane borders
set -g pane-border-style "fg=#333333"
set -g pane-active-border-style "fg=#D35400"
# Terminal settings
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-256color:Tc"
# Reduce escape delay
set -sg escape-time 10
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

-92
View File
@@ -1,92 +0,0 @@
#!/bin/bash
# kitty-web installer
# Mobile-first web terminal with tmux, ttyd, and push notifications
set -e
TTYD_VERSION="1.7.7"
SERVICE_USER="${KITTY_USER:-rdp}"
TTYD_PORT="${TTYD_PORT:-7681}"
NOTIFY_PORT="${NOTIFY_PORT:-7682}"
FONT_SIZE="${FONT_SIZE:-18}"
echo "=== kitty-web installer ==="
echo "Service user: $SERVICE_USER"
echo "ttyd port: $TTYD_PORT"
echo "Notify port: $NOTIFY_PORT"
# Check root
if [ "$EUID" -ne 0 ]; then
echo "Run as root"
exit 1
fi
# Install dependencies
echo "[1/6] Installing dependencies..."
apt install -y tmux 2>/dev/null || true
# Install ttyd
if ! command -v ttyd &>/dev/null; then
echo "[2/6] Installing ttyd $TTYD_VERSION..."
curl -sL "https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64" -o /usr/local/bin/ttyd
chmod +x /usr/local/bin/ttyd
else
echo "[2/6] ttyd already installed"
fi
# Create user if needed
if ! id "$SERVICE_USER" &>/dev/null; then
echo "[3/6] Creating user $SERVICE_USER..."
useradd -m -s /bin/bash "$SERVICE_USER"
else
echo "[3/6] User $SERVICE_USER exists"
fi
# Deploy files
echo "[4/6] Deploying files..."
mkdir -p /opt/kitty-web/static
# Build custom index (inject toolbar into ttyd's default page)
TMPINDEX=$(mktemp)
ttyd --port 0 /bin/true &
TTYD_PID=$!
sleep 1
# Can't easily grab default page without a running instance, so we'll
# add toolbar.js loading to the page at runtime via the notify server
kill $TTYD_PID 2>/dev/null || true
cp "$(dirname "$0")/toolbar.js" /opt/kitty-web/static/toolbar.js
cp "$(dirname "$0")/manifest.json" /opt/kitty-web/static/manifest.json
cp "$(dirname "$0")/icon-192.png" /opt/kitty-web/static/icon-192.png 2>/dev/null || true
cp "$(dirname "$0")/icon-512.png" /opt/kitty-web/static/icon-512.png 2>/dev/null || true
cp "$(dirname "$0")/notify-server.py" /opt/kitty-web/notify-server.py
chmod +x /opt/kitty-web/notify-server.py
# Install kitty-notify command
cp "$(dirname "$0")/kitty-notify" /usr/local/bin/kitty-notify
chmod +x /usr/local/bin/kitty-notify
# Install tmux config
sudo -u "$SERVICE_USER" cp "$(dirname "$0")/config/tmux.conf" "$(eval echo ~$SERVICE_USER)/.tmux.conf" 2>/dev/null || true
# Install systemd services
echo "[5/6] Installing services..."
sed "s/User=rdp/User=$SERVICE_USER/g; s/Group=rdp/Group=$SERVICE_USER/g; s/--port 7681/--port $TTYD_PORT/g; s/fontSize=18/fontSize=$FONT_SIZE/g" \
"$(dirname "$0")/systemd/ttyd-kitty.service" > /etc/systemd/system/ttyd-kitty.service
sed "s/User=rdp/User=$SERVICE_USER/g" \
"$(dirname "$0")/systemd/kitty-notify.service" > /etc/systemd/system/kitty-notify.service
systemctl daemon-reload
systemctl enable --now ttyd-kitty kitty-notify
echo "[6/6] Verifying..."
sleep 2
systemctl is-active ttyd-kitty && echo " ttyd: OK (port $TTYD_PORT)"
systemctl is-active kitty-notify && echo " notify: OK (port $NOTIFY_PORT)"
echo ""
echo "=== kitty-web is running ==="
echo "Direct access: http://$(hostname -I | awk '{print $1}'):$TTYD_PORT"
echo ""
echo "For reverse proxy setup, see README.md"
echo "Send notifications: kitty-notify 'Hello from the terminal!'"
-8
View File
@@ -1,8 +0,0 @@
#!/bin/bash
# Send a push notification to kitty.sethpc.xyz
# Usage: kitty-notify "Build complete!" or echo "done" | kitty-notify
if [ -n "$1" ]; then
echo "$*" > /tmp/kitty-notify
else
cat > /tmp/kitty-notify
fi
-12
View File
@@ -1,12 +0,0 @@
{
"name": "sethpc terminal",
"short_name": "kitty",
"start_url": "/",
"display": "standalone",
"background_color": "#0a0a0a",
"theme_color": "#D35400",
"icons": [
{"src": "/icon-192.png", "sizes": "192x192", "type": "image/png"},
{"src": "/icon-512.png", "sizes": "512x512", "type": "image/png"}
]
}
-37
View File
@@ -1,37 +0,0 @@
#!/usr/bin/env python3
"""Tiny HTTP server for terminal notifications. Serves /api/notifications."""
import http.server
import json
import os
import time
NOTIFY_FILE = "/tmp/kitty-notify"
PORT = 7682
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/api/notifications":
msg = ""
if os.path.exists(NOTIFY_FILE):
try:
mtime = os.path.getmtime(NOTIFY_FILE)
if time.time() - mtime < 30: # only show notifications < 30s old
with open(NOTIFY_FILE) as f:
msg = f.read().strip()
except:
pass
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(json.dumps({"message": msg}).encode())
else:
self.send_response(404)
self.end_headers()
def log_message(self, format, *args):
pass # quiet
if __name__ == "__main__":
server = http.server.HTTPServer(("0.0.0.0", PORT), Handler)
server.serve_forever()
-13
View File
@@ -1,13 +0,0 @@
[Unit]
Description=Kitty terminal notification API
After=network.target
[Service]
Type=simple
User=rdp
ExecStart=/usr/bin/python3 /opt/ttyd/notify-server.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target