2.3.0:手表界面精简 + 播放器 + 鼠标重做

手表端
- 功能页从 10 个杂项精简到 7 个(屏幕/鼠标/媒体/文件/电脑/语音/电源)
- 删掉:游戏封锁、待办、掷骰子、抽奖、壁纸、显示器开关、定时关机、
  剪贴板历史、性能/磁盘/电量、文件夹浏览、教室限制等
- 改成显式列按钮,不再把电脑端所有功能组无脑铺到表盘
- 远程鼠标控制重做:滑动区 + 左键/滚轮/右键三按钮
  滚轮点按=中键、上下滑=滚动
- 新增视频/音乐播放器(手表本地播放):进度拖动、左右滑快进退10秒、
  点画面播放暂停、上一/下一、播完自动下一首

电脑端
- 新增 kill_all(结束所有进程),白名单保护系统关键进程和自身
- 新增 media_list / media_pull(播放器数据源)
  安全边界:只允许媒体目录,用 relative_to 判断,防同前缀兄弟目录绕过

中转站
- known_devices 从 ID 集合改成「设备ID -> 配对码」映射
- 修复:管理员改码后关机超过 6 小时,改码会被超时清理冲掉
- 老格式 state 文件平滑升级
This commit is contained in:
xmz7891
2026-10-18 11:39:14 +08:00
parent 1d368979aa
commit 2061f9525f
50 changed files with 13812 additions and 584 deletions
+235
View File
@@ -0,0 +1,235 @@
# -*- coding: utf-8 -*-
r'''重写 halo 的 nginx 配置:修掉 301 循环。
== 怎么坏的 ==
我上一版**故意没写 443 块**(没证书时 `nginx -t` 会失败),
打算"等证书好了让 certbot 自己加"。
结果 `certbot --nginx` 找不到一个"纯粹的 443 块",
就把 `listen 443 ssl` 和证书那几行**塞进了那个 80 端口的块里**。
而那个块的 `location /` 内容是:
return 301 https://$host$request_uri;
于是 https 的请求进来也走这条 —— **301 到自己,无限循环**。
浏览器会报 "重定向次数过多"。
**certbot 还另外新建了一个纯 80 的块**(带两条 `if ($host = ...) return 301`),
跟原来的块功能重复。
== 教训 ==
**不要让 certbot 去改一个"结构不完整"的配置。**
`--nginx` 插件是**按 server 块猜**的:它找 `server_name` 匹配的块,
把 ssl 指令加进去。如果那个块本来是 80 的、还带 301,
它就会造出一个"443 + 301"的自环。
正确做法:**自己把 80 / 443 两块都写清楚**(443 先注释掉但不留半截),
证书有了之后**只把证书路径填进去**,而不是让 certbot 重排结构。
== 这一版的结构 ==
server A: 80 default_server -> proxy 到 Halo(IP 访问用)
server B: 80 + 域名 -> ACME 例外 + 301 到 https
server C: 443 + 域名 -> proxy 到 Halo
三个块各司其职,没有重叠,也没有"同一块里既有 301 又有 ssl"。
'''
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "_build"))
from lssh import run, upload # noqa: E402
CONF = """# xxmxxs.top —— Halo 个人博客
#
# 入口:
# http://<服务器IP>/ 博客(IP,HTTP)
# http://<服务器IP>/console 管理后台(IP,HTTP)
# https://xxmxxs.top/ 博客(HTTPS)
# https://xxmxxs.top/console 管理后台(HTTPS)
# https://www.xxmxxs.top/ 同上
# http://<服务器IP>:3001/ 直连 Halo(用户要求)
#
# 后端:容器 halo,监听 0.0.0.0:3001
# 控制台:Halo 原生 /console(2.26.1 没有可配置的路径前缀)
# ======================================================================
# A. 80 端口默认站点 —— 用 IP 访问时直接出博客
# ======================================================================
#
# nginx 规则:Host 匹配不上任何 server_name 时用该端口的 default_server。
#
# ⚠ 这里**不做 https 跳转**:IP 没证书,跳过去浏览器报警告,
# 用户会以为网站坏了。
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
access_log /var/log/nginx/halo-access.log;
error_log /var/log/nginx/halo-error.log;
server_tokens off;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 300s;
}
}
# ======================================================================
# B. 80 端口 + 域名 —— ACME 挑战 + 跳 https
# ======================================================================
#
# ⚠ 这个块**只监听 80**,不含任何 ssl 指令。
# 上一版 certbot 把 443 塞进来,造出了"443 上也 301"的自环。
server {
listen 80;
listen [::]:80;
server_name xxmxxs.top www.xxmxxs.top;
access_log /var/log/nginx/halo-access.log;
error_log /var/log/nginx/halo-error.log;
server_tokens off;
# ACME 挑战必须在 301 之前 ——
# certbot 的 HTTP-01 要在 80 端口直接拿到文件。
# 被 301 跳走就续期失败,而**最多 90 天后才暴露**(证书到期)。
location /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
# ======================================================================
# C. 443 + 域名 —— 博客(HTTPS)
# ======================================================================
#
# ⚠ `listen ... ssl` 和证书那几行是 certbot 需要的形式,
# 续期时它会检查。**别删**,也别把 80 的 301 混进来。
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name xxmxxs.top www.xxmxxs.top;
access_log /var/log/nginx/halo-access.log;
error_log /var/log/nginx/halo-error.log;
server_tokens off;
client_max_body_size 100m;
ssl_certificate /etc/letsencrypt/live/xxmxxs.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/xxmxxs.top/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
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;
# 事件总线(SSE)要用,而且不能缓冲
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "";
proxy_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_connect_timeout 30s;
}
}
"""
def main() -> int:
print("=" * 62)
print(" 重写配置,修掉 301 循环")
print("=" * 62)
print()
tmp = ROOT / "_build" / "_halo_nginx.conf"
tmp.write_text(CONF, encoding="utf-8", newline="\n")
upload(str(tmp), "/tmp/halo-nginx.conf")
tmp.unlink(missing_ok=True)
out = run(r'''
sudo cp /etc/nginx/sites-available/halo /etc/nginx/sites-available/halo.bak.$(date +%s)
sudo mv /tmp/halo-nginx.conf /etc/nginx/sites-available/halo
echo " 写好了"
echo
echo " ---- 检查 ----"
sudo nginx -t 2>&1 | tail -3 | sed 's/^/ /'
''', timeout=240, quiet=True)
o = out[1] if isinstance(out, tuple) else str(out)
print(o)
if "successful" not in o:
print(" ✗ 配置有错,撤回")
run("sudo cp $(sudo ls -t /etc/nginx/sites-available/halo.bak.* | head -1) "
"/etc/nginx/sites-available/halo && sudo nginx -t", timeout=120,
quiet=True)
return 1
out = run(r'''
sudo systemctl reload nginx && sleep 1
echo " nginx: $(systemctl is-active nginx)"
echo
echo "════════ 逐条复核 ════════"
printf " %-44s " "http://<服务器IP>/ (博客)"
curl -s -o /dev/null -w "HTTP %{http_code} %{size_download} 字节\n" --max-time 10 http://127.0.0.1/
printf " %-44s " "http://<服务器IP>/console (控制台)"
curl -s -o /dev/null -w "HTTP %{http_code}\n" --max-time 10 http://127.0.0.1/console
echo
printf " %-44s " "https://xxmxxs.top/ (博客)"
curl -sk -o /dev/null -w "HTTP %{http_code} %{size_download} 字节\n" --max-time 15 --resolve xxmxxs.top:443:127.0.0.1 https://xxmxxs.top/
printf " %-44s " "https://xxmxxs.top/console (控制台)"
curl -sk -o /dev/null -w "HTTP %{http_code}\n" --max-time 15 --resolve xxmxxs.top:443:127.0.0.1 https://xxmxxs.top/console
printf " %-44s " "https://www.xxmxxs.top/ (博客)"
curl -sk -o /dev/null -w "HTTP %{http_code}\n" --max-time 15 --resolve www.xxmxxs.top:443:127.0.0.1 https://www.xxmxxs.top/
printf " %-44s " "http://xxmxxs.top/ (应 301 到 https)"
curl -s -o /dev/null -w "HTTP %{http_code} -> %{redirect_url}\n" --max-time 15 -H "Host: xxmxxs.top" http://127.0.0.1/
echo
echo " 不能弄坏的:"
printf " %-44s " "https://yk.xxmxxs.top/ (中转站)"
curl -s -o /dev/null -w "HTTP %{http_code} %{size_download} 字节\n" --max-time 15 https://yk.xxmxxs.top/
printf " %-44s " "http://<服务器IP>:9100/ (官网)"
curl -s -o /dev/null -w "HTTP %{http_code}\n" --max-time 10 http://127.0.0.1:9100/
printf " %-44s " "http://<服务器IP>:3001/ (直连 Halo)"
curl -s -o /dev/null -w "HTTP %{http_code}\n" --max-time 10 http://127.0.0.1:3001/
echo
echo "════════ ACME 挑战路径(续期的命根子)════════"
sudo sh -c 'echo probe > /var/www/html/.well-known/acme-challenge/probe'
printf " %-44s " "http://xxmxxs.top/.well-known/... (应 200)"
curl -s -o /dev/null -w "HTTP %{http_code}\n" --max-time 8 -H "Host: xxmxxs.top" http://127.0.0.1/.well-known/acme-challenge/probe
sudo rm -f /var/www/html/.well-known/acme-challenge/probe
''', timeout=400, quiet=True)
print(out[1] if isinstance(out, tuple) else out)
return 0
if __name__ == "__main__":
sys.exit(main())