testzerogpu / README.md
HelloSun's picture
Fix multi-client tmux rendering: smallest window-size, attach only visible tab, reset xterm on reattach
645251f verified
|
Raw History Blame Contribute Delete
8.49 kB

A newer version of the Gradio SDK is available: 6.31.0

Upgrade
metadata
title: testzerogpu
emoji: 🖥️
colorFrom: gray
colorTo: blue
sdk: gradio
sdk_version: 6.25.0
python_version: '3.12'
app_file: app.py
pinned: false
license: apache-2.0
short_description: ZeroGPU web terminal with OpenCode

ZeroGPU web terminal. UI 與 HelloSun/CPU_TERMINAL_00 一致(多頁終端機、檔案瀏覽器、 下載/上傳、複製/貼上、狀態列)。

OpenCode 在啟動時安裝,並重設使用者層級設定為預設值。

檔案

  • app.py — FastAPI + xterm.js 終端機、檔案瀏覽器、/ws session 管理與固定 LXDE 桌面
  • packages.txt — 系統套件(tmux、xvfb、x11vnc、lxde-core、xterm、novnc、 x11-utils,以及 Playwright Chromium 所需的系統函式庫與字型)
  • requirements.txt — Python 相依套件(含 playwright)

終端機分頁(全部共用、全部長存)

  • 每個分頁 = 一個 tmux session:真正共用的 shell 在 tmux 裡,每個瀏覽器分頁只是 一條 tmux client attach。因此不同裝置可以有不同終端機大小,tmux 會針對每個 client 重畫畫面:
    • window 大小由 tmux 依所有目前附掛的 client 自動計算(window-size smallest), 取最小的那台,並自動扣掉狀態列。window 一定放得進每台裝置的可視區域,因此不會有 錯位、殘影或亂碼。較大的裝置會看到 tmux 以 · 填滿、外框標出 window 範圍。
    • 輸入、重連都不會改動 window 大小(舊版每次按鍵都會切換尺寸,多裝置會互相拉扯)。 手機/小視窗離線後,window 會自動放大回剩下那台的尺寸。
    • 每條連線在 URL 帶上自己的 cols/rows,第一次重畫就是正確大小。
    • 前端只有可見的分頁會保持連線;切走的分頁立即 detach,不以錯誤尺寸拖累其他裝置。
    • 每次(重新)附掛,前端都會 term.reset() 清空本機畫面後再接收 tmux 的完整重畫, 重連或重新整理不會把新舊畫面疊在一起。
    • 多位元組字元(中文)用增量 UTF-8 解碼,不會在切斷處產生亂碼。
    • 斷線只會 detach 該 client,opencode 與所有程序都留在 tmux session 裡。
  • 分頁清單由伺服器統一管理:GET /api/tabs 回傳目前所有共用分頁,所有裝置看到的 分頁完全相同。按 + 開的新分頁(POST /api/tabs)會出現在每一台裝置上。
  • 只有明確關閉分頁(DELETE /api/tabs/{id},前端會先確認)才會 kill 該 tmux session,而且所有裝置會一起關閉該分頁;至少會保留一頁。
  • 分頁清單會落地到 ~/.webterm_tabs.json;只要容器還活著,tmux session 也能跨 app 行程重啟接回。Space 重建(整個容器重來)時仍會失去程序,這是平台限制。
  • 分頁數量上限可用 Space 變數 WEBTERM_MAX_TABS 覆寫(預設 12)。
  • 除錯用端點:GET /api/tabs、GET /api/sessions(含 tmux 是否可用、每個 attach 的 尺寸與訂閱數)。

installpi.sh 已從 Space 流程中移除(不再自動執行),檔案本身保留在 repo 作為紀錄, 需要時手動執行。

固定桌面分頁(Xvfb + LXDE + x11vnc + noVNC)

終端機分頁之外,最左側有一個 固定 的「桌面 1(LXDE,共用)」分頁:

  • 伺服器永遠維持 恰好一個 Xvfb(:99)+ 一個 LXDE session + 一個 x11vnc (只監聽 127.0.0.1:5900)。看門狗每 3 秒檢查,任何一個程序死掉會自動補回, 重複的程序會被清掉。
  • 桌面裡永遠有一個標題為「共用終端機」的 xterm;把它關掉後看門狗會自動再開一個, 所以固定分頁裡永遠有 1 個可用的圖形終端機視窗(使用者自己另開的终端機不受影響)。
  • 容器內沒有 systemd-logind session,因此會自動停用 lxpolkit 與 light-locker autostart,避免桌面啟動時跳出「No session for pid …」的 Error 對話框。
  • 剪貼簿同步(固定開啟):桌面裡的 CLIPBOARD/PRIMARY 永遠與瀏覽器所在系統的 剪貼簿雙向同步;右上角「剪貼簿同步」按鈕可隨時手動再同步一次(沒有關閉選項)。 在桌面內按 Ctrl+V (或 Cmd+V)會先把本機剪貼簿送進 X11 再貼上。底層由 autocutsel 將 x11vnc 使用的 CUT_BUFFER0 與 X11 selection 對齊,xterm 也設定為選取即同步 CLIPBOARD。 瀏覽器基於安全限制,頁面需要保持在前景;若系統阻擋寫入,點一下桌面會重試。
  • 瀏覽器是透過 /vnc WebSocket 橋接到同一個桌面,所有裝置看到完全相同、可同時操作的 LXDE 畫面;關掉瀏覽器分頁不會終止桌面,重新整理或重新連線都會接回原畫面。
  • 這個分頁是固定的:按它的 × 不會刪除分頁,而是重新啟動一份新的桌面 (POST /api/desktop/restart),永遠維持一個。
  • 終端機分頁也支援系統剪貼簿:選取後按 Ctrl+Shift+C(或 Ctrl+Insert)複製, Ctrl+V、Ctrl+Shift+V(或 Shift+Insert)直接貼上。貼上走 xterm.js 的 bracketed paste,與一般終端機一致。
  • 檔案瀏覽器可瀏覽/下載伺服器上的任何路徑(含 /tmp、/),不再有白名單限制; 選取資料夾後下載會即時串流打包成 .tar.gz,不會一次載入記憶體。
  • 桌面分頁的「上傳」會把檔案存到 ~/Desktop(同名檔案自動加序號),上傳後可直接在 LXDE 桌面看到。
  • 除錯端點:GET /api/desktop/status、POST /api/desktop/restart(程序 pid、VNC port、 健康狀態);桌面 log 在 /tmp/webterm-desktop.log。
  • 可用 Space 變數覆寫:DESKTOP_DISPLAY(預設 :99)、DESKTOP_GEOMETRY (預設 1440x900x24)、DESKTOP_VNC_PORT(預設 5900)、DESKTOP_NOVNC_ROOT (預設 /usr/share/novnc)。noVNC 靜態檔由 /novnc/* 提供,缺少時前端自動改用 CDN。

Playwright + Chromium(桌面內使用)

Space 啟動後會在背景自動安裝 Playwright 的 Chromium(下載到 ~/.cache/ms-playwright,系統依賴已由 packages.txt 預裝),完成後:

  • 共用 LXDE 桌面會出現 Playwright Chromium 圖示,點兩下即可在桌面內開瀏覽器 (以 root 執行,已自動加上 --no-sandbox --disable-dev-shm-usage)。

  • 也可直接執行啟動器:~/.local/bin/playwright-chromium。

  • 在終端機用 Python 操作時,記得 Chromium 需要無沙箱參數:

    from playwright.sync_api import sync_playwright
    
    with sync_playwright() as p:
        browser = p.chromium.launch(args=["--no-sandbox", "--disable-dev-shm-usage"])
        page = browser.new_page()
        page.goto("https://example.com")
        print(page.title())
        browser.close()
    
  • 若要開在共用桌面(看的到視窗),把 headless 設為 False 並使用 DISPLAY=:99; Playwright 會自動沿用 PLAYWRIGHT_BROWSERS_PATH。

  • 除錯端點:GET /api/playwright/status(是否就緒、Chromium 路徑、最後錯誤)、 POST /api/playwright/install(手動觸發重裝)。安裝 log 在 /tmp/webterm-playwright.log。

  • 手動更新 Chromium:python3 -m playwright install chromium。

記憶體守衛(memguard)

每次 Space 啟動時,app.py 的 install_memguard() 會自動:

  • 把 repo 的 memguard.py 複製到 ~/memkill.py 並啟動它
    • opencode 本體 + 它啟動的所有子行程合計 RSS > 80 GiB(單一行程 > 64 GiB)→ 對記憶體最肥的那個子行程樹送 SIGKILL
    • 只砍子行程,opencode 本體永不砍
    • 被砍的指令與峰值寫入 /tmp/memkill.last.json、歷史 /tmp/memkill.jsonl、log /var/log/memkill.log
  • 把 repo 的 AGENTS.md 複製到 ~/.config/opencode/AGENTS.md
    • opencode 每輪開工都會讀:被砍後不准用同一種做法重跑,必須改用串流/分頁/分段落盤/降批次等低記憶體做法,改不過來就停下來問使用者
  • 在 ~/.bashrc 加冪等自動啟動,新開的終端機也一定有守衛

上限可用 Space 變數覆寫:MEMGUARD_TOTAL_GIB、MEMGUARD_PER_PROC_GIB。

使用者在終端機檢查:

pgrep -af 'memkill[.]py'      # 守衛是否活著
tail -f /var/log/memkill.log  # 每 2 秒的用量
cat /tmp/memkill.last.json    # 上次被砍的是哪條指令、峰值幾 GiB