From 51e8932dde6218930fe6a0bba1b749f48fd1b04d Mon Sep 17 00:00:00 2001 From: Mortdecai Date: Thu, 26 Mar 2026 19:00:07 -0400 Subject: [PATCH] refactor: strip tmux/ttyd artifacts, kitty-web is now purely xpra+kitty tmux web terminal moved to sethmux (git.sethpc.xyz/Seth/sethmux) --- README.md | 112 ++++++++++------------------------- config/tmux.conf | 53 ----------------- icon-192.png | Bin 3809 -> 0 bytes icon-512.png | Bin 10782 -> 0 bytes install.sh | 92 ---------------------------- kitty-notify | 8 --- manifest.json | 12 ---- notify-server.py | 37 ------------ systemd/kitty-notify.service | 13 ---- 9 files changed, 32 insertions(+), 295 deletions(-) delete mode 100644 config/tmux.conf delete mode 100644 icon-192.png delete mode 100644 icon-512.png delete mode 100755 install.sh delete mode 100755 kitty-notify delete mode 100644 manifest.json delete mode 100755 notify-server.py delete mode 100644 systemd/kitty-notify.service 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 7cb08756db3ede479f7c9490b4233a2b434f5a2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3809 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 Kex;uCqkjQ{s_|U_ diff --git a/icon-512.png b/icon-512.png deleted file mode 100644 index ebffc7ee1b537343580a1f098a9c76e927d55b52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10782 zcmeHtcT^MG-u57#1Beul1r-4i6_pYMDbm6fJ*WuS5CMUMG^qjUgb+MZR1_2hqz067 z=u$&~fCd5S2ndAGkzPVgfQ01TgZI1NcfPgWfA3m%tvib~Gb_xVz4veb%JV#Xp5N5h z;oEa`4+KGc*REc^4MDrW?Jh_VzDT{mErQE&tE;#5Ajt1D1O)~`&=&Y9a2kR<sx~rw@Q%xIC`uUE!MLJi7O|s7-QB69k=MUAz2` zyFUF3gJ_S~^(RxyR4Sbrc=C)g;p9Hl+uLa|hPN(PVRW+--gv6WxxC{I34lGdwR2|8+I`oVoT{ZIJ>VWdiqOo>6Zya;$B!#mgw~ z)BY6}p&Bjp+ASZ+Q4Xz=34msZb4j>Mm%cp!7%;!?>@0#!UnglGtgAck_sS#juHTqF71pC6Ty)ksHSvXX2s$QR&GD@NTg2T& z6BAcwwxv5AwV7!V&bFZh`BK{ZhP=jqL380J8r~WhWW>4{pd~;Rh(tjrR)UQmCN)$c%bXWo8K(4)>R#O}+g3bvdAokN%v3Zz8t%K7}qfeWTt5`v{hHkJs~?S7L1z^%%nkp+IDt0^~0(VamuyX!_~aw71x!uxkV!q4~j$BoRsK zfcJXb0%t$3M3DtQ>g~{Vc=YT9(?FF!DlFn392LzrHWk^EI$1Xx#%nqE4kH<)8Ij!M zXX4^z<*w`OGULeb8jQrKz-C(h4LWvNCvV=({Hxi)kRbo^b9;Tyi3QZSG<3}Gd(|pa z+Su7Jl}n9HD72!o5)_&{&G$fpym-j%YLZGW{o`0Zsym>3id#vV8jwy-V1$l^6bENE_xzoGd=h`{psJ{`hiJrrWp)DyD43^(K1&w1h zf0KMUgf*?XH7FEwt_yVJ>f^rm(PjSn^C_XXE%og64zq61y({c`J&vhw-K}XYjw;$7 zmR@YUaUCrJeH#5NqjgLva$np&%Y{BUxdX*kq5S>?jVd~wf#4R99xAW4aJky>w9e7N ztNUa_Ch-82vig`;Ftkvj=9{xo`5IzKeKC(Y`wLMM-CO;n=CFKz05!L!fBU%xLy~VJ zN3`JJupU?$t*H~x^zHCU_A`d=oy=TTx5Yg6(P+GT!0^58r;hq4XS7G>yV>)b_8Q^M zvS$WkX}?2(7YM&Rla*3XR#0}jD6yBju03hruWOD1mG~c1!BmYx_u3_E%=m0f|AVe( z1$_IB)9~O%I+ppMeKTZaFLJ=++iR(k-)$+yATcRB4<)ns@*^@ZGo4@@6sG`7MFYjF_;Vw zjI;54%D#&O5gZz8{jz6r_b5vft!tYb39nl7N(t#Xwy1@KfrI)2uRdcHp-+~L3eXb! zZqMt)L$+++!~UL@G+gLs%pS|G4dRsb?@Fxq>+5Nj-0#*d z7iEa6R!Rp|(3af0{IKX$tuoU}-jmL=iB?$c9Qp-XYViFP(u{eNhv^j(Bc>1AO_t!1 zO5P*^dNnP{QgXNkHT(aE$S3yXq2vDw5kiUTflGRSRhJ8vg_FH6O&3pB#IzDBg%eB> zjWQTaUH(BbqYHTZ7hUMnKRmjdM5lrWek?slOx1$3`Q{SCQ!T_c;?Wt@S>1cGaa942 z7biu~^x;I~7}{;<_#qdLKecDSSQ0}Idh4l8b~P*_?Qhv$@}h=Q^O5WQ7)cz_XJm|7 zX6Ya;~H))I0G(2gxAq3A-r>EC{2WHOj zBqR}>bb+Y?^R*!@$Q+)Pg38B2>%*F`{=&OLIl+}DCe{NT&}_irpQCD zH9Y>u1{}mmu|iL-dK`rsIS&xN(K10{3nK*S$?Y>XBiBm0DM($x%|Q_=+oy@&~XPH?@lnM)RK> z(-acA_r2SwSI9O=Ep;}Fxee#&WV=fn#}iJ^C%ryGzTc~gw7-9$#wNgJloCcyj7y54 z$wDKwzd=DQ19JG1iEhvBeCf8_&8gXDUiY@mdlNF7q3Bp|+`;-xVtCtPbQr$oZJzTZ zm#kJrjpd<`q{i=+249C0;$q>~MC^x{`x>9MJnQWLhFHgD`CMx?$eCR&*7P1t;pLqR z_aRbZNuKrvA66Z(DN9-#V^I-KekYIgEa#8e8IT`er+psN7~N zD>PP#H-}c$ZV3BcG@&>_*BeK4?pO~ld%pf1Db=FtSQqHdE9p@wVY=@SJTFH*syDTX z-|4}iNrE(n$u-BAu$_NUjrGkqzhDL`7sn@#u3~IHb|$0UOlA5#sRF!bebe{@cA%zW z!w#PDN2LW%kKZ?!>y|C&?_iYM^_$d^Px#o*tLYY@4)nb3u@1>3@ zz5=sExB8M?#=3g}K+F8>3?a20MU6PPZ&rhTv zDwSWOsO-(CqWQ-4!dWm>ibJdESH)<>gBBO@Mur6>COn^DPWk<%{tY$v#+6rlD@S~Fk(aN!$Y47-@|vG*2NB^8_XI%!Sk3p1*z=aJLC3cp)Sl} zHfL_NSe0K~#NV;qRM)+0=!)Ii{~P)JZp>*l6NiX>Q%4r6YJUl$B^-J$UQYWw%t zsj2SVyo&d7b~o?%-kFIm@%~15oAEXyh*d-MYTlZ+5;54PzWFsHG|G}OBpq?LEP5&wHUOUhkbEkK`vPh+P{PP&RvMq=ivmwmA!#J9% zYHSR}Yd9vZb023|zT@CK=EHG3t<0t&jmQh}AL7tD@DuU;Uem{}8#}ds?q|eUPFxB0 z)L=|)g(h^G>twq5J=%M)-J*28X}+a?GNyl^GdPyng78&~p&OlCWNHELA z+gj>+Si8yjkpU8q;<_R1?PyBM9IvxYLR9PMB5)4l60@8454t?6z|<}@28&!@qqF%XAFFmQCGlkHGqKH8~yeF62WhbQd(IQE8^{55s=9l)lj&ZmP z(%FuA%`#2|=XPiESpdER1wh?n-)Zl4w>W1;-{j>oyMNzkwQF{ZnfuR-Ab&#PL@w~3 zq&g`f6!xqsrDNcBf}`IUkxwlu#~b&$Mp*#?)_AFb2ioBFi}g-0Jc?v}Zh8|8TL$(L zQN^~F))qcfV3jQz(Rkx}dC~aCZ{bgMa2htvXUSY)^q_Hth71sGAcZWQ7bz`j@J$~e z?9Wba8n(D&VQBGgJuZx4{@}rPHx>K-xvRr5Sktm>TFHqTq0KvOWX2Vf!knx@M>2wFLX>F8249I{Wlf|%R)}(Zhh%7IEk#VqEj=X z%~i%5wsXQ744C$wn1G&g>fWYUW{AIXyz zGXVy)&Q5K+yu+?84d^i5;(C4&m6F#oltd?f?@YLzG#Ko@1%U%%U;USRExR}gOLOsh z;zEFpm37X$g>h+`A2tD*Y=x9q9H5Yr;p*>V>}7oa4b2?R7EncQ8SkDPd1ZA`rM2eZ zhT4$Vyc!nmhS9RZ7RH-@q@wjOh8R|OSLHg+xa!=IH%IpMWPnap4(x_5TmM9$JCrbW zv_Ho2@wi{XVp+w7twamMS1kpLDsIznHMh48P@8`G_7Jd-&o1bF*?8Kvq)jsBOx3}P zb-y!Jo;LUntbWABbQ+J&J&`xql<`wGl4b)_VOKK0acT*PLxEVU=57q|XRPQD_h9wf z>eBBKE~!P{o1J4)ijFz5h?cnf7Td+lTuTFb{QcQj7@!%v(L+NWWM^j2P}%&uEwp)M zCIu74E44E56rn+FjJEM+`7~FY9o(v^e%Sk>W!oOL8=!3t$ln^}Od-msVOX05$-~a= z6Us8f#sTXVO33x=WCGoP^}9Vm$V7^M*H%^+Fq)+X!Y|w^5NjfbT563;78(*B#>`iZ z_XG>-4}4?Plk&rbLmhhVS=@T3;OawO>f8cIP;1_w6FPRFhDsdIw;mB_yN~{zPN|lA zIP-jBt^o5R?v5XGHWs@)IXf`Duu5y?Er1Ei!;?_pQ|emB;d4Kc+-4gdOTS0|junl+ zK0#!y_6-M=HS~%GEG+a!dJi)g+d4s9ouYdnGn{E);DG~Fo(8*cjI0yiHJ`;7dE!~P zQuMmeb;Qd}2e#u2P7nyaQ+BTyZ7FFTx82Ma$*zw5XpWRQK}aj}=CpBzg8M9rq~?8k zRGHv?Nj{xHEsBj1Rh^sVK+0BiFaQ2$p*9!4j5YdLtTZZjtX3~oi`mRr-{{V`@E26?4 z1$n$Q|D3igI2lb|f@3T?n>9Hn;Z@3JR$)~tibghWK^s8)R%uCu2{8BeP!U8~WT6JC%5 zuX?l_>Ue&42lN(=uUkgd-^In@XpMLL%*>A-QVJEERN>=>G&MP(v<#}}V1X20oE(ft zkzW24$dv?)X_yts-O{~p|0N%lfh52XryX`fuFux|HR$P`p5szd*tdt6c^H z+jfT}?IIeF3G44+9|-3Y4|09nuf~z$xF0aAZ{y|je|BPU#D^mlq<#Af3WiMHY0_Ri zncI_7NsJ_(l;#tEus6cS z1Q)pv+EaH!yP>6T8bWOXXnhu?8f*F&RVCb=6qKF0BFxBLtX9qCVGEwCwGj`p>q_Q< zn8V=lRDTDr0&-nZxB=a<=Pp3ud`x|EOXm+d*4LBU(xYFPBT(QpkZH#HjL`bBL+UeI zvw6WY`!nyMnhlU;7VUNEM@9*PBrwHMd&F*tbzv98%{eSpnYA&?$fO$N4aMMCS$Rae z*e22%9WgCrn+B5yIgns74b13Px{`Y3=_@fz2@&rl+(7a0@S)IenV_5?%ddk`ppJkYVDAnUzd zjI~9`Xn6F{xdsdmce`=1`waFTGw~^7oNde+P3;ivul-Z*#LKH_N$B+<4k)xRyo+u? z_3CaJGZcxK4XDmu(Fur=rNA+vPE z#j`N=8odm7?c-s04b&D{5{n2uK5_qx4~|qP(lh}pl?1$* zla<}zSm*5svX1$?F1KS-&ujgL?w?Hh1*8Gl?^+(qKGFOb0G2`&1HuSVBbz%obJB);bq zud;eM-H6Nsphe2ue#jMq`O7U6|DG2dQ-}1Y%j2fz7dTO8O4e0mjRhBV(IPVxPZA4V zTO~O}`KUnfyJhBR<}&(!lpo%}!vJA@{V4?=^{l-`2~*ausP+Tq-LNnO%;e(Ox{Jl0 z-gIL4+Cl1B5wY;?1%oo?$;zs61(u{3dF`rv?)H;0&Nu{sT7iAwT{kt|J#dx0&1#y1 zNSIYHnE@aCr1wVKQ{NYF#1>TdM*3EOy58`6o>;W$N5x7+0yz$_#tr&zz#4HIjc=ZY z+36jasZ6sx){HN-z-C@1Y z)(1wuANGMbVke5aUtbWxFW-naA?C?pzC>#3zCfiB!`f{0S}PUF$dxTbg;H;{F3#ZveKn9*h{S*!z=9Q4lw$5QhiLN z%WI1Xmj>J|N=YT8E8*AjQY+_*i(zWUl?#tful90;Fi5Q!h=zEDUd6s)WZkt>zBw+HO z?_SwO^07r2$k)%Lp1-S({oW&0tRU2if^q!TVhANFjMjFhh~`eef;qiF_IMFucxj0d9P-qKW|>G|DIR+eUle}J2%-i~u!0|js*6FVlU!!Mf@4O3oZRr%`s zmWrPOyqHwUP<%q=!X^!2t&{;XHRP^9?H_+VjCh#UOVGKu-qcQ2!A+%wG5ib5&n=jF z{7y+8rU!p?TW(r!|FJl~@scfosP?|)Wd?Mr6e&RfVsx15^wrMT`WiFx?Tyy7Ik^9b ziqXFo>V%6e+N$;eK^~Zrg*rqJy4Eb|9RGH0Zo>^b(QGQ)mcNp02B{q04RL8jw31uX zE8B;ZrnfGcHn=6)ZQE+;jcyDS%@=(<0c#Mgd6a_2aMQc6(&OX5veigP@HUI7k#&*Oa=;#LjMIzYEaA9D;6lg zSl<^CCL$kn{a?lC*&Aqt(<8AOdmR^mdExEpUx96tF~t86TU5-al)DB1Vdf>R?Q1(X z9w@!zQn1ns5@I?aunSGrwhg?5PIE4(5SEK1`R6opKjw@dZ;LNAG&(T3hP$Jb*_{k8 zNax$mw;kB;*x`Ojw9)u%4J^2qraAV`2{)>E{*z zb!paTqDRtTX5H2PKJct6FWKQ5^$W6_-CGRzJ3t)l0TKosPc4&_fMb<;cDL4LP2Z+B z0z#qN^P9y8CsU!HyHJSt3bQ`#i(OXAh-*WSfM}o>|94qb6&e|DO4kU~mOS!i)8oD{ zOFU7mzjNr)RMBE?QiNTZ;syMJNJo8AD6 z5T|BaghaRf=IVb;z1rEZGF6^8#%*7p7MrZXHdaNf3qVkXAV33g8WEE^p_G`R>gCGD zDFg*%i0#3$nr3|$Cl|uW-tO*_<$3F`i!z%samM07xM0DIAVKD|0OWCd7o@4>IFa1c zVgq`gd(0~&(tA5r`T^U5Zl1J#iF=enwOAp-`w9N{cR{RIgQhE{B>F#h7~k(e5QGew zMgHN)_WS&Olow`z8(lom`+HTH9{wsc@}088NX*A^{vBlCDsMfUP*-4`t<8U=KyHuYi zKY&?S*WpWtj~2n>ycz|zAMhFUN^cu3rF;;IF`5DWTfu}QYB_X7`vnkIhmWxw%N5`k zCxt>Ia>c&Rl>Qs*A3m5E=p6#uC#bL0zIBTRHd8_1*(7)Y+Vp|TmhwsO-P_CDdiSc~ zq@7|=`DjCABc>Gj@!uK7pi3x(hJ8mD!of$B>99Hiwg-Ns|7;Rd<8J%huP+_ z+m*5utZ_LV>|A(F=DPq%UN1ggcqmW5jhB7_-_d*cIr-r}l)8!SVO1&krc=ubhTsrpRT&XEAWEeCe(Z_BbOO~6-3P`Y(v=PZ% zQwQ5bt3?D-e`kCPQGANNA7J|_E+x%RvsgcR>byO0&DqG12FU_kV&x{Wrd{lf?+Cm6 zOREN0aXyYuHHG%1A)dH55#bIRiiAv%=ka9=i*J%dFO|#g<-v;O>jGbc#6qEGCX`=- z4M#(OLh5tzBEFDNs)YCWUX-UBRY{r|&5#39J=p2@*>(Ix`ZoZ9a{c_~y2xbE`{dNo z_(M(uiHQJWrJi#qGlam)0c1Z5cKLNP-h+cA;15B9zk~v2rga&7X9aIRLH|*=Cu3<4 z#Gh6a@}s14UXts^(Qi$_6_>zVRjz$ab*j)DDq z&G4Vu8+-pOAN!%UcpCh1J#yu7Lc_>+^8 zlT($GD>2JB`nLwoC~JFL-~YWq9VD6o8l3r84<7c;Htrsl&aVG^o5+JmU(g1+c18bk J{-r/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