diff --git a/README.md b/README.md index 7673c36..cca7297 100644 --- a/README.md +++ b/README.md @@ -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 + kitty-web.service # Xpra + kitty 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 diff --git a/config/tmux.conf b/config/tmux.conf deleted file mode 100644 index 9177d2e..0000000 --- a/config/tmux.conf +++ /dev/null @@ -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 diff --git a/icon-192.png b/icon-192.png deleted file mode 100644 index 7cb0875..0000000 Binary files a/icon-192.png and /dev/null differ diff --git a/icon-512.png b/icon-512.png deleted file mode 100644 index ebffc7e..0000000 Binary files a/icon-512.png and /dev/null differ diff --git a/install.sh b/install.sh deleted file mode 100755 index e9ed777..0000000 --- a/install.sh +++ /dev/null @@ -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!'" diff --git a/kitty-notify b/kitty-notify deleted file mode 100755 index efdd163..0000000 --- a/kitty-notify +++ /dev/null @@ -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 diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 23b4955..0000000 --- a/manifest.json +++ /dev/null @@ -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"} - ] -} diff --git a/notify-server.py b/notify-server.py deleted file mode 100755 index a4a7292..0000000 --- a/notify-server.py +++ /dev/null @@ -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() diff --git a/systemd/kitty-notify.service b/systemd/kitty-notify.service deleted file mode 100644 index 638fee7..0000000 --- a/systemd/kitty-notify.service +++ /dev/null @@ -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