diff --git a/README.md b/README.md index e780f78..7673c36 100644 --- a/README.md +++ b/README.md @@ -1,137 +1,129 @@ # kitty-web -Mobile-first web terminal powered by [ttyd](https://github.com/tsl0922/ttyd) + [tmux](https://github.com/tmux/tmux). One persistent tmux session, multiple tabs, accessible from any browser. Designed for phones and tablets. +Run the real [kitty terminal](https://sw.kovidgoyal.net/kitty/) in your browser. Mobile-friendly, GPU-accelerated, with full tab and split support. + +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. ## Features -- **Single persistent session** — one tmux session shared across all connected clients -- **Mobile touch toolbar** — on-screen buttons for common shortcuts (new tab, ^C, ^D, Esc, arrows, split panes, etc.) -- **Text selection mode** — tap `Sel` to enter selection mode, long-press to select and copy text, tap `Done` to resume typing -- **Push notifications** — send browser notifications from any terminal command -- **PWA installable** — add to home screen for an app-like experience -- **Dark theme** — styled for dark-mode terminals with orange accents +- **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 ## Architecture ``` -Browser -> Caddy (HTTPS + Auth) -> ttyd (port 7681) -> tmux session - -> notify-server (port 7682) -> /api/notifications +Browser -> Caddy (HTTPS + Auth) -> Xpra HTML5 (port 7681) -> kitty ``` -- **ttyd** serves the terminal UI with a custom index page that loads `toolbar.js` -- **toolbar.js** injects the mobile toolbar (shortcut buttons, selection mode) into the page at runtime -- **notify-server.py** provides a simple HTTP endpoint for push notifications -- **kitty-notify** is a CLI command to trigger notifications from scripts +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. ## Quick Start +### Prerequisites + +- Debian/Ubuntu (tested on Debian 13 Trixie) +- kitty (`apt install kitty`) +- Xpra (`https://xpra.org/` — add their repo for latest version) + +### Install + ```bash -sudo ./install.sh +# Add Xpra repo (Debian example) +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 systemd service +sudo cp systemd/kitty-web.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now kitty-web ``` -This installs ttyd, tmux, systemd services, and the notification system. The terminal will be available at `http://YOUR_IP:7681`. +### 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 -Set environment variables before running the installer: +Edit `systemd/kitty-web.service` to customize: -| Variable | Default | Description | -|----------|---------|-------------| -| `KITTY_USER` | `rdp` | System user that owns the tmux session | -| `TTYD_PORT` | `7681` | Port for ttyd web terminal | -| `NOTIFY_PORT` | `7682` | Port for notification API | -| `FONT_SIZE` | `18` | Terminal font size (optimized for mobile) | +| 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 | -### Reverse Proxy +### Reverse Proxy (Caddy) -See `caddy-example.conf` for a complete Caddy v2 configuration with authentication. The setup supports OAuth2 Proxy, Authentik, or Authelia for access control. - -Key requirements for the reverse proxy: -- WebSocket support (ttyd uses WebSockets for terminal I/O) -- Serve `/toolbar.js`, `/manifest.json`, `/icon-*.png` as static files -- Proxy `/api/*` to the notification server (port 7682) -- Proxy everything else to ttyd (port 7681) - -## Mobile Toolbar - -The toolbar appears automatically on screens narrower than 900px. Buttons: - -| Button | Action | tmux Key | -|--------|--------|----------| -| **+Tab** | New tab | `Ctrl-A c` | -| **Next** | Next tab | `Ctrl-A n` | -| **Prev** | Previous tab | `Ctrl-A p` | -| **^C** | Interrupt | `Ctrl-C` | -| **^D** | EOF / logout | `Ctrl-D` | -| **Clr** | Clear screen | `Ctrl-L` | -| **Esc** | Escape key | `Escape` | -| **Tab** | Tab completion | `Tab` | -| **Up/Down** | History navigation | Arrow keys | -| **Sel** | Toggle text selection mode | — | -| **Spl** | Split pane vertically | `Ctrl-A %` | -| **Pane** | Cycle between panes | `Ctrl-A o` | -| **Kill** | Kill current pane/tab | `Ctrl-A x` | - -## Push Notifications - -Send notifications from any terminal session: - -```bash -# Direct message -kitty-notify "Build complete!" - -# Pipe output -echo "Deploy finished" | kitty-notify - -# Use in scripts -make build && kitty-notify "Build succeeded" || kitty-notify "Build FAILED" +``` +terminal.example.com { + # Add your auth here (OAuth2 Proxy, Authentik, etc.) + reverse_proxy YOUR_SERVER:7681 +} ``` -Notifications appear as browser push notifications on mobile. Tap the bell icon in the terminal UI to enable them. Notifications expire after 30 seconds. +WebSocket support is required. Caddy handles this automatically. See `caddy-example.conf` for a full example with authentication options. -## tmux Keybindings +### Kitty Config -The included tmux config uses `Ctrl-A` as the prefix (easier on mobile than the default `Ctrl-B`): +Place your kitty config at `~/.config/kitty/kitty.conf` for the service user. See `config/kitty.conf` for a dark-themed example. -| Key | Action | -|-----|--------| -| `Ctrl-A c` | New window/tab | -| `Ctrl-A n` / `Ctrl-A p` | Next / previous window | -| `Ctrl-A %` / `Ctrl-A "` | Split vertical / horizontal | -| `Ctrl-A o` | Cycle panes | -| `Ctrl-A x` | Kill pane | -| `Alt-1` through `Alt-5` | Jump to window 1-5 | -| `Alt-Left` / `Alt-Right` | Previous / next window | -| `Alt-t` | New window | -| Mouse scroll | Scroll through history | +## 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`. ## Files ``` kitty-web/ - toolbar.js # Mobile toolbar (injected into ttyd page) - notify-server.py # Push notification HTTP API - kitty-notify # CLI notification command - manifest.json # PWA manifest - icon-192.png # PWA icon (192x192) - icon-512.png # PWA icon (512x512) - install.sh # Installer script - caddy-example.conf # Reverse proxy configuration example + 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 configuration (dark theme, mobile-friendly) + tmux.conf # tmux config (for optional tmux-inside-kitty usage) + kitty.conf # Kitty terminal config (dark theme) systemd/ - ttyd-kitty.service # ttyd systemd unit + kitty-web.service # Xpra + kitty systemd unit kitty-notify.service # Notification API systemd unit ``` -## Requirements - -- Linux (Debian/Ubuntu tested) -- tmux -- Python 3 (for notification server) -- [ttyd](https://github.com/tsl0922/ttyd) (installed automatically) -- Caddy, nginx, or another reverse proxy (for HTTPS and auth) - ## License MIT diff --git a/config/kitty.conf b/config/kitty.conf new file mode 100644 index 0000000..ee016db --- /dev/null +++ b/config/kitty.conf @@ -0,0 +1,52 @@ +# Sethian Terminal Theme +font_family JetBrains Mono +font_size 11.0 +bold_font auto +italic_font auto + +# Sethian colors - dark with orange accents +foreground #e0e0e0 +background #0a0a0a +background_opacity 0.95 + +cursor #D35400 +cursor_shape beam + +selection_foreground #0a0a0a +selection_background #D35400 + +# Tab bar +active_tab_foreground #0a0a0a +active_tab_background #D35400 +inactive_tab_foreground #999999 +inactive_tab_background #1a1a1a +tab_bar_style powerline + +# Normal colors +color0 #1a1a1a +color1 #cc3333 +color2 #4e9a06 +color3 #D35400 +color4 #3465a4 +color5 #75507b +color6 #06989a +color7 #d3d7cf + +# Bright colors +color8 #555753 +color9 #ef2929 +color10 #8ae234 +color11 #fce94f +color12 #729fcf +color13 #ad7fa8 +color14 #34e2e2 +color15 #eeeeec + +# Window +window_padding_width 4 +confirm_os_window_close 0 +enable_audio_bell no + +# URL handling +url_color #D35400 +url_style curly diff --git a/systemd/kitty-web.service b/systemd/kitty-web.service new file mode 100644 index 0000000..0365fe4 --- /dev/null +++ b/systemd/kitty-web.service @@ -0,0 +1,30 @@ +[Unit] +Description=Kitty terminal via Xpra HTML5 (kitty.sethpc.xyz) +After=network.target + +[Service] +Type=simple +User=rdp +Group=rdp +WorkingDirectory=/home/rdp +Environment=XDG_RUNTIME_DIR=/run/user/1002 + +ExecStart=/usr/bin/xpra start \ + --bind-ws=0.0.0.0:7681 \ + --start="kitty" \ + --html=on \ + --no-notifications \ + --no-pulseaudio \ + --no-mdns \ + --no-printing \ + --sharing=yes \ + --readonly=no \ + --no-daemon + +ExecStop=/usr/bin/xpra stop :0 + +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/systemd/ttyd-kitty.service b/systemd/ttyd-kitty.service deleted file mode 100644 index 34c16d0..0000000 --- a/systemd/ttyd-kitty.service +++ /dev/null @@ -1,29 +0,0 @@ -[Unit] -Description=ttyd web terminal (kitty.sethpc.xyz) -After=network.target - -[Service] -Type=simple -User=rdp -Group=rdp - -ExecStartPre=/bin/bash -c "/usr/bin/tmux kill-session -t kitty 2>/dev/null; sleep 0.5; /usr/bin/tmux new-session -d -s kitty -x 120 -y 40" -ExecStart=/usr/local/bin/ttyd \ - --port 7681 \ - --interface 0.0.0.0 \ - --index /opt/ttyd/index-with-toolbar.html \ - --writable \ - --check-origin \ - --max-clients 5 \ - --ping-interval 30 \ - --client-option titleFixed=sethpc.xyz \ - --client-option fontSize=18 \ - --client-option fontFamily=monospace \ - --client-option enableSixel=true \ - /usr/bin/tmux attach-session -t kitty - -Restart=always -RestartSec=3 - -[Install] -WantedBy=multi-user.target diff --git a/toolbar.js b/toolbar.js deleted file mode 100644 index ed90a7f..0000000 --- a/toolbar.js +++ /dev/null @@ -1,81 +0,0 @@ -(function(){ - if(window._toolbar) return; - window._toolbar=true; - - var css=document.createElement('style'); - css.textContent=` - #mb{display:none;position:fixed;bottom:0;left:0;right:0;background:#111; - border-top:2px solid #D35400;padding:5px 4px;gap:4px;justify-content:center; - flex-wrap:wrap;z-index:99999} - #mb button{background:#222;color:#ccc;border:1px solid #444;border-radius:5px; - padding:10px 12px;font-size:14px;font-family:ui-monospace,monospace; - cursor:pointer;touch-action:manipulation;-webkit-tap-highlight-color:transparent; - min-width:42px;text-align:center;user-select:none} - #mb button:active{background:#D35400;color:#0a0a0a;border-color:#D35400} - #mb button.hi{border-color:#D35400;color:#D35400} - #mb button.on{background:#D35400;color:#0a0a0a;border-color:#D35400} - #mb .sep{width:1px;background:#333;margin:0 2px;align-self:stretch} - @media(max-width:900px){#mb{display:flex}} - body.selmode .xterm-screen{pointer-events:none!important; - user-select:text!important;-webkit-user-select:text!important} - `; - document.head.appendChild(css); - - var bar=document.createElement('div'); - bar.id='mb'; - bar.innerHTML= - ''+ - ''+ - ''+ - '
'+ - ''+ - ''+ - ''+ - '
'+ - ''+ - ''+ - ''+ - ''+ - '
'+ - ''+ - ''+ - ''+ - ''; - document.body.appendChild(bar); - - function send(k){ - if(document.body.classList.contains('selmode')) toggleSel(); - k=k.replace(/\\x([0-9a-f]{2})/gi,function(_,h){return String.fromCharCode(parseInt(h,16));}); - k=k.replace(/\\t/g,'\t'); - if(window.term){window.term.input(k);window.term.focus();} - } - - function toggleSel(){ - var b=document.getElementById('selbtn'); - document.body.classList.toggle('selmode'); - if(document.body.classList.contains('selmode')){ - b.classList.add('on');b.textContent='Done'; - } else { - b.classList.remove('on');b.textContent='Sel'; - window.getSelection().removeAllRanges(); - if(window.term) window.term.focus(); - } - } - - bar.addEventListener('click',function(e){ - var btn=e.target.closest('button'); - if(!btn) return; - if(btn.dataset.sel) return toggleSel(); - if(btn.dataset.k) send(btn.dataset.k); - }); - - // Shrink terminal for toolbar on mobile - var obs=new MutationObserver(function(){ - var el=document.querySelector('.xterm'); - if(el && window.innerWidth<=900){ - el.style.height='calc(100vh - 54px)'; - if(window.term && window.term.fit) window.term.fit(); - } - }); - obs.observe(document.body,{childList:true,subtree:true}); -})();