From 21400700c56a4163112f9161a26e826cf4e2c504 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 12 Jun 2026 15:00:15 +0800 Subject: [PATCH] Add HTTPS reverse proxy guide and PNG icons for real PWA install. Co-authored-by: Cursor --- DEPLOY.md | 47 ++++++++++++++++++++++++++++++++++++++ app.py | 30 ++++++++++++++++++++---- nginx/trading_studio.conf | 45 ++++++++++++++++++++++++++++++++++++ pwa/icons/icon-192.png | Bin 0 -> 966 bytes pwa/icons/icon-512.png | Bin 0 -> 2827 bytes pwa/manifest.webmanifest | 24 ++++++++++++++----- scripts/gen_pwa_icons.sh | 23 +++++++++++++++++++ scripts/gen_ssl_cert.sh | 34 +++++++++++++++++++++++++++ 8 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 nginx/trading_studio.conf create mode 100644 pwa/icons/icon-192.png create mode 100644 pwa/icons/icon-512.png create mode 100644 scripts/gen_pwa_icons.sh create mode 100644 scripts/gen_ssl_cert.sh diff --git a/DEPLOY.md b/DEPLOY.md index 64a33f7..d05a282 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -140,6 +140,53 @@ SKIP_PYTORCH=1 bash deploy.sh deps 下载过程中出现 `Retrying... Read timed out` 属于正常重试,**并非卡死**,请耐心等待 10-30 分钟。 +### 0.7 PWA 安装 App 与 HTTPS 反向代理 + +| 访问方式 | 浏览器行为 | +|----------|------------| +| `http://IP:5683` | 只能「快捷方式 / 添加到主屏幕」,**不能**系统级一键安装 | +| `https://IP` 或 `https://域名` | Chrome/Edge 可弹出 **「安装 Trading Studio」**,独立窗口运行 | + +**原因:** PWA 规范要求 **HTTPS 安全上下文**(`localhost` 除外)。局域网直连 HTTP 是正常现象,不是代码 bug。 + +#### 推荐方案:Nginx + 自签证书(纯局域网) + +```bash +cd /opt/Trading_Studio +bash server-update.sh # 或 git pull + +# 1. 安装 Nginx +sudo apt install -y nginx + +# 2. 生成自签 SSL(替换为你的服务器局域网 IP) +sudo bash scripts/gen_ssl_cert.sh 192.168.8.100 + +# 3. 启用站点配置 +sudo cp nginx/trading_studio.conf /etc/nginx/sites-available/trading_studio.conf +sudo ln -sf /etc/nginx/sites-available/trading_studio.conf /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx + +# 4. 确保 Gradio 仍由 PM2 监听 127.0.0.1:5683(仅本机),外网只走 443 +pm2 restart trading_studio +``` + +**访问:** `https://192.168.8.100`(首次需在手机/平板点「高级 → 继续访问」信任证书) + +**安装 App:** +- 电脑 Chrome:地址栏出现 ⊕ 安装图标,或点页面「安装 App」按钮 +- 安卓 Chrome:菜单 → 安装应用 +- iPad/iPhone Safari:分享 → 添加到主屏幕(iOS 无系统安装弹窗,但 HTTPS 下体验更完整) + +#### 有公网域名时 + +在 Nginx 前加 [Let's Encrypt](https://letsencrypt.org/) 免费证书(`certbot`),可免信任自签证书步骤。 + +#### 无反向代理时的替代 + +HTTP 下点击「安装 App」会显示手动引导;桌面快捷方式仍可用,功能不受影响。 + +--- + ### 0.5 PM2 运维(root 环境) ```bash diff --git a/app.py b/app.py index bd6f1a0..94dc38d 100644 --- a/app.py +++ b/app.py @@ -232,15 +232,25 @@ PWA_HEAD = """ document.body.appendChild(overlay); } + function isSecure() { + return location.protocol === "https:" || location.hostname === "localhost" || location.hostname === "127.0.0.1"; + } + function manualInstallGuide() { + var httpWarn = ""; + if (!isSecure()) { + httpWarn = "
⚠️ 当前为 HTTP 访问
浏览器只能创建快捷方式,无法弹出系统级「安装 App」。
要一键安装,请配置 HTTPS 反向代理(见服务器 DEPLOY.md)。
"; + } var steps = isIOS() - ? "
  1. 点击 Safari 底部分享按钮 □↑
  2. 选择 「添加到主屏幕」
  3. 点击 添加 即可
" - : "
  1. 点击浏览器右上角 菜单
  2. 选择 「安装应用」「添加到主屏幕」
  3. 确认安装即可
"; + ? "
  1. 点击 Safari 底部分享按钮 □↑
  2. 选择 「添加到主屏幕」
  3. 点击 添加
" + : (isSecure() + ? "
  1. Chrome/Edge 地址栏点击 安装 图标
  2. 或菜单 → 安装 Trading Studio
" + : "
  1. Chrome 菜单 → 添加到主屏幕创建快捷方式
  2. 配置 HTTPS 后可真正「安装应用」
"); showModal( '' + - '

📲 安装 Trading Studio

' + - '

当前环境需手动安装,按以下步骤操作:

' + steps + - '

安装后可像原生 App 一样从桌面/icon 启动。

' + '

📲 安装 Trading Studio

' + httpWarn + + '

按以下步骤操作:

' + steps + + '

HTTPS 安装后可独立窗口运行,体验接近原生 App。

' ); } @@ -583,6 +593,16 @@ gradio-app, .pwa-modal p { color: #cbd5e1 !important; line-height: 1.6 !important; } .pwa-modal ol { color: #e2e8f0 !important; padding-left: 20px !important; line-height: 1.8 !important; } .pwa-modal-tip { font-size: 0.85rem !important; color: #93c5fd !important; margin-top: 16px !important; } +.pwa-modal-warn { + background: #422006 !important; + border: 1px solid #f59e0b !important; + border-radius: 8px !important; + padding: 12px 14px !important; + margin-bottom: 14px !important; + color: #fde68a !important; + font-size: 0.9rem !important; + line-height: 1.6 !important; +} .pwa-modal-close { position: absolute; top: 12px; diff --git a/nginx/trading_studio.conf b/nginx/trading_studio.conf new file mode 100644 index 0000000..25ecc11 --- /dev/null +++ b/nginx/trading_studio.conf @@ -0,0 +1,45 @@ +# Trading Studio — Nginx HTTPS 反向代理 +# 将 https://你的域名或IP 转发到本机 Gradio 5683 +# +# 安装步骤见 DEPLOY.md「PWA 安装与 HTTPS」 + +# HTTP 自动跳转 HTTPS(可选,不需要可删除此 server 块) +server { + listen 80; + listen [::]:80; + server_name trading.local _; + + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name trading.local _; + + # 自签证书路径(先用 scripts/gen_ssl_cert.sh 生成) + ssl_certificate /etc/nginx/ssl/trading_studio.crt; + ssl_certificate_key /etc/nginx/ssl/trading_studio.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + client_max_body_size 200M; + + location / { + proxy_pass http://127.0.0.1:5683; + proxy_http_version 1.1; + + # Gradio WebSocket 必需 + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } +} diff --git a/pwa/icons/icon-192.png b/pwa/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..2057557f9b33449adac273456e840b88ff0afa8f GIT binary patch literal 966 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE2}s`E_d9@rfw|Yy#WAE}&fD9De$tK-ZHW$F zOgK8Na;K~=m@K1aelqmJmYoiMg02cI$8PNY-23+GpYxYD7k&KnHiPGS`Si1ho0e;Xiu>1NMJ=eS4|>p{JY~rk_901M16={k+-P zL3_n>V@5&23qjA)^?`K2^FKRSsuUHJn5@pde9piqyKvrNkki{|%wG(m^RTsmwR~;x__4LbScZdHgm(K>; zdEyT+AeK})JW~&`U9dHV>8*zBgr6s`@_N`hU*YXg>|#r)bJLU(SRBH0;tx-Vp#qzh zcz}_;UgIkECr?dd);3723vhV$TqMu`(-lu%pABb==B(P}FniW~GjH2grbjMbEGcz< z%Uv99UwpO-NC72&`p8{a_A2x8+4FxGbKPfeoOhD>)|EeJvYs_;TkLCPZ_K`B?$6B6 z*NlrD<{Y2HICK8ynFb19=Ui@KEcDyVno@TzXIq2WqN^*?w>4}twlDP&z2LUR=(BMs z&=E?{e$IZ;xc~dV@->f`avin9`WwD|(28+&IJ~9mv+{z(sFPJJE?y4Id=6`0{o%-H z2-_jYI8i8I$<=xCjIQhp!uEl=>Yss8WP9bao5T7V)@$w#t7p#t&a$PistgpjVKNP8 z&Zjd)dD&KT0=Z&__VI!jJ~B_J;u8R-1=++pKH&?BuPj$pIqYNN4&{Hr@Og7f!?)n` zPE1NcRtwhiIXp|>d5LkN@CCiko4Xq90?tR3G8ZwMxZG{9Gq#sy`Ql|;3yh5n2WxhP z&zIL`IJ{q}4vhJ$%trPLXP;tM`1(YP?TAAukn1c0jG@I-xKD&9Bv0i&@uw$LPvI+w zI3@&i2smY;5EE~&7OtqMvOAi5Q-K?j`k(xI_s_7JX=x-dNhm3iN<7l~$iHUq3X4Pi Sc1wWylfl!~&t;ucLK6Vq_k>pf literal 0 HcmV?d00001 diff --git a/pwa/icons/icon-512.png b/pwa/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..7d08ac9527521dc0429d052b75a51db35e3fa540 GIT binary patch literal 2827 zcmds3ac~n=8vl0LQqn?GTHC3#HXve zA{q--u(c&QLc{3{I)|hgE1I4hUCbRgP*}7FX~%lqULl>E774^aHzxb;?c4tx$C;ba zf9|ih@0a(!@Atm%_kJ(Ut5;RcNST!a05hsrR6Yhk<4+oYcLg&omo>F`Oo80k970tC~6zlVZ1Gyd;}*!Lg%MOJw+S_OaInYgZBbnfO1n7tC< zJ?7_!XXW0cPZEZ4@ZM~^%{~yyh4Oa+Ds{j3Al!En-CneoWBmI-pe)=I1ptrIAl{GG zYYFp85T}US)^gc+h05Te3teczBOq0H*`t|i$M`BI+Eh-?^j*EuolNml>^eS62SaD# zwp<#vH6#X`$=0E>aa#!u>4ngc8Sx~hsizUn^l_gSKzJg0J3D7DkpO>t@`mFra#rDB zA9t&WQ0gi{gPkDUYvEED==^sfTrPxgVZ<|yC}%P^;z`rK+GO({5UyLeXGxJI)W_{& zAb)J3v7-pAN@k%NuKbW}?tuNF*cuYO{D5qB6ONCHBDaY0{f5Na6OZX>xL62RoG6@2 z9sWnu!4lT#Pb+~9gyHJV6LHKumuPtQsV)>Y(}H5*?Cm7n!4fpGkQ{lUFYeej4TjE} zV4?yBI%NIzOt^I=7H|-n1N&wDXC#QVG{g?AQ0vJbbE1aDl+{q@?fpy`cteXA9@P(- z$j^6SRzQe$*pxsW!yoTa!bSpQKe$tAN^Lnr*!t%8DuFhVW?ieS7c=4VXe=;C16pK# z)lBfWAAo@t@MS~{X+#3T{e4_n2l%E24Qcb#iU~9ZULzE`@u3Q6D7}%L^TSLyQm+IW z8NRVcX_!xP7j0AmgCw}~9a%qxP%s|SjrHObtzbN$>tZL!9pIat$k?idG@@FK`4OuU zu+u`+!ufZDkbENPw3p%cx==$F3GV%)5_p{i_haV44DgP`0#`LaSQ;5N!KNBgoUgL~ zgnXrVPwd5L^5Km=a_>;`&h~kCo;X_yrw2!!#b=^pT4$y|t!(%rK6V!k-)u=Vdce3# z)@NqGK$|Q&7{1?yMthW@r6lN9JaHl1ow9ev+0JqC8cAHXS7tk9@hNTcI+^^9ftRIN z1SrWKVi#0EnG}ft*DCsYCGrp@ffrf5 ztb@oxS|38z5Q}@Yu6bO1mEk{dB407ZZ*-zO6WHA{+d3|eFnn^4g4N3>Igz^p?7InE zt<~osp2%V&1EH(2t@9~qNr>IF4E(Q+tFYEFe>+)&y}o~Q;!gZyv#iHGcJ7r$BaQDW zs&| zL^Th?z(uzn@VdDKn~O2+vV2=J2}0bqiiWW*30DV^mHW%Mt)2$k=7h@)zIL4l0-rFj z3e#pO9=8=YM!jb{YHq4qvuLQ=l34#H*_wAcZd*tTMa3LQvF<$5nKE7$_h9$A8tc{` ztd|=`GOZEMOR6nKZO@R49-bZXT++e0&#zGYxHy$mMS3B`Hfgu}`B4L4!(0YZnwMQX zlQ`(_U1-yCSU2!xGI&{BZq-%yVXPXYDPHz-;)>SC>pI~e!s?bD@v=>t^#<`GGpzf1 zP^}2|Mf|o%rVu-m25vOgKLjb)OXU;eJFr^|2P)BJ=PJ{R@~0+wBR#GtH%4;vCAsiX=8YWSb> zLg4~7M+X=eck!pHu>f2syo9ovz3g_ajWg8pG^A42Qtyv!@DKk2evJ-(FrtxS+!$;S z2S}$DV}PQCn`Ztg&fY?x=deT7(!v)w)f-@(i&FtD{H2exw}P)AXz0`&?G-&>PumT? zoS@pVqib``lbMeNI8UA7{cPgzy(d36K4KASJn*12QWlbD4f7++QFELbknC7 zLCx8?qgw|hTN1UJJs)YpS`OsUg2lq+EGLrFtj~B(O*EA>)0z@uTjqg9j5xy|7VBjrQq=Prl<`h`Xu%1u=?fy+5B%j1yetQtlqF1rPf{l1p?aH@c;k- literal 0 HcmV?d00001 diff --git a/pwa/manifest.webmanifest b/pwa/manifest.webmanifest index a9812ec..ee9e269 100644 --- a/pwa/manifest.webmanifest +++ b/pwa/manifest.webmanifest @@ -11,17 +11,29 @@ "lang": "zh-CN", "categories": ["productivity", "utilities"], "icons": [ + { + "src": "/pwa/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/pwa/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/pwa/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + }, { "src": "/pwa/icons/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" - }, - { - "src": "/pwa/icons/icon.svg", - "sizes": "512x512", - "type": "image/svg+xml", - "purpose": "maskable" } ] } diff --git a/scripts/gen_pwa_icons.sh b/scripts/gen_pwa_icons.sh new file mode 100644 index 0000000..09b6b5f --- /dev/null +++ b/scripts/gen_pwa_icons.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# 从 SVG 生成 PWA 所需 PNG 图标(Chrome 安装条件) +# 用法: bash scripts/gen_pwa_icons.sh +set -euo pipefail +cd "$(dirname "$0")/../pwa/icons" +SVG="icon.svg" + +if command -v rsvg-convert &>/dev/null; then + rsvg-convert -w 192 -h 192 "${SVG}" -o icon-192.png + rsvg-convert -w 512 -h 512 "${SVG}" -o icon-512.png +elif command -v convert &>/dev/null; then + convert -background none "${SVG}" -resize 192x192 icon-192.png + convert -background none "${SVG}" -resize 512x512 icon-512.png +elif command -v ffmpeg &>/dev/null; then + ffmpeg -y -f lavfi -i "color=c=0x0f1419:s=512x512" -frames:v 1 icon-512.png 2>/dev/null + ffmpeg -y -f lavfi -i "color=c=0x0f1419:s=192x192" -frames:v 1 icon-192.png 2>/dev/null + echo "[WARN] 仅用 ffmpeg 生成纯色图标,建议: apt install librsvg2-bin" +else + echo "[ERROR] 需要 rsvg-convert / imagemagick / ffmpeg 之一" + exit 1 +fi + +echo "[OK] 已生成 icon-192.png icon-512.png" diff --git a/scripts/gen_ssl_cert.sh b/scripts/gen_ssl_cert.sh new file mode 100644 index 0000000..66aeaf1 --- /dev/null +++ b/scripts/gen_ssl_cert.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# 生成本地 HTTPS 自签证书(局域网 PWA 安装用) +# 用法: sudo bash scripts/gen_ssl_cert.sh [服务器局域网IP] +set -euo pipefail + +SERVER_IP="${1:-}" +SSL_DIR="/etc/nginx/ssl" +KEY="${SSL_DIR}/trading_studio.key" +CRT="${SSL_DIR}/trading_studio.crt" + +if [[ "${EUID:-0}" -ne 0 ]]; then + echo "请使用 root: sudo bash scripts/gen_ssl_cert.sh 192.168.x.x" + exit 1 +fi + +if [[ -z "${SERVER_IP}" ]]; then + SERVER_IP=$(hostname -I | awk '{print $1}') + echo "[INFO] 未指定 IP,使用: ${SERVER_IP}" +fi + +mkdir -p "${SSL_DIR}" + +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ + -keyout "${KEY}" \ + -out "${CRT}" \ + -subj "/CN=TradingStudio/O=Trading/C=CN" \ + -addext "subjectAltName=IP:${SERVER_IP},DNS:trading.local,DNS:localhost" + +chmod 600 "${KEY}" +echo "[OK] 证书已生成:" +echo " ${CRT}" +echo " ${KEY}" +echo "" +echo "手机/平板首次访问 HTTPS 需点「继续访问」信任自签证书,之后即可安装 App。"