<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title> Yizong </title><description>Technical Blog</description><link>https://blog.yizong.de/</link><language>en</language><item><title>VPS常用命令/工具</title><link>https://blog.yizong.de/posts/linux/</link><guid isPermaLink="true">https://blog.yizong.de/posts/linux/</guid><description>VPS常用命令/工具</description><pubDate>Wed, 30 Dec 2099 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::important
请注意，以下命令适用于基于&lt;strong&gt;Linux&lt;/strong&gt;的系统。对于其他操作系统，操作命令可能有所不同。
:::&lt;/p&gt;
&lt;h1&gt;命令类&lt;/h1&gt;
&lt;h2&gt;更新VPS环境&lt;/h2&gt;
&lt;p&gt;Ubuntu/Debian&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt update -y &amp;amp;&amp;amp; apt install -y curl socat wget sudo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alpine&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apk update &amp;amp;&amp;amp; apk add curl
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.runoob.com/linux/linux-comm-tree.html&quot;&gt;Tree命令&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install tree
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;vim安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get install vim
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;unzip安装&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install zip
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://www.runoob.com/linux/linux-comm-tree.html&quot;&gt;Nginx命令&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo nginx -t #检查配置文件语法

sudo systemctl start nginx #启动 Nginx

sudo systemctl stop nginx #停止 Nginx

sudo systemctl restart nginx #重启 Nginx

sudo systemctl reload nginx #重新加载配置（不中断服务）

sudo systemctl status nginx #查看状态
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;工具类&lt;/h1&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/eooce/Sing-box&quot;&gt;ssh综合工具箱&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://raw.githubusercontent.com/eooce/ssh_tool/main/ssh_tool.sh -o ssh_tool.sh &amp;amp;&amp;amp; chmod +x ssh_tool.sh &amp;amp;&amp;amp; ./ssh_tool.sh

bash &amp;lt;(curl -sL kejilion.sh)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/xykt/IPQuality&quot;&gt;IP质量体检脚本&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;bash &amp;lt;(curl -Ls IP.Check.Place)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/eooce/Sing-box&quot;&gt;老王四协议&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;bash &amp;lt;(curl -Ls https://raw.githubusercontent.com/eooce/sing-box/main/sing-box.sh)

PORT=11111 CFIP=www.visa.com.tw CFPORT=443 bash &amp;lt;(curl -Ls https://raw.githubusercontent.com/eooce/sing-box/main/sing-box.sh)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/yonggekkk/x-ui-yg&quot;&gt;甬哥X-ui&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;bash &amp;lt;(curl -Ls https://raw.githubusercontent.com/yonggekkk/x-ui-yg/main/install.sh)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;IPv6专区&lt;/h1&gt;
&lt;h2&gt;&lt;a href=&quot;https://github.com/fscarmen/warp&quot;&gt;warp&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;wget -N https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh &amp;amp;&amp;amp; bash menu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;纯IPv6修改DNS64(NAT64)，可访问IPV4地址&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/resolv.conf
nameserver 2001:67c:2b0::4 
nameserver 2001:67c:27e4::64
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>从零部署 Emby 媒体服与用户管理</title><link>https://blog.yizong.de/posts/emby-guide/</link><guid isPermaLink="true">https://blog.yizong.de/posts/emby-guide/</guid><description>基于 Debian 12 的 Emby 媒体服务器完整搭建笔记：Docker、rclone、Google Drive/OpenList、Caddy、Cloudflare、Telegram 上传机器人、EmbyPulse 与 EmbyTGBot。</description><pubDate>Sat, 09 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文整理自一次完整的 Emby 搭建过程，覆盖 NAT VPS 与独立服务器两种场景。涉及到的 Token、API Key、域名、Telegram ID 请全部替换成你自己的值，不要直接复制示例里的占位符。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;0. 最终目标&lt;/h2&gt;
&lt;p&gt;我们要搭建的是一套完整的个人媒体系统：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Google Drive / OpenList / OneDrive
        ↓ rclone 挂载
/mnt/gdrive 或 /mnt/openlist
        ↓
Emby 读取媒体库
        ↓
Caddy / Cloudflare 提供访问线路
        ↓
客户端播放、网页管理、用户自助注册、统计面板
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时还会额外搭建一个 Telegram 上传机器人：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;你把资源发给 Telegram 机器人
        ↓
服务器自动接收文件
        ↓
rclone 上传到网盘指定目录
        ↓
你整理命名后 Emby 扫库
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可选管理系统有两个方向：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;th&gt;适合人群&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EmbyPulse&lt;/td&gt;
&lt;td&gt;Web 面板、统计、用户中心、播放排行、缺集等&lt;/td&gt;
&lt;td&gt;想要图形化管理面板&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EmbyTGBot&lt;/td&gt;
&lt;td&gt;Telegram 管理用户、注册码、续期、查询在线人数&lt;/td&gt;
&lt;td&gt;想通过 TG 管理用户&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;:::warning
不要把机器人 Token、Emby API Key、Google OAuth Secret、rclone.conf 公开到博客或 GitHub。
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 服务器场景与端口规划&lt;/h2&gt;
&lt;p&gt;本文覆盖两种服务器。&lt;/p&gt;
&lt;h3&gt;1.1 NAT VPS 场景&lt;/h3&gt;
&lt;p&gt;假设只有少量端口，比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;服务器 IP：176.123.6.17
可用端口：48111-48119
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐规划：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;48111  Emby 反代线路
48112  EmbyPulse 管理后台
48113  EmbyPulse 用户中心
48114  Emby 直连播放线路
48119  SSH，若服务商已经分配给 SSH 就不要占用
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NAT VPS 的重点是：&lt;strong&gt;服务商面板必须把外部端口映射到 VPS 内部端口&lt;/strong&gt;。如果外部访问超时，第一优先排查端口映射。&lt;/p&gt;
&lt;h3&gt;1.2 独立服务器场景&lt;/h3&gt;
&lt;p&gt;假设服务器有独立公网 IP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;服务器 IP：147.224.40.215
SSH 端口：22700
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;已有占用端口示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;80/tcp      caddy
443/tcp     caddy
22700/tcp   sshd
8001/tcp    sing-box
50501/tcp   sing-box
50503/udp   sing-box
50504/udp   sing-box
40823/udp   sing-box
5353/udp    avahi-daemon / openclaw-gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新增端口建议：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8096/tcp    Emby 直连播放线路
18080/tcp   可选 Web API，仅监听 127.0.0.1
18081/tcp   可选 Web 前端，仅监听 127.0.0.1
18082/tcp   本地 Telegram Bot API，仅监听 127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;独立服务器推荐线路：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://emby.example.com     Cloudflare 橙云线路，用于网页管理
https://play.example.com     DNS only 灰云线路，用于直连播放
http://147.224.40.215:8096   IP 直连线路，用于调试或客户端播放
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important
视频播放不建议长期走 Cloudflare 橙云线路。Cloudflare 线路适合网页访问、管理、登录；播放大文件建议走灰云直连域名或 IP:8096。
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 系统初始化&lt;/h2&gt;
&lt;p&gt;以下命令以 Debian 12 为例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh root@你的服务器IP -p 你的SSH端口
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更新系统并安装基础组件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt update &amp;amp;&amp;amp; apt upgrade -y
apt install -y ca-certificates curl gnupg lsb-release nano vim unzip fuse3 jq socat openssl git
hostnamectl set-timezone Asia/Shanghai
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查端口占用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ss -lntup
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 安装 Docker 和 Docker Compose&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do
  apt remove -y &quot;$pkg&quot; || true
done

install -m 0755 -d /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

cat &amp;gt; /etc/apt/sources.list.d/docker.sources &amp;lt;&amp;lt;EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release &amp;amp;&amp;amp; echo &quot;$VERSION_CODENAME&quot;)
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

systemctl enable --now docker

docker version
docker compose version
docker run --rm hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 Docker 网络：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker network create media-net 2&amp;gt;/dev/null || true
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 配置 rclone 与 Google Drive&lt;/h2&gt;
&lt;h3&gt;4.1 安装 rclone&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl https://rclone.org/install.sh | bash
rclone version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;允许 FUSE 挂载被 Docker 容器读取：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep -qxF &apos;user_allow_other&apos; /etc/fuse.conf || echo &apos;user_allow_other&apos; &amp;gt;&amp;gt; /etc/fuse.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /mnt/gdrive
mkdir -p /var/cache/rclone-gdrive
mkdir -p /root/.config/rclone
chmod 700 /root/.config/rclone
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.2 给 rclone 配自己的 Google Drive Client ID&lt;/h3&gt;
&lt;p&gt;不建议长期使用 rclone 默认共享 client_id，容易遇到 Google Drive API 限流：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;googleapi: Error 403: Quota exceeded
RATE_LIMIT_EXCEEDED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;去 Google Cloud Console 创建项目：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. 创建项目，例如 rclone-emby-upload
2. APIs &amp;amp; Services -&amp;gt; Library
3. 启用 Google Drive API
4. Google Auth Platform / OAuth consent screen
5. Data Access 添加 scope:
   https://www.googleapis.com/auth/drive
6. Audience 添加自己的 Google 账号为测试用户，或发布到 In production
7. Clients -&amp;gt; Create OAuth client
8. Application type 选择 Desktop app
9. 复制 Client ID 和 Client Secret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在服务器配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rclone config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按提示填写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n) New remote
name&amp;gt; gdrive
Storage&amp;gt; drive
client_id&amp;gt; 你的 Client ID
client_secret&amp;gt; 你的 Client Secret
scope&amp;gt; drive
root_folder_id&amp;gt; 直接回车
service_account_file&amp;gt; 直接回车
Edit advanced config? n
Use web browser to automatically authenticate rclone with remote? n
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果服务器没有浏览器，rclone 会要求你在本地电脑执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rclone authorize &quot;drive&quot; &quot;一大串配置&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果本地网络访问 Google 有问题，PowerShell 先设置代理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$env:HTTP_PROXY=&quot;http://127.0.0.1:7890&quot;
$env:HTTPS_PROXY=&quot;http://127.0.0.1:7890&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;授权成功后，把 JSON token 粘贴回服务器。&lt;/p&gt;
&lt;p&gt;测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rclone about gdrive:
rclone lsd gdrive:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建媒体目录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rclone mkdir &quot;gdrive:媒体/电影&quot;
rclone mkdir &quot;gdrive:媒体/电视剧&quot;
rclone mkdir &quot;gdrive:媒体/动漫&quot;
rclone mkdir &quot;gdrive:TelegramUploads/待整理&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 挂载 Google Drive&lt;/h3&gt;
&lt;p&gt;创建 systemd 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /etc/systemd/system/rclone-gdrive.service &amp;lt;&amp;lt;&apos;EOF&apos;
[Unit]
Description=Rclone mount Google Drive for Emby
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStartPre=/bin/mkdir -p /mnt/gdrive /var/cache/rclone-gdrive
ExecStart=/usr/bin/rclone mount gdrive: /mnt/gdrive \
  --config=/root/.config/rclone/rclone.conf \
  --allow-other \
  --read-only \
  --uid=1000 \
  --gid=1000 \
  --umask=002 \
  --dir-cache-time=72h \
  --poll-interval=15s \
  --vfs-cache-mode=full \
  --vfs-cache-max-size=50G \
  --vfs-cache-max-age=6h \
  --buffer-size=64M \
  --cache-dir=/var/cache/rclone-gdrive
ExecStop=/usr/bin/fusermount3 -uz /mnt/gdrive
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable --now rclone-gdrive
systemctl status rclone-gdrive --no-pager
ls -la /mnt/gdrive
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 可选：通过 OpenList WebDAV 挂载网盘&lt;/h2&gt;
&lt;p&gt;如果你已经搭好了 OpenList，可以通过 WebDAV 挂到本地。&lt;/p&gt;
&lt;p&gt;OpenList WebDAV 地址格式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://openlist.example.com/dav/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置 rclone：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rclone config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n) New remote
name&amp;gt; openlist
Storage&amp;gt; webdav
url&amp;gt; https://openlist.example.com/dav/
vendor&amp;gt; other
user&amp;gt; OpenList 用户名
password&amp;gt; OpenList 密码
bearer_token&amp;gt; 直接回车
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rclone lsd openlist:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建挂载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /mnt/openlist
mkdir -p /var/cache/rclone-openlist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 systemd：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /etc/systemd/system/rclone-openlist.service &amp;lt;&amp;lt;&apos;EOF&apos;
[Unit]
Description=Rclone mount OpenList WebDAV for Emby
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
ExecStartPre=/bin/mkdir -p /mnt/openlist /var/cache/rclone-openlist
ExecStart=/usr/bin/rclone mount openlist: /mnt/openlist \
  --config=/root/.config/rclone/rclone.conf \
  --allow-other \
  --read-only \
  --uid=1000 \
  --gid=1000 \
  --umask=002 \
  --dir-cache-time=72h \
  --poll-interval=30s \
  --vfs-cache-mode=full \
  --vfs-cache-max-size=20G \
  --vfs-cache-max-age=6h \
  --buffer-size=32M \
  --cache-dir=/var/cache/rclone-openlist
ExecStop=/usr/bin/fusermount3 -uz /mnt/openlist
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
systemctl enable --now rclone-openlist
ls -la /mnt/openlist
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 部署 Emby&lt;/h2&gt;
&lt;h3&gt;6.1 判断服务器架构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;uname -m
docker info | grep -i architecture
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是 &lt;code&gt;x86_64&lt;/code&gt; / &lt;code&gt;amd64&lt;/code&gt;，用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;emby/embyserver:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果是 &lt;code&gt;aarch64&lt;/code&gt; / &lt;code&gt;arm64&lt;/code&gt;，用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;emby/embyserver_arm64v8:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning
ARM64 服务器如果误用 amd64 镜像，会出现 &lt;code&gt;exec /init: exec format error&lt;/code&gt;。
:::&lt;/p&gt;
&lt;h3&gt;6.2 创建 Compose&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /opt/media-stack/emby
mkdir -p /opt/media/emby/config
mkdir -p /opt/media/emby/transcode
chown -R 1000:1000 /opt/media/emby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;amd64 示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /opt/media-stack/emby/docker-compose.yml &amp;lt;&amp;lt;&apos;EOF&apos;
services:
  emby:
    image: emby/embyserver:latest
    container_name: embyserver
    restart: unless-stopped
    ports:
      - &quot;8096:8096&quot;
    environment:
      UID: &quot;1000&quot;
      GID: &quot;1000&quot;
      GIDLIST: &quot;1000&quot;
      TZ: Asia/Shanghai
    volumes:
      - /opt/media/emby/config:/config
      - /opt/media/emby/transcode:/transcode
      - type: bind
        source: /mnt/gdrive
        target: /mnt/gdrive
        read_only: true
        bind:
          propagation: rslave
    networks:
      - media-net

networks:
  media-net:
    external: true
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ARM64 改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image: emby/embyserver_arm64v8:latest
platform: linux/arm64/v8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/media-stack/emby
docker compose pull
docker compose up -d
docker logs --tail=100 embyserver
curl -I http://127.0.0.1:8096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;让 Docker 尽量等 rclone 挂载后启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /etc/systemd/system/docker.service.d

cat &amp;gt; /etc/systemd/system/docker.service.d/override.conf &amp;lt;&amp;lt;&apos;EOF&apos;
[Unit]
Wants=rclone-gdrive.service
After=rclone-gdrive.service
EOF

systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Caddy、Cloudflare 与播放线路&lt;/h2&gt;
&lt;h3&gt;7.1 独立服务器推荐配置&lt;/h3&gt;
&lt;p&gt;Cloudflare DNS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A  emby  服务器IP  Proxied / 橙云
A  play  服务器IP  DNS only / 灰云
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caddy 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /etc/caddy/conf.d
cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak.$(date +%F-%H%M%S)

grep -q &apos;import /etc/caddy/conf.d/\*.caddy&apos; /etc/caddy/Caddyfile || \
  printf &apos;\nimport /etc/caddy/conf.d/*.caddy\n&apos; &amp;gt;&amp;gt; /etc/caddy/Caddyfile

cat &amp;gt; /etc/caddy/conf.d/emby.caddy &amp;lt;&amp;lt;&apos;EOF&apos;
emby.example.com {
  encode zstd gzip
  reverse_proxy 127.0.0.1:8096
}

play.example.com {
  encode zstd gzip
  reverse_proxy 127.0.0.1:8096
}
EOF

caddy fmt --overwrite /etc/caddy/Caddyfile /etc/caddy/conf.d/emby.caddy
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终入口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cloudflare 代理线路：
https://emby.example.com

直连 HTTPS 线路：
https://play.example.com

IP 直连线路：
http://服务器IP:8096
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.2 NAT VPS 推荐配置&lt;/h3&gt;
&lt;p&gt;如果只有高端口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;48111 -&amp;gt; Emby 反代线路
48114 -&amp;gt; Emby 直连播放线路
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caddyfile 示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  auto_https off
}

:48111 {
  encode gzip
  reverse_proxy 127.0.0.1:8096
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直连播放线路可以用服务商面板直接配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;外部 48114 -&amp;gt; 内部 8096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果服务商只能同端口映射，则用 socat：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt install -y socat

cat &amp;gt; /etc/systemd/system/emby-direct-48114.service &amp;lt;&amp;lt;&apos;EOF&apos;
[Unit]
Description=Direct TCP forward 48114 to Emby 8096
After=network-online.target docker.service
Wants=network-online.target

[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:48114,fork,reuseaddr TCP:127.0.0.1:8096
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now emby-direct-48114
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;访问：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://服务器IP:48111  Emby 反代线路
http://服务器IP:48114  Emby 直连播放线路
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 初始化 Emby 与媒体库&lt;/h2&gt;
&lt;p&gt;浏览器打开：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://服务器IP:8096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://play.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首次向导：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;语言：中文
创建管理员账号
允许远程访问：开启
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加媒体库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;电影库：
内容类型：电影
路径：/mnt/gdrive/媒体/电影

电视剧库：
内容类型：电视节目
路径：/mnt/gdrive/媒体/电视剧

动漫库：
内容类型：电视节目
路径：/mnt/gdrive/媒体/动漫
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 媒体库整理规范&lt;/h2&gt;
&lt;p&gt;Emby 主要根据媒体库类型、文件夹名、文件名和季集编号识别，不会真正理解视频内容。&lt;/p&gt;
&lt;h3&gt;9.1 电影&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;媒体/
  电影/
    流浪地球 (2019)/
      流浪地球 (2019).mkv

    Avatar (2009)/
      Avatar (2009).mkv
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9.2 电视剧&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;媒体/
  电视剧/
    庆余年 (2019)/
      Season 01/
        庆余年 S01E01.mkv
        庆余年 S01E02.mkv
      Season 02/
        庆余年 S02E01.mkv
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9.3 动漫&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;媒体/
  动漫/
    葬送的芙莉莲 (2023)/
      Season 01/
        葬送的芙莉莲 S01E01.mkv
        葬送的芙莉莲 S01E02.mkv
      Specials/
        葬送的芙莉莲 S00E01.mkv
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;9.4 不推荐&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;电视剧/
  第01集.mkv
  第02集.mkv
  01.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或把所有电影、剧集、动漫混在一个媒体库里。&lt;/p&gt;
&lt;p&gt;:::tip
Telegram 上传机器人建议只上传到 &lt;code&gt;gdrive:TelegramUploads/待整理&lt;/code&gt;，不要直接传到正式媒体库。确认片名、年份、季集后，再移动到正式目录。
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Telegram 上传网盘机器人&lt;/h2&gt;
&lt;p&gt;这个机器人负责：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;接收 Telegram 文件
自动排队
上传到当前设置的网盘目录
支持 /setdir 切换上传目录
支持 /cancel 中止当前任务
支持 /queue 查看等待队列
支持每 10 秒刷新上传进度
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10.1 准备信息&lt;/h3&gt;
&lt;p&gt;需要：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BOT_TOKEN
TELEGRAM_API_ID
TELEGRAM_API_HASH
你的 Telegram 数字 ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;BOT_TOKEN&lt;/code&gt; 从 &lt;code&gt;@BotFather&lt;/code&gt; 创建。&lt;br /&gt;
&lt;code&gt;TELEGRAM_API_ID&lt;/code&gt; 和 &lt;code&gt;TELEGRAM_API_HASH&lt;/code&gt; 从 &lt;code&gt;https://my.telegram.org&lt;/code&gt; 创建 App 获取。&lt;/p&gt;
&lt;h3&gt;10.2 创建目录与配置&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /opt/tg-bot-uploader/botapi-data
mkdir -p /opt/tg-bot-uploader/downloads
cd /opt/tg-bot-uploader
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 &lt;code&gt;.env&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /opt/tg-bot-uploader/.env &amp;lt;&amp;lt;&apos;EOF&apos;
BOT_TOKEN=这里填你的BOT_TOKEN
TELEGRAM_API_ID=这里填你的API_ID
TELEGRAM_API_HASH=这里填你的API_HASH

ALLOWED_USER_IDS=

DEST_REMOTE=gdrive:TelegramUploads/待整理
ALLOWED_DEST_PREFIXES=gdrive:

BOT_API_URL=http://127.0.0.1:18082
FILE_API_URL=http://127.0.0.1:18082/file

LOCAL_BOT_API_PATH_PREFIX=/var/lib/telegram-bot-api
HOST_BOT_API_PATH_PREFIX=/opt/tg-bot-uploader/botapi-data

DOWNLOAD_DIR=/opt/tg-bot-uploader/downloads
STATE_FILE=/opt/tg-bot-uploader/state.json
PROGRESS_INTERVAL=10
SKIP_OLD_UPDATES_ON_START=true
EOF

chmod 600 /opt/tg-bot-uploader/.env
vim /opt/tg-bot-uploader/.env
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10.3 部署本地 Telegram Bot API Server&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /opt/tg-bot-uploader/docker-compose.yml &amp;lt;&amp;lt;&apos;EOF&apos;
services:
  telegram-bot-api:
    image: aiogram/telegram-bot-api:latest
    container_name: telegram-bot-api
    restart: unless-stopped
    ports:
      - &quot;127.0.0.1:18082:8081&quot;
    env_file:
      - /opt/tg-bot-uploader/.env
    environment:
      TELEGRAM_LOCAL: &quot;1&quot;
    volumes:
      - /opt/tg-bot-uploader/botapi-data:/var/lib/telegram-bot-api
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/tg-bot-uploader
docker compose pull
docker compose up -d
docker logs --tail=100 telegram-bot-api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source /opt/tg-bot-uploader/.env
curl &quot;http://127.0.0.1:18082/bot${BOT_TOKEN}/getMe&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回 &lt;code&gt;&quot;ok&quot;:true&lt;/code&gt; 即成功。&lt;/p&gt;
&lt;p&gt;给机器人发 &lt;code&gt;/start&lt;/code&gt;，然后获取你的 Telegram ID：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -s &quot;http://127.0.0.1:18082/bot${BOT_TOKEN}/getUpdates&quot; | jq
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把 &lt;code&gt;from.id&lt;/code&gt; 写进 &lt;code&gt;.env&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALLOWED_USER_IDS=123456789
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10.4 上传机器人脚本&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /opt/tg-bot-uploader/tg_uploader.py &amp;lt;&amp;lt;&apos;PY&apos;
#!/usr/bin/env python3
import os
import re
import time
import json
import shutil
import select
import threading
import subprocess
from pathlib import Path
from collections import deque
from urllib.parse import quote
from urllib.request import urlopen, Request

BOT_TOKEN = os.environ[&quot;BOT_TOKEN&quot;]
BOT_API_URL = os.environ.get(&quot;BOT_API_URL&quot;, &quot;http://127.0.0.1:18082&quot;).rstrip(&quot;/&quot;)
FILE_API_URL = os.environ.get(&quot;FILE_API_URL&quot;, &quot;http://127.0.0.1:18082/file&quot;).rstrip(&quot;/&quot;)
DEFAULT_DEST_REMOTE = os.environ.get(&quot;DEST_REMOTE&quot;, &quot;gdrive:TelegramUploads/待整理&quot;).rstrip(&quot;/&quot;)
STATE_FILE = Path(os.environ.get(&quot;STATE_FILE&quot;, &quot;/opt/tg-bot-uploader/state.json&quot;))
DOWNLOAD_DIR = Path(os.environ.get(&quot;DOWNLOAD_DIR&quot;, &quot;/opt/tg-bot-uploader/downloads&quot;))
PROGRESS_INTERVAL = int(os.environ.get(&quot;PROGRESS_INTERVAL&quot;, &quot;10&quot;))
SKIP_OLD = os.environ.get(&quot;SKIP_OLD_UPDATES_ON_START&quot;, &quot;true&quot;).lower() in [&quot;1&quot;, &quot;true&quot;, &quot;yes&quot;, &quot;y&quot;]

ALLOWED_DEST_PREFIXES = [x.strip() for x in os.environ.get(&quot;ALLOWED_DEST_PREFIXES&quot;, &quot;gdrive:&quot;).split(&quot;,&quot;) if x.strip()]
ALLOWED_USER_IDS = {int(x.strip()) for x in os.environ.get(&quot;ALLOWED_USER_IDS&quot;, &quot;&quot;).split(&quot;,&quot;) if x.strip()}

LOCAL_PREFIX = os.environ.get(&quot;LOCAL_BOT_API_PATH_PREFIX&quot;, &quot;&quot;)
HOST_PREFIX = os.environ.get(&quot;HOST_BOT_API_PATH_PREFIX&quot;, &quot;&quot;)

DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)

state_lock = threading.Lock()
queue_cond = threading.Condition()
job_queue = deque()
current_job = None
job_seq = 0

class Cancelled(Exception):
    pass

def load_state_unlocked():
    if not STATE_FILE.exists():
        return {&quot;users&quot;: {}, &quot;last_update_id&quot;: None}
    try:
        data = json.loads(STATE_FILE.read_text(&quot;utf-8&quot;))
    except Exception:
        return {&quot;users&quot;: {}, &quot;last_update_id&quot;: None}
    data.setdefault(&quot;users&quot;, {})
    data.setdefault(&quot;last_update_id&quot;, None)
    return data

def save_state_unlocked(data):
    tmp = STATE_FILE.with_suffix(&quot;.tmp&quot;)
    tmp.write_text(json.dumps(data, ensure_ascii=False, indent=2), &quot;utf-8&quot;)
    tmp.replace(STATE_FILE)

def get_user_dest(user_id):
    with state_lock:
        state = load_state_unlocked()
        return state[&quot;users&quot;].get(str(user_id), {}).get(&quot;dest&quot;, DEFAULT_DEST_REMOTE)

def set_user_dest(user_id, dest):
    dest = dest.strip().rstrip(&quot;/&quot;)
    if not dest:
        raise ValueError(&quot;目录不能为空&quot;)
    if not any(dest.startswith(prefix) for prefix in ALLOWED_DEST_PREFIXES):
        raise ValueError(f&quot;不允许的目录。允许前缀：{&apos;, &apos;.join(ALLOWED_DEST_PREFIXES)}&quot;)
    with state_lock:
        state = load_state_unlocked()
        state[&quot;users&quot;].setdefault(str(user_id), {})
        state[&quot;users&quot;][str(user_id)][&quot;dest&quot;] = dest
        save_state_unlocked(state)

def get_last_update_id():
    with state_lock:
        return load_state_unlocked().get(&quot;last_update_id&quot;)

def set_last_update_id(update_id):
    with state_lock:
        state = load_state_unlocked()
        state[&quot;last_update_id&quot;] = update_id
        save_state_unlocked(state)

def api(method, data=None, timeout=300):
    url = f&quot;{BOT_API_URL}/bot{BOT_TOKEN}/{method}&quot;
    if data is None:
        req = Request(url)
    else:
        req = Request(url, data=json.dumps(data).encode(&quot;utf-8&quot;), headers={&quot;Content-Type&quot;: &quot;application/json&quot;})
    with urlopen(req, timeout=timeout) as r:
        return json.loads(r.read().decode(&quot;utf-8&quot;))

def send(chat_id, text):
    try:
        res = api(&quot;sendMessage&quot;, {&quot;chat_id&quot;: chat_id, &quot;text&quot;: text})
        if res.get(&quot;ok&quot;):
            return res[&quot;result&quot;][&quot;message_id&quot;]
    except Exception as e:
        print(&quot;sendMessage error:&quot;, repr(e), flush=True)
    return None

def edit(chat_id, message_id, text):
    if not message_id:
        return
    try:
        api(&quot;editMessageText&quot;, {&quot;chat_id&quot;: chat_id, &quot;message_id&quot;: message_id, &quot;text&quot;: text})
    except Exception as e:
        print(&quot;editMessageText error:&quot;, repr(e), flush=True)

def human_size(num):
    try:
        num = float(num)
    except Exception:
        return &quot;未知&quot;
    units = [&quot;B&quot;, &quot;KB&quot;, &quot;MB&quot;, &quot;GB&quot;, &quot;TB&quot;]
    for unit in units:
        if abs(num) &amp;lt; 1024:
            return f&quot;{num:.2f} {unit}&quot;
        num /= 1024
    return f&quot;{num:.2f} PB&quot;

def safe_name(name):
    return name.replace(&quot;/&quot;, &quot;_&quot;).replace(&quot;\\&quot;, &quot;_&quot;).strip() or f&quot;telegram_file_{int(time.time())}&quot;

def rclone_join(remote_dir, file_name):
    remote_dir = remote_dir.strip().rstrip(&quot;/&quot;)
    return remote_dir + file_name if remote_dir.endswith(&quot;:&quot;) else remote_dir + &quot;/&quot; + file_name

def ensure_remote_dir(remote_dir):
    subprocess.run([&quot;rclone&quot;, &quot;mkdir&quot;, remote_dir], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def get_media(message):
    for key in [&quot;document&quot;, &quot;video&quot;, &quot;audio&quot;]:
        if key in message:
            obj = message[key]
            file_id = obj[&quot;file_id&quot;]
            file_name = obj.get(&quot;file_name&quot;) or f&quot;{key}_{message[&apos;message_id&apos;]}&quot; + (&quot;.mp4&quot; if key == &quot;video&quot; else &quot;&quot;)
            size = obj.get(&quot;file_size&quot;, 0)
            return file_id, safe_name(file_name), size
    if &quot;photo&quot; in message:
        obj = message[&quot;photo&quot;][-1]
        return obj[&quot;file_id&quot;], f&quot;photo_{message[&apos;message_id&apos;]}.jpg&quot;, obj.get(&quot;file_size&quot;, 0)
    return None, None, None

def get_file_path(file_id):
    info = api(&quot;getFile&quot;, {&quot;file_id&quot;: file_id})
    if not info.get(&quot;ok&quot;):
        raise RuntimeError(info)
    return info[&quot;result&quot;][&quot;file_path&quot;]

def resolve_local_path(file_path):
    paths = []
    if file_path.startswith(&quot;/&quot;):
        paths.append(file_path)
        if LOCAL_PREFIX and HOST_PREFIX and file_path.startswith(LOCAL_PREFIX):
            paths.append(file_path.replace(LOCAL_PREFIX, HOST_PREFIX, 1))
    for p in paths:
        if os.path.exists(p):
            return Path(p)
    return None

def download_via_http(file_path, file_name, job):
    url = f&quot;{FILE_API_URL}/bot{BOT_TOKEN}/{quote(file_path)}&quot;
    local_path = DOWNLOAD_DIR / file_name
    with urlopen(url, timeout=3600) as r, open(local_path, &quot;wb&quot;) as f:
        total = r.headers.get(&quot;Content-Length&quot;)
        total = int(total) if total and total.isdigit() else 0
        done = 0
        last_update = 0
        while True:
            if job[&quot;cancel_event&quot;].is_set():
                raise Cancelled(&quot;已中止当前任务&quot;)
            chunk = r.read(8 * 1024 * 1024)
            if not chunk:
                break
            f.write(chunk)
            done += len(chunk)
            now = time.time()
            if now - last_update &amp;gt;= PROGRESS_INTERVAL:
                if total:
                    percent = done / total * 100
                    text = f&quot;正在从 Telegram 下载到服务器：\n{file_name}\n\n进度：{percent:.2f}%\n已下载：{human_size(done)} / {human_size(total)}\n\n可发送 /cancel 中止当前任务&quot;
                else:
                    text = f&quot;正在从 Telegram 下载到服务器：\n{file_name}\n\n已下载：{human_size(done)}\n\n可发送 /cancel 中止当前任务&quot;
                edit(job[&quot;chat_id&quot;], job[&quot;progress_message_id&quot;], text)
                last_update = now
    return local_path

def strip_ansi(s):
    return re.sub(r&quot;\x1b\[[0-9;]*[A-Za-z]&quot;, &quot;&quot;, s).replace(&quot;\r&quot;, &quot;&quot;).replace(&quot;\n&quot;, &quot;&quot;).strip()

def get_queue_size():
    with queue_cond:
        return len(job_queue)

def format_progress_message(job, line):
    line = strip_ansi(line) or &quot;等待 rclone 输出进度...&quot;
    if len(line) &amp;gt; 900:
        line = line[-900:]
    return (
        f&quot;正在上传：\n{job[&apos;file_name&apos;]}\n\n&quot;
        f&quot;目标目录：\n{job[&apos;dest_dir&apos;]}\n\n&quot;
        f&quot;rclone 进度：\n{line}\n\n&quot;
        f&quot;队列中等待：{get_queue_size()} 个\n&quot;
        f&quot;可发送 /cancel 中止当前任务&quot;
    )

def terminate_process(proc):
    if not proc:
        return
    try:
        proc.terminate()
        proc.wait(timeout=8)
    except Exception:
        try:
            proc.kill()
        except Exception:
            pass

def upload_with_rclone_progress(src, job):
    src = Path(src)
    dest_path = rclone_join(job[&quot;dest_dir&quot;], job[&quot;file_name&quot;])
    ensure_remote_dir(job[&quot;dest_dir&quot;])

    cmd = [
        &quot;rclone&quot;, &quot;copyto&quot;, str(src), dest_path,
        &quot;-P&quot;,
        &quot;--stats=1s&quot;,
        &quot;--stats-one-line&quot;,
        &quot;--stats-unit=bytes&quot;,
        &quot;--stats-log-level=NOTICE&quot;,
        &quot;--transfers=1&quot;,
        &quot;--checkers=1&quot;,
        &quot;--tpslimit=3&quot;,
        &quot;--tpslimit-burst=3&quot;,
        &quot;--drive-pacer-min-sleep=200ms&quot;,
        &quot;--drive-pacer-burst=10&quot;,
        &quot;--retries=20&quot;,
        &quot;--retries-sleep=30s&quot;,
        &quot;--low-level-retries=20&quot;,
        &quot;--log-level=NOTICE&quot;
    ]

    proc = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=0)
    job[&quot;process&quot;] = proc
    job[&quot;stage&quot;] = &quot;uploading&quot;

    last_edit = 0
    line_buf = &quot;&quot;
    last_line = &quot;等待 rclone 输出进度...&quot;

    try:
        while True:
            if job[&quot;cancel_event&quot;].is_set():
                terminate_process(proc)
                raise Cancelled(&quot;已中止当前任务&quot;)

            if proc.poll() is not None:
                break

            ready, _, _ = select.select([proc.stdout], [], [], 1.0)
            if not ready:
                continue

            ch = proc.stdout.read(1)
            if not ch:
                continue

            if ch in [&quot;\r&quot;, &quot;\n&quot;]:
                clean = strip_ansi(line_buf)
                line_buf = &quot;&quot;
                if clean:
                    last_line = clean
                now = time.time()
                if now - last_edit &amp;gt;= PROGRESS_INTERVAL:
                    edit(job[&quot;chat_id&quot;], job[&quot;progress_message_id&quot;], format_progress_message(job, last_line))
                    last_edit = now
            else:
                line_buf += ch
                if len(line_buf) &amp;gt; 3000:
                    line_buf = line_buf[-3000:]

        rc = proc.wait()
        if job[&quot;cancel_event&quot;].is_set():
            raise Cancelled(&quot;已中止当前任务&quot;)
        if rc != 0:
            raise RuntimeError(f&quot;rclone 上传失败，退出码：{rc}\n最后输出：{last_line}&quot;)

        edit(
            job[&quot;chat_id&quot;],
            job[&quot;progress_message_id&quot;],
            f&quot;上传完成：\n{job[&apos;file_name&apos;]}\n\n目标目录：\n{job[&apos;dest_dir&apos;]}\n\n网盘路径：\n{dest_path}\n\n队列中等待：{get_queue_size()} 个&quot;
        )
        return dest_path
    finally:
        job[&quot;process&quot;] = None

def queue_snapshot(limit=10):
    with queue_cond:
        items = list(job_queue)[:limit]
        total = len(job_queue)
    if not items:
        return &quot;当前没有等待中的任务。&quot;
    lines = [f&quot;等待队列：共 {total} 个任务&quot;]
    for i, job in enumerate(items, 1):
        lines.append(f&quot;{i}. #{job[&apos;id&apos;]} {job[&apos;file_name&apos;]} -&amp;gt; {job[&apos;dest_dir&apos;]}&quot;)
    if total &amp;gt; limit:
        lines.append(f&quot;... 还有 {total - limit} 个未显示&quot;)
    return &quot;\n&quot;.join(lines)

def status_text():
    with queue_cond:
        cur = current_job
        total = len(job_queue)
    if cur:
        cur_text = f&quot;当前任务：\n#{cur[&apos;id&apos;]} {cur[&apos;file_name&apos;]}\n阶段：{cur.get(&apos;stage&apos;, &apos;未知&apos;)}\n目标目录：{cur[&apos;dest_dir&apos;]}\n&quot;
    else:
        cur_text = &quot;当前没有正在上传的任务。\n&quot;
    return f&quot;{cur_text}\n等待队列：{total} 个\n\n命令：\n/queue 查看队列\n/cancel 中止当前任务\n/clear 清空等待队列&quot;

def help_text(user_id):
    current = get_user_dest(user_id)
    return (
        &quot;上传机器人使用说明\n\n&quot;
        &quot;直接发送文件、视频或音频给我，我会自动上传到当前网盘目录。\n&quot;
        &quot;多个文件会自动排队，按顺序依次上传。\n\n&quot;
        &quot;目录命令：\n&quot;
        &quot;/dir 查看当前上传目录\n&quot;
        &quot;/setdir gdrive:TelegramUploads/待整理 设置上传目录\n&quot;
        &quot;/setdir gdrive:媒体/电视剧/剧名 (年份)/Season 01 切换到指定目录\n\n&quot;
        &quot;队列命令：\n&quot;
        &quot;/status 查看当前任务和队列数量\n&quot;
        &quot;/queue 查看等待队列\n&quot;
        &quot;/cancel 中止当前正在上传的任务\n&quot;
        &quot;/clear 清空等待队列，不影响当前正在上传的任务\n\n&quot;
        f&quot;当前上传目录：\n{current}\n\n&quot;
        &quot;建议发送时选择“作为文件发送 / Send as File”，不要选择压缩视频。&quot;
    )

def normalize_command(text):
    if not text:
        return &quot;&quot;, &quot;&quot;
    parts = text.strip().split(maxsplit=1)
    cmd = parts[0].split(&quot;@&quot;, 1)[0]
    arg = parts[1].strip() if len(parts) &amp;gt; 1 else &quot;&quot;
    return cmd, arg

def add_job(message, chat_id, user_id):
    global job_seq
    file_id, file_name, size = get_media(message)
    if not file_id:
        send(chat_id, help_text(user_id))
        return

    dest_dir = get_user_dest(user_id)

    with queue_cond:
        job_seq += 1
        job_id = job_seq
        position = len(job_queue) + (1 if current_job else 0) + 1

    progress_message_id = send(
        chat_id,
        f&quot;已加入上传队列：\n#{job_id} {file_name}\n\n&quot;
        f&quot;大小：{human_size(size) if size else &apos;未知&apos;}\n&quot;
        f&quot;目标目录：\n{dest_dir}\n\n&quot;
        f&quot;当前排队位置：{position}\n&quot;
        f&quot;发送 /queue 查看队列，/cancel 中止当前上传。&quot;
    )

    job = {
        &quot;id&quot;: job_id,
        &quot;chat_id&quot;: chat_id,
        &quot;user_id&quot;: user_id,
        &quot;file_id&quot;: file_id,
        &quot;file_name&quot;: file_name,
        &quot;size&quot;: size,
        &quot;dest_dir&quot;: dest_dir,
        &quot;progress_message_id&quot;: progress_message_id,
        &quot;stage&quot;: &quot;queued&quot;,
        &quot;cancel_event&quot;: threading.Event(),
        &quot;process&quot;: None,
    }

    with queue_cond:
        job_queue.append(job)
        queue_cond.notify()

def handle_message(message):
    chat_id = message.get(&quot;chat&quot;, {}).get(&quot;id&quot;)
    user_id = message.get(&quot;from&quot;, {}).get(&quot;id&quot;)
    if not chat_id or not user_id:
        return

    if ALLOWED_USER_IDS and user_id not in ALLOWED_USER_IDS:
        send(chat_id, &quot;你没有权限使用这个上传机器人。&quot;)
        return

    text = (message.get(&quot;text&quot;) or &quot;&quot;).strip()
    cmd, arg = normalize_command(text)

    if cmd in [&quot;/start&quot;, &quot;/help&quot;]:
        send(chat_id, help_text(user_id))
        return
    if cmd == &quot;/dir&quot;:
        send(chat_id, f&quot;当前上传目录：\n{get_user_dest(user_id)}&quot;)
        return
    if cmd == &quot;/setdir&quot;:
        if not arg:
            send(chat_id, &quot;用法：\n/setdir gdrive:TelegramUploads/待整理\n/setdir gdrive:媒体/电视剧/剧名 (年份)/Season 01&quot;)
            return
        try:
            set_user_dest(user_id, arg)
            ensure_remote_dir(arg)
            send(chat_id, f&quot;已切换上传目录：\n{arg}&quot;)
        except Exception as e:
            send(chat_id, f&quot;设置失败：{e}&quot;)
        return
    if cmd == &quot;/queue&quot;:
        send(chat_id, queue_snapshot())
        return
    if cmd == &quot;/status&quot;:
        send(chat_id, status_text())
        return
    if cmd == &quot;/clear&quot;:
        with queue_cond:
            count = len(job_queue)
            job_queue.clear()
        send(chat_id, f&quot;已清空等待队列：{count} 个任务。当前正在上传的任务不受影响。&quot;)
        return
    if cmd == &quot;/cancel&quot;:
        with queue_cond:
            cur = current_job
        if not cur:
            send(chat_id, &quot;当前没有正在上传的任务。&quot;)
            return
        cur[&quot;cancel_event&quot;].set()
        if cur.get(&quot;process&quot;):
            terminate_process(cur[&quot;process&quot;])
        send(chat_id, f&quot;已请求中止当前任务：\n#{cur[&apos;id&apos;]} {cur[&apos;file_name&apos;]}&quot;)
        return

    add_job(message, chat_id, user_id)

def worker_loop():
    global current_job
    while True:
        with queue_cond:
            while not job_queue:
                queue_cond.wait()
            job = job_queue.popleft()
            current_job = job

        src = None
        should_delete = False

        try:
            job[&quot;stage&quot;] = &quot;preparing&quot;
            edit(job[&quot;chat_id&quot;], job[&quot;progress_message_id&quot;], f&quot;开始处理：\n#{job[&apos;id&apos;]} {job[&apos;file_name&apos;]}\n\n目标目录：\n{job[&apos;dest_dir&apos;]}\n\n正在获取 Telegram 文件信息...&quot;)

            if job[&quot;cancel_event&quot;].is_set():
                raise Cancelled(&quot;已中止当前任务&quot;)

            file_path = get_file_path(job[&quot;file_id&quot;])
            local_path = resolve_local_path(file_path)

            if local_path:
                src = local_path
                should_delete = False
                job[&quot;stage&quot;] = &quot;ready&quot;
            else:
                job[&quot;stage&quot;] = &quot;downloading&quot;
                src = download_via_http(file_path, job[&quot;file_name&quot;], job)
                should_delete = True

            if job[&quot;cancel_event&quot;].is_set():
                raise Cancelled(&quot;已中止当前任务&quot;)

            upload_with_rclone_progress(src, job)

        except Cancelled as e:
            edit(job[&quot;chat_id&quot;], job[&quot;progress_message_id&quot;], f&quot;任务已中止：\n#{job[&apos;id&apos;]} {job[&apos;file_name&apos;]}\n\n{e}\n\n队列中等待：{get_queue_size()} 个&quot;)
        except Exception as e:
            edit(job[&quot;chat_id&quot;], job[&quot;progress_message_id&quot;], f&quot;任务失败：\n#{job[&apos;id&apos;]} {job[&apos;file_name&apos;]}\n\n错误：{e}\n\n队列中等待：{get_queue_size()} 个&quot;)
            print(&quot;worker error:&quot;, repr(e), flush=True)
        finally:
            if should_delete and src and Path(src).exists():
                try:
                    Path(src).unlink()
                except Exception:
                    pass
            with queue_cond:
                current_job = None
                queue_cond.notify_all()

def initialize_offset():
    last = get_last_update_id()
    if last is not None:
        return int(last) + 1
    if not SKIP_OLD:
        return 0
    try:
        res = api(&quot;getUpdates&quot;, {&quot;timeout&quot;: 0, &quot;limit&quot;: 100}, timeout=30)
        updates = res.get(&quot;result&quot;, [])
        if updates:
            max_id = max(u[&quot;update_id&quot;] for u in updates)
            set_last_update_id(max_id)
            return max_id + 1
    except Exception as e:
        print(&quot;initialize_offset error:&quot;, repr(e), flush=True)
    return 0

def polling_loop():
    offset = initialize_offset()
    print(f&quot;polling started, offset={offset}&quot;, flush=True)
    while True:
        try:
            res = api(&quot;getUpdates&quot;, {&quot;offset&quot;: offset, &quot;timeout&quot;: 50, &quot;allowed_updates&quot;: [&quot;message&quot;]}, timeout=70)
            for upd in res.get(&quot;result&quot;, []):
                update_id = upd[&quot;update_id&quot;]
                offset = update_id + 1
                set_last_update_id(update_id)
                msg = upd.get(&quot;message&quot;) or {}
                if msg:
                    handle_message(msg)
        except Exception as e:
            print(&quot;polling error:&quot;, repr(e), flush=True)
            time.sleep(5)

def main():
    print(&quot;tg-uploader started&quot;, flush=True)
    worker = threading.Thread(target=worker_loop, daemon=True)
    worker.start()
    polling_loop()

if __name__ == &quot;__main__&quot;:
    main()
PY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x /opt/tg-bot-uploader/tg_uploader.py
python3 -m py_compile /opt/tg-bot-uploader/tg_uploader.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 systemd：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /etc/systemd/system/tg-uploader.service &amp;lt;&amp;lt;&apos;EOF&apos;
[Unit]
Description=Telegram file uploader to rclone remote
After=network-online.target docker.service
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/opt/tg-bot-uploader/.env
WorkingDirectory=/opt/tg-bot-uploader
ExecStart=/usr/bin/python3 /opt/tg-bot-uploader/tg_uploader.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now tg-uploader
journalctl -u tg-uploader -f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;10.5 机器人命令&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/help
查看帮助

/dir
查看当前上传目录

/setdir gdrive:TelegramUploads/待整理
设置上传目录

/setdir gdrive:媒体/电视剧/剧名 (年份)/Season 01
切换到指定目录

/status
查看当前任务

/queue
查看等待队列

/cancel
中止当前正在上传的任务

/clear
清空等待队列
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;11. EmbyPulse 管理方案&lt;/h2&gt;
&lt;p&gt;EmbyPulse 适合需要 Web 管理面板、用户中心、统计、播放排行等功能的场景。&lt;/p&gt;
&lt;h3&gt;11.1 Emby 里安装 Playback Reporting&lt;/h3&gt;
&lt;p&gt;Emby 后台：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;管理服务器
插件
目录 / Catalog
搜索 Playback Reporting
安装
重启 Emby
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11.2 创建 Emby API Key&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;管理服务器
服务器
API
新增 API Key
名称：EmbyPulse
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11.3 部署 EmbyPulse&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /opt/embypulse/config
mkdir -p /opt/embypulse/data
mkdir -p /opt/media-stack/embypulse
cd /opt/media-stack/embypulse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; .env &amp;lt;&amp;lt;&apos;EOF&apos;
LOCAL_ADMIN_USERNAME=admin
LOCAL_ADMIN_PASSWORD=这里改成强密码

EMBY_HOST=http://host.docker.internal:8096
EMBY_API_KEY=这里填Emby_API_Key
EOF

chmod 600 .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  emby-pulse:
    image: zeyu8023/embypulse-pro:latest
    container_name: emby-pulse
    restart: unless-stopped
    ports:
      - &quot;127.0.0.1:10307:10307&quot;
      - &quot;127.0.0.1:10308:10308&quot;
    extra_hosts:
      - &quot;host.docker.internal:host-gateway&quot;
    volumes:
      - /opt/embypulse/config:/workspace/config
      - /opt/embypulse/data:/workspace/data
    environment:
      TZ: Asia/Shanghai
      PORT: &quot;10307&quot;
      REQUEST_PORT: &quot;10308&quot;
      LOCAL_AUTH_ENABLED: &quot;true&quot;
      LOCAL_ADMIN_USERNAME: &quot;${LOCAL_ADMIN_USERNAME}&quot;
      LOCAL_ADMIN_PASSWORD: &quot;${LOCAL_ADMIN_PASSWORD}&quot;
      EMBY_HOST: &quot;${EMBY_HOST}&quot;
      EMBY_API_KEY: &quot;${EMBY_API_KEY}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker compose pull
docker compose up -d
docker logs --tail=100 emby-pulse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caddy 反代：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;admin.example.com {
  reverse_proxy 127.0.0.1:10307
}

user.example.com {
  reverse_proxy 127.0.0.1:10308
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;12. EmbyTGBot 管理方案&lt;/h2&gt;
&lt;p&gt;EmbyTGBot 适合通过 Telegram 管理 Emby 用户。&lt;/p&gt;
&lt;h3&gt;12.1 准备&lt;/h3&gt;
&lt;p&gt;需要：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Emby API Key
Emby 模板用户 testone
管理员 Bot Token
客户端 Bot Token
管理员 Telegram ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Emby 创建模板用户：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;用户名：testone
用途：新用户复制它的权限和配置
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;12.2 部署&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /opt/emby_tg_admin
cd /opt/emby_tg_admin

git clone https://github.com/sd87671067/EmbyTGBot.git EmbyTGBot
cd /opt/emby_tg_admin/EmbyTGBot

cp .env.example .env
chmod 600 .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成密钥：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openssl rand -hex 32
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑 &lt;code&gt;.env&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;APP_NAME=Emby TG 管理中心
APP_ENV=production
APP_PORT=18080
APP_BASE_URL=http://127.0.0.1:18080
APP_TIMEZONE=Asia/Shanghai
APP_MASTER_KEY=这里填openssl生成的字符串

APP_WEB_ADMIN_USERNAME=admin
APP_WEB_ADMIN_PASSWORD=一个强密码

EMBY_BASE_URL=http://host.docker.internal:8096
EMBY_API_KEY=Emby_API_Key

EMBY_SERVER_PUBLIC_URL=http://play1.example.com:8096,https://play2.example.com

EMBY_TEMPLATE_USER=testone
EMBY_IMPORT_IGNORE_USERNAMES=admin,testone
EMBY_SYNC_LOCAL_DEFAULT_PASSWORD=1234

ADMIN_BOT_TOKEN=管理员BotToken
ADMIN_CHAT_IDS=你的Telegram数字ID

CLIENT_BOT_TOKEN=客户端BotToken

ADMIN_CONTACT_TG_USERNAME=@你的TG用户名
ADMIN_CONTACT_TG_USER_ID=你的Telegram数字ID

DEFAULT_USER_EXPIRE_DAYS=90
REGISTER_CODE_LENGTH=16
CODE_BATCH_LIMIT=500
WEB_EXPIRING_SOON_DAYS=3
EXPIRY_CHECK_SECONDS=3600
ONLINE_CHECK_SECONDS=60
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改 &lt;code&gt;docker-compose.yml&lt;/code&gt;，给容器访问宿主机 Emby：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extra_hosts:
  - &quot;host.docker.internal:host-gateway&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker compose up -d --build
docker logs -f --tail=120 emby_tg_admin
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;12.3 常见问题&lt;/h3&gt;
&lt;h4&gt;Bot Token 无效&lt;/h4&gt;
&lt;p&gt;日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TokenValidationError: Token is invalid!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source .env
curl &quot;https://api.telegram.org/bot${ADMIN_BOT_TOKEN}/getMe&quot;
curl &quot;https://api.telegram.org/bot${CLIENT_BOT_TOKEN}/getMe&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ARM64 镜像问题&lt;/h4&gt;
&lt;p&gt;Emby 本体需要 ARM64 镜像：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image: emby/embyserver_arm64v8:latest
platform: linux/arm64/v8
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;查询有效用户只显示一个&lt;/h4&gt;
&lt;p&gt;可以确认 SQLite 数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 - &amp;lt;&amp;lt;&apos;PY&apos;
import sqlite3
from pathlib import Path

for db in Path(&quot;data&quot;).rglob(&quot;*.db&quot;):
    print(f&quot;\n===== {db} =====&quot;)
    conn = sqlite3.connect(db)
    cur = conn.cursor()
    for t in [r[0] for r in cur.execute(&quot;SELECT name FROM sqlite_master WHERE type=&apos;table&apos; ORDER BY name&quot;)]:
        cols = [r[1] for r in cur.execute(f&apos;PRAGMA table_info(&quot;{t}&quot;)&apos;)]
        if &quot;username&quot; in cols:
            print(f&quot;\n--- {t} ---&quot;)
            cur.execute(f&apos;SELECT * FROM &quot;{t}&quot;&apos;)
            for row in cur.fetchall():
                print(row)
    conn.close()
PY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果数据库里有多个用户，但机器人只显示一个，需要修正 &lt;code&gt;send_users_page&lt;/code&gt; 函数或等待项目更新。&lt;/p&gt;
&lt;h4&gt;服务地址一行显示两个 URL&lt;/h4&gt;
&lt;p&gt;可以把客户端显示改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;服务地址：
直连线路：http://play1.example.com:8096
CF线路：https://play2.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;核心逻辑是把逗号分隔的 &lt;code&gt;EMBY_SERVER_PUBLIC_URL&lt;/code&gt; 格式化成多行。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;13. Foam 卸载&lt;/h2&gt;
&lt;p&gt;如果之前部署过 Foam，可以这样卸载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/media-stack/foam 2&amp;gt;/dev/null || true

if [ -f docker-compose.yml ]; then
  docker compose down --remove-orphans
fi

docker rm -f foam foam-api foam-mysql foam-redis foam-selenium 2&amp;gt;/dev/null || true

mkdir -p /root/backup
if [ -d /opt/media-stack/foam ]; then
  tar -czf /root/backup/foam-backup-$(date +%F-%H%M%S).tar.gz /opt/media-stack/foam
fi

rm -rf /opt/media-stack/foam
docker image prune -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;清理 Caddy 中 Foam 域名反代：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep -R &quot;foam&quot; -n /etc/caddy 2&amp;gt;/dev/null || true
vim /etc/caddy/conf.d/media-stack.caddy
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;14. 启停与维护&lt;/h2&gt;
&lt;h3&gt;14.1 停止全部服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker stop embyserver 2&amp;gt;/dev/null || true
docker stop emby_tg_admin 2&amp;gt;/dev/null || true
docker stop telegram-bot-api 2&amp;gt;/dev/null || true

systemctl stop tg-uploader 2&amp;gt;/dev/null || true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14.2 重启全部服务&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;systemctl restart rclone-gdrive

cd /opt/media-stack/emby
docker compose up -d --force-recreate

cd /opt/emby_tg_admin/EmbyTGBot
docker compose up -d --force-recreate

cd /opt/tg-bot-uploader
docker compose up -d --force-recreate

systemctl restart tg-uploader
systemctl reload caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14.3 查看日志&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker logs -f --tail=100 embyserver
docker logs -f --tail=100 emby_tg_admin
docker logs -f --tail=100 telegram-bot-api
journalctl -u tg-uploader -f
journalctl -u rclone-gdrive -n 100 --no-pager
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14.4 更新 Emby&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/media-stack/emby
docker compose pull
docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14.5 更新 EmbyTGBot&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/emby_tg_admin/EmbyTGBot
git pull --ff-only
docker compose up -d --build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;14.6 更新上传机器人&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/tg-bot-uploader
docker compose pull
docker compose up -d
systemctl restart tg-uploader
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;15. 备份&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /root/backup

tar -czf /root/backup/media-server-backup-$(date +%F).tar.gz \
  /opt/media-stack \
  /opt/media/emby/config \
  /opt/tg-bot-uploader \
  /opt/emby_tg_admin/EmbyTGBot/.env \
  /opt/emby_tg_admin/EmbyTGBot/data \
  /root/.config/rclone \
  /etc/systemd/system/rclone-gdrive.service \
  /etc/systemd/system/tg-uploader.service \
  /etc/caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重点备份：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/opt/media/emby/config
/root/.config/rclone/rclone.conf
/opt/tg-bot-uploader/.env
/opt/tg-bot-uploader/state.json
/opt/emby_tg_admin/EmbyTGBot/.env
/opt/emby_tg_admin/EmbyTGBot/data
/etc/caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;16. 最终推荐使用流程&lt;/h2&gt;
&lt;p&gt;日常新增资源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. Telegram 上传机器人：
   /setdir gdrive:TelegramUploads/待整理

2. 把资源作为文件发送给机器人

3. 上传完成后，用 rclone moveto 整理到正式目录：
   gdrive:媒体/电影
   gdrive:媒体/电视剧
   gdrive:媒体/动漫

4. Emby 扫描媒体库

5. 如果识别错误，在 Emby 中手动 Identify
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户管理：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;方式 A：EmbyPulse Web 面板
方式 B：EmbyTGBot Telegram 管理
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;播放线路：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;首选：直连线路 / 灰云域名
备用：IP:8096
管理：Cloudflare 橙云域名
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;17. 总结&lt;/h2&gt;
&lt;p&gt;这套系统的核心原则是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Emby 只负责播放和刮削
rclone 负责把网盘挂成本地目录
Telegram 上传机器人负责收资源和上传网盘
EmbyPulse 或 EmbyTGBot 负责用户管理
Caddy 和 Cloudflare 负责访问线路
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想稳定，最重要的是三点：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. 媒体文件命名规范
2. 播放线路不要长期走 Cloudflare 橙云
3. rclone 使用自己的 Google Drive client_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;把这三点做好，整套 Emby + 网盘媒体库的体验会稳定很多。&lt;/p&gt;
</content:encoded></item><item><title>使用哪吒面板为 VPS 配置 Cloudflare DDNS</title><link>https://blog.yizong.de/posts/nezha-ddns/</link><guid isPermaLink="true">https://blog.yizong.de/posts/nezha-ddns/</guid><description>用哪吒面板给 VPS 配置 Cloudflare DDNS，并说明 IP 检测间隔、IP 变更通知</description><pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
本文记录的是：使用哪吒面板把 VPS 的公网 IP 自动同步到 Cloudflare DNS。适合家宽、动态 IP VPS、NAT 后端、经常重装或迁移 IP 的机器。
:::&lt;/p&gt;
&lt;h2&gt;一、先说结论&lt;/h2&gt;
&lt;p&gt;如果你想给 VPS 配置 Cloudflare DDNS，目前更推荐使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;哪吒面板 + Cloudflare API Token
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;哪吒面板可以在 Agent 上报新 IP 后，由 Dashboard 自动更新 Cloudflare 里的 DNS 记录。&lt;/p&gt;
&lt;p&gt;而 Komari 目前更偏向监控面板，并没有像哪吒面板一样内置 Cloudflare DDNS 功能。如果你使用 Komari，可以搭配 &lt;code&gt;ddns-go&lt;/code&gt; 或自写脚本来完成 DDNS。&lt;/p&gt;
&lt;h2&gt;二、准备工作&lt;/h2&gt;
&lt;p&gt;你需要提前准备好：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个已经托管到 Cloudflare 的域名&lt;/li&gt;
&lt;li&gt;一台已经接入哪吒 Agent 的 VPS&lt;/li&gt;
&lt;li&gt;一个 Cloudflare API Token&lt;/li&gt;
&lt;li&gt;哪吒 Dashboard 管理员权限&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文假设你的主域名是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;准备用来指向 VPS 的 DDNS 域名是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vps.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
实际操作时，请把 &lt;code&gt;example.com&lt;/code&gt; 和 &lt;code&gt;vps.example.com&lt;/code&gt; 换成你自己的域名。
:::&lt;/p&gt;
&lt;h2&gt;三、在 Cloudflare 里准备 DNS 记录&lt;/h2&gt;
&lt;p&gt;进入 Cloudflare 后，选择你的域名，然后进入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DNS → Records
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你只需要 IPv4，添加一条 A 记录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;类型：A
名称：vps
内容：先填当前 VPS IPv4，或者临时填一个占位 IP
代理状态：DNS only / 仅 DNS / 灰云
TTL：Auto 或 1 分钟
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你还需要 IPv6，再添加一条 AAAA 记录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;类型：AAAA
名称：vps
内容：先填当前 VPS IPv6，或者临时填一个占位 IPv6
代理状态：DNS only / 仅 DNS / 灰云
TTL：Auto 或 1 分钟
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::warning
如果这个域名是用来连接 SSH、VPS 面板、代理服务、游戏服或其他非 HTTP 服务，建议使用灰云，也就是 &lt;code&gt;DNS only&lt;/code&gt;。不要开启 Cloudflare 橙云代理，否则可能导致真实端口无法连接。
:::&lt;/p&gt;
&lt;h2&gt;四、创建 Cloudflare API Token&lt;/h2&gt;
&lt;p&gt;进入 Cloudflare 账号设置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;右上角头像
→ My Profile
→ API Tokens
→ Create Token
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以使用 &lt;code&gt;Edit zone DNS&lt;/code&gt; 模板，也可以自定义 Token。&lt;/p&gt;
&lt;p&gt;权限建议设置为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Zone → Zone → Read
Zone → DNS → Edit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;资源范围建议限制到指定域名：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Include → Specific zone → example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建后，Cloudflare 会显示一次 API Token。&lt;/p&gt;
&lt;p&gt;:::caution
Cloudflare API Token 只会显示一次，请立即复制保存。不要把 Token 发到公开群聊、博客正文、GitHub 仓库或截图里。
:::&lt;/p&gt;
&lt;h2&gt;五、在哪吒面板添加 DDNS 配置&lt;/h2&gt;
&lt;p&gt;登录哪吒 Dashboard，进入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;动态域名解析
→ 新配置
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按照下面这样填写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;名称：Cloudflare-DDNS
DDNS 供应商：cloudflare
域名：vps.example.com
最大重试次数：3
DDNS 凭据 1：留空
DDNS 凭据 2：填写 Cloudflare API Token
启用 DDNS IPv4：如果需要更新 A 记录就勾选
启用 DDNS IPv6：如果需要更新 AAAA 记录就勾选
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important
使用 Cloudflare 作为 DDNS 供应商时，&lt;code&gt;DDNS 凭据 1&lt;/code&gt; 一般留空，&lt;code&gt;DDNS 凭据 2&lt;/code&gt; 填 Cloudflare API Token。
:::&lt;/p&gt;
&lt;p&gt;如果你要同时更新多个域名，可以用英文逗号分隔：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vps.example.com,node1.example.com,home.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;六、把 DDNS 配置绑定到指定 VPS&lt;/h2&gt;
&lt;p&gt;添加 DDNS 配置后，还需要把它绑定到具体的服务器。&lt;/p&gt;
&lt;p&gt;进入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;服务器
→ 找到目标 VPS
→ 编辑
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后开启：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;启用 DDNS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并选择刚才创建的配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cloudflare-DDNS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存后，哪吒会在 Agent 上报 IP 变化时，自动更新 Cloudflare DNS 记录。&lt;/p&gt;
&lt;h2&gt;七、检查 DDNS 是否生效&lt;/h2&gt;
&lt;p&gt;可以先在哪吒面板里看日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;日志
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正常情况下，应该能看到类似内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;正在尝试更新域名(vps.example.com)DDNS
尝试更新域名(vps.example.com)DDNS成功
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以在本地终端检查解析结果。&lt;/p&gt;
&lt;p&gt;检查 IPv4：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dig vps.example.com A +short
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查 IPv6：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dig vps.example.com AAAA +short
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nslookup vps.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果返回的 IP 和 VPS 当前公网 IP 一致，就说明 DDNS 已经正常工作。&lt;/p&gt;
&lt;h2&gt;八、哪吒多久检测一次 IP？&lt;/h2&gt;
&lt;p&gt;哪吒不是秒级实时检测。默认情况下，Agent 的 IP 上报间隔通常是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1800 秒，也就是 30 分钟
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，默认逻辑大概是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VPS 公网 IP 变化
→ Agent 下次上报新 IP
→ Dashboard 根据 DDNS 配置更新 Cloudflare DNS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以默认情况下，IP 变化后可能需要最多约 30 分钟才会被发现。&lt;/p&gt;
&lt;p&gt;如果你想让它更及时，可以修改 Agent 的 IP 上报间隔。&lt;/p&gt;
&lt;p&gt;例如改成 60 秒：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip_report_period: 60
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改完成后重启哪吒 Agent：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart nezha-agent.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;推荐值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;普通 VPS：300 秒，也就是 5 分钟
希望更快发现 IP 变化：60 秒
不建议长期使用 30 秒，除非确实需要非常快
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::note
哪吒更新 Cloudflare DNS 记录后，客户端实际解析到新 IP 的时间还会受 DNS TTL 和本地 DNS 缓存影响。Cloudflare 里建议把相关记录的 TTL 设置为 &lt;code&gt;Auto&lt;/code&gt; 或 &lt;code&gt;1 分钟&lt;/code&gt;。
:::&lt;/p&gt;
&lt;h2&gt;九、IP 变化后可以收到通知吗？&lt;/h2&gt;
&lt;p&gt;可以。哪吒面板可以配置 IP 变更通知。&lt;/p&gt;
&lt;p&gt;大致入口是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;右上角头像
→ 系统设置
→ 系统配置
→ IP 变更提醒
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置项一般包括：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;覆盖范围：监控全部服务器，或只监控指定服务器
特定服务器：选择需要包含或排除的服务器
提醒发送至通知分组：选择已经配置好的通知组
启用功能：开启
通知中显示完整 IP 地址：按需开启
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在配置 IP 变更提醒之前，需要先配置通知方式。&lt;/p&gt;
&lt;p&gt;进入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;通知
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后添加你想使用的推送方式，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Telegram
Bark
Server 酱
企业微信
飞书
Slack
邮件
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终效果大概是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VPS 公网 IP 变化
→ 哪吒 Agent 检测并上报新 IP
→ 哪吒 Dashboard 更新 Cloudflare DNS
→ 哪吒发送 IP 变更通知
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
如果你找不到 &lt;code&gt;IP 变更提醒&lt;/code&gt;，不要只在 &lt;code&gt;通知&lt;/code&gt; 页面找。通知页面通常只是创建通知方式和通知组，IP 变更提醒本身一般在 &lt;code&gt;系统设置 → 系统配置&lt;/code&gt; 里。
:::&lt;/p&gt;
&lt;h2&gt;十、为什么我找不到 IP 变更提醒？&lt;/h2&gt;
&lt;p&gt;如果后台里看不到相关设置，可以按下面几个方向排查。&lt;/p&gt;
&lt;h3&gt;1. 是否使用管理员账号登录&lt;/h3&gt;
&lt;p&gt;IP 变更提醒属于系统配置，普通账号可能看不到。&lt;/p&gt;
&lt;p&gt;请使用管理员账号登录后台后再查看：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;右上角头像
→ 系统设置
→ 系统配置
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 是否在错误页面查找&lt;/h3&gt;
&lt;p&gt;很多人会去：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;通知
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面找 IP 变更提醒。&lt;/p&gt;
&lt;p&gt;但这个页面一般只是用来配置通知方式，例如 Telegram、Bark、邮件等。真正的 IP 变更提醒通常在系统设置里。&lt;/p&gt;
&lt;h3&gt;3. 是否界面语言不同&lt;/h3&gt;
&lt;p&gt;如果你使用英文界面，可能会显示为类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IP Change Notification
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以在系统设置页面用浏览器搜索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ctrl + F
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后搜索：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IP
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 是否哪吒版本较旧或使用了第三方改版&lt;/h3&gt;
&lt;p&gt;不同版本、不同主题、不同第三方镜像的菜单位置可能不完全一样。&lt;/p&gt;
&lt;p&gt;如果确实找不到，建议先查看当前面板版本，或者更新到官方较新的版本后再看。&lt;/p&gt;
&lt;h2&gt;十一、Komari 有类似功能吗？&lt;/h2&gt;
&lt;p&gt;目前 Komari 没有像哪吒面板这样内置 Cloudflare DDNS 的功能。&lt;/p&gt;
&lt;p&gt;Komari 更偏向服务器监控，虽然可以配合 Cloudflare Tunnel 暴露面板，也有一些社区工具可以把节点 IP 同步到 Git，但这些都不等于 Cloudflare DDNS。&lt;/p&gt;
&lt;p&gt;简单对比：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;面板&lt;/th&gt;
&lt;th&gt;是否内置 Cloudflare DDNS&lt;/th&gt;
&lt;th&gt;适合用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;哪吒面板&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;监控 + DDNS + 通知&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Komari&lt;/td&gt;
&lt;td&gt;暂不支持&lt;/td&gt;
&lt;td&gt;轻量监控&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Komari + ddns-go&lt;/td&gt;
&lt;td&gt;支持，靠 ddns-go 实现&lt;/td&gt;
&lt;td&gt;监控和 DDNS 分开管理&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果你使用 Komari，但也想做 Cloudflare DDNS，可以使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Komari + ddns-go
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Komari + 自写 Cloudflare API 脚本 + cron
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
如果只是为了 VPS 动态解析域名，&lt;code&gt;ddns-go&lt;/code&gt; 会更直接；如果你已经在用哪吒面板，则直接用哪吒内置 DDNS 更省事。
:::&lt;/p&gt;
&lt;h2&gt;十二、常见问题排查&lt;/h2&gt;
&lt;h3&gt;Cloudflare DNS 没有更新&lt;/h3&gt;
&lt;p&gt;优先检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cloudflare API Token 是否正确
Token 是否有 Zone Read 和 DNS Edit 权限
Token 的资源范围是否包含当前域名
哪吒 DDNS 配置里供应商是否选择 cloudflare
DDNS 凭据 2 是否填写了 Token
目标服务器是否启用了 DDNS
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;DDNS 日志显示失败&lt;/h3&gt;
&lt;p&gt;可以先确认 Token 权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Zone → Zone → Read
Zone → DNS → Edit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再确认域名确实托管在 Cloudflare，并且 DNS 记录已经存在。&lt;/p&gt;
&lt;h3&gt;IPv6 没有更新&lt;/h3&gt;
&lt;p&gt;检查 VPS 是否真的有公网 IPv6：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip -6 addr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以测试公网 IPv6：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -6 ip.sb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 VPS 没有公网 IPv6，就不要勾选 &lt;code&gt;启用 DDNS IPv6&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;更新成功但访问还是旧 IP&lt;/h3&gt;
&lt;p&gt;这通常是 DNS 缓存问题。&lt;/p&gt;
&lt;p&gt;可以尝试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dig vps.example.com A +short @1.1.1.1
dig vps.example.com A +short @8.8.8.8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果公共 DNS 已经返回新 IP，但你本地还是旧 IP，可以清理本地 DNS 缓存，或者等待 TTL 过期。&lt;/p&gt;
&lt;h3&gt;开了橙云后连不上&lt;/h3&gt;
&lt;p&gt;如果你是用这个域名连接 SSH、代理服务、游戏服或自定义端口，请把 Cloudflare 代理状态改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DNS only / 灰云
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不要使用橙云代理。&lt;/p&gt;
&lt;h2&gt;十三、推荐配置总结&lt;/h2&gt;
&lt;p&gt;我个人比较推荐这样配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Cloudflare DNS：灰云
TTL：Auto 或 1 分钟
哪吒 DDNS：启用 IPv4，按需启用 IPv6
Agent IP 上报间隔：300 秒
需要快速通知：改为 60 秒
IP 变更通知：开启
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你的 VPS IP 经常变动，建议使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip_report_period: 60
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 IP 很少变化，只是防止偶尔变动，可以使用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip_report_period: 300
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;十四、完整流程回顾&lt;/h2&gt;
&lt;p&gt;最后把完整流程压缩成一张清单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. Cloudflare 中添加 A / AAAA 记录
2. 创建 Cloudflare API Token
3. Token 权限设置为 Zone Read + DNS Edit
4. 哪吒 Dashboard 添加 Cloudflare DDNS 配置
5. DDNS 凭据 2 填写 Cloudflare API Token
6. 到服务器编辑页启用 DDNS
7. 检查日志是否更新成功
8. 根据需要调整 ip_report_period
9. 配置通知方式
10. 开启 IP 变更提醒
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样配置完成后，VPS 公网 IP 变化时，哪吒就可以自动更新 Cloudflare DNS，并在检测到 IP 变化后给你发送通知。&lt;/p&gt;
</content:encoded></item><item><title>Debian 12 VPS + Modal 部署 Docker 完整教程（免费额度内）</title><link>https://blog.yizong.de/posts/modal/</link><guid isPermaLink="true">https://blog.yizong.de/posts/modal/</guid><description>从一台全新的 Debian 12 VPS 出发，使用 Modal 部署 Docker 镜像，并给出一个尽量保持在线且不超过免费额度的稳定方案。</description><pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;这篇文章基于 Modal 官方文档和官方定价页整理，目标是：&lt;strong&gt;用一台全新的 Debian 12 VPS 当作控制端，在 Modal 上部署一个 Docker 镜像，并把费用控制在免费额度内。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;先说结论&lt;/h2&gt;
&lt;p&gt;如果你的目标是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;部署后服务一直可访问&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;尽量像“常驻”一样保持在线&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每月不超过 Modal Starter 的免费额度&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那么最稳的方案是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;modal deploy&lt;/code&gt; 做&lt;strong&gt;持久部署&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;modal.Image.from_registry()&lt;/code&gt; 直接引用现成镜像&lt;/li&gt;
&lt;li&gt;资源固定为 &lt;strong&gt;&lt;code&gt;cpu=0.125&lt;/code&gt; + &lt;code&gt;memory=128&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;配置 &lt;strong&gt;&lt;code&gt;min_containers=1&lt;/code&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;code&gt;max_containers=1&lt;/code&gt;&lt;/strong&gt;、&lt;strong&gt;&lt;code&gt;buffer_containers=0&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要指定区域&lt;/strong&gt;，除非你真的有延迟或合规需求&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套配置在 &lt;strong&gt;31 天整月 24x7&lt;/strong&gt; 的最保守口径下，单个常驻热容器大约是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不指定区域：约 &lt;code&gt;$5.13/月&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1.25x 区域：约 &lt;code&gt;$6.41/月&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2.5x 区域：约 &lt;code&gt;$12.82/月&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而 Modal Starter 当前自带 &lt;strong&gt;&lt;code&gt;$30/月&lt;/code&gt; 免费 compute credits&lt;/strong&gt;，所以&lt;strong&gt;一个最小规格的常驻热容器是明显在免费额度内的&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;你需要知道的 3 个前提&lt;/h2&gt;
&lt;h3&gt;1）你的 Debian 12 VPS 只是“控制端”&lt;/h3&gt;
&lt;p&gt;Modal 的工作方式不是“在你的 VPS 上一直跑 Docker 守护进程”，而是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你在 VPS 上安装 Modal CLI&lt;/li&gt;
&lt;li&gt;你在 VPS 上执行 &lt;code&gt;modal deploy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;真正的容器由 Modal 在云端运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以这台 Debian 12 VPS 更像“部署控制台”，不是运行容器的宿主机。&lt;/p&gt;
&lt;h3&gt;2）“保持运行”不等于“同一个容器永不重启”&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@modal.web_server(...)&lt;/code&gt; 本质上仍然属于 Modal 的 Web Endpoint，底层是&lt;strong&gt;自动扩缩容的容器池&lt;/strong&gt;。把 &lt;code&gt;min_containers=1&lt;/code&gt; 打开后，可以做到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务一直有 URL&lt;/li&gt;
&lt;li&gt;至少保留 1 个 warm container&lt;/li&gt;
&lt;li&gt;空闲时不缩到 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但它&lt;strong&gt;不保证永远是同一个容器&lt;/strong&gt;。你应该把它理解成“服务持续可用”，而不是“这一台容器永不被替换”。&lt;/p&gt;
&lt;h3&gt;3）为什么这套方案能控制在免费额度内&lt;/h3&gt;
&lt;p&gt;Modal 当前公开价格里：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU：&lt;code&gt;$0.0000131 / core / sec&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Memory：&lt;code&gt;$0.00000222 / GiB / sec&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Starter：&lt;code&gt;$30 / month free credits&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而默认最小资源请求是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;0.125 CPU&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;128 MiB 内存&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以只保留 &lt;strong&gt;1 个最小规格热容器&lt;/strong&gt;，即便整月不停，仍然离 &lt;code&gt;$30&lt;/code&gt; 免费额度有比较大的安全边际。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;一、在全新的 Debian 12 VPS 上安装环境&lt;/h2&gt;
&lt;p&gt;先登录你的 Debian 12 VPS，执行下面这组命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt update
apt install -y python3 python3-pip python3-venv git curl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后创建一个 Python 虚拟环境，避免污染系统环境：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m venv ~/modal-venv
source ~/modal-venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 Modal CLI：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m pip install -U pip modal
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化登录：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m modal setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你不方便在浏览器里登录，也可以使用 Token 方式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m modal token set
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;二、创建项目目录&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/modal-docker-demo
cd ~/modal-docker-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;三、准备你的镜像信息&lt;/h2&gt;
&lt;p&gt;这篇教程使用的是“&lt;strong&gt;直接引用现成镜像&lt;/strong&gt;”的方式，也就是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你已经有一个公开镜像，例如 Docker Hub 或 GHCR&lt;/li&gt;
&lt;li&gt;Modal 直接从镜像仓库拉取这个镜像&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最典型的形式是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Docker Hub: your-user/your-image:latest
GHCR:      ghcr.io/your-user/your-image:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果镜像本身没有合适的 Python 运行时，Modal 官方支持在 &lt;code&gt;from_registry()&lt;/code&gt; 里通过 &lt;code&gt;add_python=&quot;3.11&quot;&lt;/code&gt; 补进去。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;四、写一份“免费额度内、尽量常驻”的 &lt;code&gt;modal_app.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;下面这份是本文的推荐配置。&lt;/p&gt;
&lt;p&gt;你只需要改 4 个地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;APP_NAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IMAGE_REF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PORT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;START_CMD&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把下面内容保存成 &lt;code&gt;modal_app.py&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import subprocess
import modal

APP_NAME = &quot;docker-on-modal-min&quot;
IMAGE_REF = &quot;ghcr.io/your-user/your-image:latest&quot;   # 改成你的镜像
PORT = 3000                                           # 改成你的容器实际监听端口
START_CMD = [&quot;node&quot;, &quot;index.js&quot;]                    # 改成你的启动命令

app = modal.App(APP_NAME)

image = modal.Image.from_registry(
    IMAGE_REF,
    add_python=&quot;3.11&quot;,  # 让现成镜像更容易兼容 Modal Function
)

@app.function(
    image=image,
    cpu=0.125,
    memory=128,
    min_containers=1,
    max_containers=1,
    buffer_containers=0,
    timeout=24 * 60 * 60,
)
@modal.web_server(PORT)
def serve():
    print(&quot;provider =&quot;, os.environ.get(&quot;MODAL_CLOUD_PROVIDER&quot;))
    print(&quot;region   =&quot;, os.environ.get(&quot;MODAL_REGION&quot;))
    subprocess.Popen(START_CMD)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;这份配置为什么这样写&lt;/h3&gt;
&lt;h4&gt;&lt;code&gt;cpu=0.125&lt;/code&gt; + &lt;code&gt;memory=128&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;这是 Modal 当前默认最小资源请求。显式写出来有两个好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;成本预估更清楚&lt;/li&gt;
&lt;li&gt;后面自己回看代码时不会误会&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;min_containers=1&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;这会让函数&lt;strong&gt;至少保留 1 个 warm container&lt;/strong&gt;，即使没有请求，也不会缩到 0。对于“希望尽量持续在线”的场景，这个参数最关键。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;max_containers=1&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;这会把容器上限固定为 1，避免流量稍微高一点就自动扩成多个容器，从而出现意外账单。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;buffer_containers=0&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;不预留额外容器。因为我们这篇教程的目标是&lt;strong&gt;免费额度内稳定运行&lt;/strong&gt;，不是极致低延迟。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;timeout=24 * 60 * 60&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Modal Function 默认执行超时是 300 秒。手动拉到 24 小时，是为了尽量减少过早回收带来的不确定性。&lt;/p&gt;
&lt;h4&gt;为什么这里没有写 &lt;code&gt;region&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;因为只要你显式指定区域，就会触发区域价格乘数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;US / EU / UK / AP&lt;/code&gt;：&lt;strong&gt;1.25x&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CA / SA / ME / MX / AF&lt;/code&gt;：&lt;strong&gt;2.5x&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你的目标是“&lt;strong&gt;优先稳在免费额度内&lt;/strong&gt;”，那么&lt;strong&gt;不指定区域最省钱&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;五、部署到 Modal&lt;/h2&gt;
&lt;p&gt;确保你当前还在虚拟环境里：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;source ~/modal-venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行部署：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m modal deploy modal_app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;部署完成后，Modal 会为这个 Web Endpoint 分配可访问的 URL。即使你关闭 SSH 会话，&lt;strong&gt;这个部署本身仍然存在&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;六、如何查看日志、状态和停止部署&lt;/h2&gt;
&lt;h3&gt;查看 App 列表&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal app list
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实时查看日志&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal app logs docker-on-modal-min -f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看容器列表&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal container list
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看某个容器日志&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal container logs ta-xxxxxxxx -f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;停止部署&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal app stop docker-on-modal-min
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;modal app stop&lt;/code&gt; 是永久停止当前部署，后面如果还要跑，需要重新 &lt;code&gt;modal deploy&lt;/code&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;七、怎么确认费用不会超出免费额度&lt;/h2&gt;
&lt;h3&gt;方案 A：本文推荐的“常驻热容器版”&lt;/h3&gt;
&lt;p&gt;配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cpu=0.125&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memory=128&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min_containers=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_containers=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;不指定区域&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;按 &lt;strong&gt;31 天整月不停机&lt;/strong&gt; 来算，约 &lt;strong&gt;&lt;code&gt;$5.13/月&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这比 Starter 自带的 &lt;strong&gt;&lt;code&gt;$30/月&lt;/code&gt; 免费 credits`&lt;/strong&gt; 低很多，所以&lt;strong&gt;长期放 1 个最小规格热容器是安全的&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;方案 B：如果你必须指定区域&lt;/h3&gt;
&lt;p&gt;如果你写：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;region=&quot;ap&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么会触发 &lt;strong&gt;1.25x&lt;/strong&gt; 的区域乘数。此时单个最小规格热容器大约 &lt;strong&gt;&lt;code&gt;$6.41/月&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果是 2.5x 的区域类别，则约 &lt;strong&gt;&lt;code&gt;$12.82/月&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;结论依然是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1 个最小规格热容器&lt;/strong&gt;，即便指定区域，仍然通常在免费额度内&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要把 &lt;code&gt;max_containers&lt;/code&gt; 调大&lt;/strong&gt;，否则费用会开始线性上涨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;方案 C：如果你更在意费用，而不是“热容器常驻”&lt;/h3&gt;
&lt;p&gt;把 &lt;code&gt;min_containers=1&lt;/code&gt; 去掉：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.function(
    image=image,
    cpu=0.125,
    memory=128,
    max_containers=1,
    buffer_containers=0,
    timeout=24 * 60 * 60,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这时服务仍然是&lt;strong&gt;持久部署&lt;/strong&gt;，URL 一直有效，但容器在空闲时会缩到 0。这样通常更便宜，不过首次请求会有冷启动。&lt;/p&gt;
&lt;p&gt;如果你只是个人项目、低频访问站点，实际上&lt;strong&gt;这是最省钱的方案&lt;/strong&gt;。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;八、如何查看本月账单&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;python -m modal billing report --for &quot;this month&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想看今天的小时级用量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m modal billing report --for today -r h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建议你在刚部署后的前几天每天看一次账单，确认没有因为镜像本身的高负载把 CPU 或内存实际使用拉高。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modal 官方文档写明：CPU 和内存是按“&lt;strong&gt;请求值&lt;/strong&gt;”和“&lt;strong&gt;实际使用值&lt;/strong&gt;”里更高的那个计费。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;九、常见问题&lt;/h2&gt;
&lt;h3&gt;Q1：为什么我已经 &lt;code&gt;modal deploy&lt;/code&gt; 了，但感觉容器还是会变？&lt;/h3&gt;
&lt;p&gt;因为 Modal 的核心模型不是传统 VPS，而是&lt;strong&gt;服务持续存在、容器由平台调度维护&lt;/strong&gt;。&lt;code&gt;min_containers=1&lt;/code&gt; 只能保证有至少 1 个 warm container，不代表某个容器实例永远不被替换。&lt;/p&gt;
&lt;h3&gt;Q2：为什么我不推荐一开始就锁到 Tokyo、Seoul 这类更细区域？&lt;/h3&gt;
&lt;p&gt;因为区域越细，资源池越小；官方也建议优先使用更宽泛的区域，这通常会更有利于可用性和冷启动表现。并且一旦显式指定区域，就会引入价格乘数。&lt;/p&gt;
&lt;h3&gt;Q3：为什么我不用自己的 Docker 直接在 VPS 上跑？&lt;/h3&gt;
&lt;p&gt;当然可以，但那是另一条路线。本文这条路的核心价值是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 VPS 只做“控制台”&lt;/li&gt;
&lt;li&gt;把运行、扩缩容、日志、部署 URL 交给 Modal&lt;/li&gt;
&lt;li&gt;省掉自己维护公网服务、进程守护和扩缩容的工作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Q4：如果我想把现成 Dockerfile 直接丢给 Modal 呢？&lt;/h3&gt;
&lt;p&gt;也可以，Modal 还支持 &lt;code&gt;modal.Image.from_dockerfile(&quot;./Dockerfile&quot;)&lt;/code&gt;。但如果你已经有 GHCR / Docker Hub 镜像，直接 &lt;code&gt;from_registry()&lt;/code&gt; 通常更省事。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;十、一份可直接复制的完整流程&lt;/h2&gt;
&lt;h3&gt;1）安装环境&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apt update
apt install -y python3 python3-pip python3-venv git curl
python3 -m venv ~/modal-venv
source ~/modal-venv/bin/activate
python -m pip install -U pip modal
python -m modal setup
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2）创建项目&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/modal-docker-demo
cd ~/modal-docker-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3）写 &lt;code&gt;modal_app.py&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import os
import subprocess
import modal

APP_NAME = &quot;docker-on-modal-min&quot;
IMAGE_REF = &quot;ghcr.io/your-user/your-image:latest&quot;
PORT = 3000
START_CMD = [&quot;node&quot;, &quot;index.js&quot;]

app = modal.App(APP_NAME)

image = modal.Image.from_registry(
    IMAGE_REF,
    add_python=&quot;3.11&quot;,
)

@app.function(
    image=image,
    cpu=0.125,
    memory=128,
    min_containers=1,
    max_containers=1,
    buffer_containers=0,
    timeout=24 * 60 * 60,
)
@modal.web_server(PORT)
def serve():
    print(&quot;provider =&quot;, os.environ.get(&quot;MODAL_CLOUD_PROVIDER&quot;))
    print(&quot;region   =&quot;, os.environ.get(&quot;MODAL_REGION&quot;))
    subprocess.Popen(START_CMD)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4）部署&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;source ~/modal-venv/bin/activate
python -m modal deploy modal_app.py
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5）看日志&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal app logs docker-on-modal-min -f
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6）看账单&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python -m modal billing report --for &quot;this month&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;最后的建议&lt;/h2&gt;
&lt;p&gt;如果你只有一个小站点、一个小 API，或者只是想把一个 Docker 镜像稳定挂在公网：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优先用最小资源&lt;/strong&gt;：&lt;code&gt;0.125 CPU + 128 MiB&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先锁死容器数&lt;/strong&gt;：&lt;code&gt;max_containers=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;想更像常驻就加&lt;/strong&gt;：&lt;code&gt;min_containers=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;想更省钱就去掉&lt;/strong&gt;：&lt;code&gt;min_containers=1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有强需求就不要指定区域&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样做，基本就能在 Modal 的免费额度内，把一个最小规格的 Docker 服务长期挂住。&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Modal 官方文档总览：&lt;code&gt;https://modal.com/docs/guide&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用现成镜像：&lt;code&gt;https://modal.com/docs/guide/existing-images&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;图片与镜像 API：&lt;code&gt;https://modal.com/docs/reference/modal.Image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Web endpoints：&lt;code&gt;https://modal.com/docs/guide/webhooks&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modal.web_server&lt;/code&gt;：&lt;code&gt;https://modal.com/docs/reference/modal.web_server&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;部署管理：&lt;code&gt;https://modal.com/docs/guide/managing-deployments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modal app&lt;/code&gt; CLI：&lt;code&gt;https://modal.com/docs/reference/cli/app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;modal container&lt;/code&gt; CLI：&lt;code&gt;https://modal.com/docs/reference/cli/container&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;资源配置与计费：&lt;code&gt;https://modal.com/docs/guide/resources&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;自动扩缩容：&lt;code&gt;https://modal.com/docs/guide/scale&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;冷启动与 warm container：&lt;code&gt;https://modal.com/docs/guide/cold-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;区域选择：&lt;code&gt;https://modal.com/docs/guide/region-selection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Function 超时：&lt;code&gt;https://modal.com/docs/guide/timeouts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pricing：&lt;code&gt;https://modal.com/pricing&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Debian12 VPS 添加 HE IPv6 隧道并保持运行</title><link>https://blog.yizong.de/posts/he-ipv6/</link><guid isPermaLink="true">https://blog.yizong.de/posts/he-ipv6/</guid><description>给只有 IPv4 的 Debian 12 VPS 配置 Hurricane Electric Tunnelbroker IPv6，并用 ifupdown 与 systemd 定时器保持运行</description><pubDate>Sun, 26 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::important
请注意，本文适用于 &lt;strong&gt;Debian 12 纯 IPv4 VPS&lt;/strong&gt;。如果你的 VPS 已经自带原生 IPv6，一般不需要使用 HE Tunnelbroker。&lt;/p&gt;
&lt;p&gt;本文方案不会接管主网卡，也不会重启主网络服务，目的是尽量避免远程 SSH 断连。
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;操作环境&lt;/strong&gt;：Debian 12 KVM VPS / 仅 IPv4 入口&lt;br /&gt;
&lt;strong&gt;目标&lt;/strong&gt;：给纯 IPv4 VPS 添加 HE Tunnelbroker IPv6，并在重启后自动恢复&lt;br /&gt;
&lt;strong&gt;核心方案&lt;/strong&gt;：&lt;code&gt;ifupdown v4tunnel&lt;/code&gt; + &lt;code&gt;systemd timer watchdog&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;一、准备 HE Tunnelbroker 参数&lt;/h2&gt;
&lt;p&gt;先到 &lt;a href=&quot;https://tunnelbroker.net/&quot;&gt;HE Tunnelbroker&lt;/a&gt; 创建一个 Regular Tunnel。&lt;/p&gt;
&lt;p&gt;创建完成后，在隧道详情页记录下面 4 个核心参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server IPv4 Address
Client IPv4 Address
Server IPv6 Address
Client IPv6 Address
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server IPv4 Address: 74.82.46.6
Client IPv4 Address: 162.43.91.133
Server IPv6 Address: 2001:470:23:543::1/64
Client IPv6 Address: 2001:470:23:543::2/64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;脚本里填写 IPv6 时，不要带 &lt;code&gt;/64&lt;/code&gt; 后缀，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2001:470:23:543::2
2001:470:23:543::1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::important
HE 的 6in4 隧道使用的是 &lt;strong&gt;IPv4 protocol 41&lt;/strong&gt;，不是 TCP 41 端口，也不是 UDP 41 端口。&lt;/p&gt;
&lt;p&gt;如果 VPS 服务商或上游网络不放行 protocol 41，隧道接口可能能创建成功，但 IPv6 不会真正通。
:::&lt;/p&gt;
&lt;h2&gt;二、确认 VPS 当前公网 IPv4&lt;/h2&gt;
&lt;p&gt;在 VPS 上执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip -4 addr
ip route
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果已经安装 &lt;code&gt;curl&lt;/code&gt;，也可以查看公网出口 IP：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -4 https://ifconfig.co
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确认结果要和 HE 后台的 &lt;code&gt;Client IPv4 Address&lt;/code&gt; 一致。&lt;/p&gt;
&lt;p&gt;如果不一致，需要先在 HE 后台修改 &lt;code&gt;Client IPv4 Address&lt;/code&gt;，否则隧道不会正常回包。&lt;/p&gt;
&lt;h2&gt;三、一键安装脚本&lt;/h2&gt;
&lt;p&gt;下面脚本适合全新的 Debian 12 VPS 使用。&lt;/p&gt;
&lt;p&gt;它会做这些事情：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装最小依赖：&lt;code&gt;ifupdown&lt;/code&gt;、&lt;code&gt;iproute2&lt;/code&gt;、&lt;code&gt;iputils-ping&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;写入 &lt;code&gt;/etc/network/interfaces.d/he-ipv6&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;只拉起 &lt;code&gt;he-ipv6&lt;/code&gt; 隧道，不重启主网卡&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;networking&lt;/code&gt;，保证重启后自动拉起&lt;/li&gt;
&lt;li&gt;创建 &lt;code&gt;systemd timer&lt;/code&gt;，每 2 分钟检查一次 IPv6，不通则自动重建隧道&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::important
执行前请先修改脚本开头的 4 个参数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CLIENT4=&quot;你的 Client IPv4&quot;
SERVER4=&quot;HE 的 Server IPv4&quot;
CLIENT6=&quot;你的 Client IPv6，不带 /64&quot;
SERVER6=&quot;HE 的 Server IPv6，不带 /64&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;gt; /root/install-he-ipv6.sh &amp;lt;&amp;lt;&apos;EOF&apos;
#!/bin/sh
set -eu

TUN=&quot;he-ipv6&quot;

# ====== 请修改下面 4 个参数 ======
CLIENT4=&quot;YOUR_CLIENT_IPV4&quot;
SERVER4=&quot;YOUR_SERVER_IPV4&quot;
CLIENT6=&quot;YOUR_CLIENT_IPV6&quot;
SERVER6=&quot;YOUR_SERVER_IPV6&quot;
# =================================

# 用于 watchdog 测试 IPv6 出网，不依赖 DNS
TEST6=&quot;2001:4860:4860::8888&quot;

if [ &quot;$(id -u)&quot; != &quot;0&quot; ]; then
    echo &quot;请使用 root 用户执行本脚本。&quot;
    exit 1
fi

echo &quot;[1/9] 检查并安装最小依赖...&quot;
NEED_APT=0
command -v ifup &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || NEED_APT=1
command -v ifdown &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || NEED_APT=1
command -v ip &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || NEED_APT=1
command -v ping &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || NEED_APT=1

if [ &quot;$NEED_APT&quot; = &quot;1&quot; ]; then
    apt-get update
    apt-get install -y ifupdown iproute2 iputils-ping
fi

echo &quot;[2/9] 备份现有网络配置...&quot;
BACKUP_DIR=&quot;/root/he-ipv6-backup-$(date +%Y%m%d-%H%M%S)&quot;
mkdir -p &quot;$BACKUP_DIR&quot;
cp -a /etc/network/interfaces &quot;$BACKUP_DIR/interfaces&quot; 2&amp;gt;/dev/null || true
cp -a /etc/network/interfaces.d &quot;$BACKUP_DIR/interfaces.d&quot; 2&amp;gt;/dev/null || true

echo &quot;[3/9] 开启 IPv6，并关闭 rp_filter 干扰...&quot;
cat &amp;gt; /etc/sysctl.d/99-he-ipv6.conf &amp;lt;&amp;lt;EON
net.ipv6.conf.all.disable_ipv6=0
net.ipv6.conf.default.disable_ipv6=0
net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.default.rp_filter=0
EON
sysctl -p /etc/sysctl.d/99-he-ipv6.conf &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true
modprobe sit 2&amp;gt;/dev/null || true

echo &quot;[4/9] 准备 /etc/network/interfaces.d ...&quot;
mkdir -p /etc/network/interfaces.d

if [ ! -f /etc/network/interfaces ]; then
    cat &amp;gt; /etc/network/interfaces &amp;lt;&amp;lt;&apos;EON&apos;
auto lo
iface lo inet loopback

source /etc/network/interfaces.d/*
EON
else
    grep -q &apos;^source /etc/network/interfaces.d/\*&apos; /etc/network/interfaces 2&amp;gt;/dev/null || \
    echo &apos;source /etc/network/interfaces.d/*&apos; &amp;gt;&amp;gt; /etc/network/interfaces
fi

echo &quot;[5/9] 写入 HE IPv6 隧道配置...&quot;
cat &amp;gt; /etc/network/interfaces.d/he-ipv6 &amp;lt;&amp;lt;EON
auto ${TUN}
iface ${TUN} inet6 v4tunnel
        address ${CLIENT6}
        netmask 64
        endpoint ${SERVER4}
        local ${CLIENT4}
        ttl 255
        gateway ${SERVER6}
        pre-up ip tunnel del ${TUN} 2&amp;gt;/dev/null || true
        post-down ip tunnel del ${TUN} 2&amp;gt;/dev/null || true
EON

echo &quot;[6/9] 创建 watchdog 自恢复脚本...&quot;
cat &amp;gt; /usr/local/sbin/he-ipv6-watchdog.sh &amp;lt;&amp;lt;EON
#!/bin/sh
set -eu

TUN=&quot;${TUN}&quot;
TEST6=&quot;${TEST6}&quot;

need_restart=0

if ! ip link show &quot;\$TUN&quot; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    need_restart=1
fi

if ! ip -6 route show default 2&amp;gt;/dev/null | grep -q &quot;dev \$TUN&quot;; then
    need_restart=1
fi

if ! ping -6 -c 1 -W 3 &quot;\$TEST6&quot; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
    need_restart=1
fi

if [ &quot;\$need_restart&quot; = &quot;1&quot; ]; then
    echo &quot;[he-ipv6-watchdog] IPv6 tunnel seems down, rebuilding...&quot;
    ifdown --force &quot;\$TUN&quot; 2&amp;gt;/dev/null || true
    ip tunnel del &quot;\$TUN&quot; 2&amp;gt;/dev/null || true
    ifup &quot;\$TUN&quot; 2&amp;gt;/dev/null || true
else
    echo &quot;[he-ipv6-watchdog] IPv6 tunnel is OK.&quot;
fi
EON
chmod +x /usr/local/sbin/he-ipv6-watchdog.sh

echo &quot;[7/9] 创建 systemd timer...&quot;
cat &amp;gt; /etc/systemd/system/he-ipv6-watchdog.service &amp;lt;&amp;lt;&apos;EON&apos;
[Unit]
Description=Check and recover HE IPv6 tunnel

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/he-ipv6-watchdog.sh
EON

cat &amp;gt; /etc/systemd/system/he-ipv6-watchdog.timer &amp;lt;&amp;lt;&apos;EON&apos;
[Unit]
Description=Run HE IPv6 tunnel watchdog periodically

[Timer]
OnBootSec=60
OnUnitActiveSec=120
AccuracySec=15
Persistent=true

[Install]
WantedBy=timers.target
EON

systemctl daemon-reload

echo &quot;[8/9] 拉起 HE IPv6 隧道，只操作 he-ipv6，不重启主网络...&quot;
ip tunnel del &quot;${TUN}&quot; 2&amp;gt;/dev/null || true
ifdown --force &quot;${TUN}&quot; 2&amp;gt;/dev/null || true
ifup &quot;${TUN}&quot;

echo &quot;[9/9] 启用开机自启和 watchdog...&quot;
systemctl enable networking &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true
systemctl enable --now he-ipv6-watchdog.timer &amp;gt;/dev/null

echo
echo &quot;[OK] HE IPv6 隧道配置完成。&quot;
echo
echo &quot;===== tunnel =====&quot;
ip tunnel show &quot;${TUN}&quot; || true
echo
echo &quot;===== address =====&quot;
ip -6 addr show dev &quot;${TUN}&quot; || true
echo
echo &quot;===== route =====&quot;
ip -6 route || true
echo
echo &quot;===== test =====&quot;
ping -6 -c 3 &quot;${TEST6}&quot; || true
echo
echo &quot;如果上面的 ping 有回包，可以继续测试：&quot;
echo &quot;  getent hosts ipv6.google.com&quot;
echo &quot;  ping -6 -c 3 ipv6.google.com&quot;
echo &quot;  curl -6 https://ifconfig.co&quot;
EOF

chmod +x /root/install-he-ipv6.sh
/root/install-he-ipv6.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;四、使用示例&lt;/h2&gt;
&lt;p&gt;假设你的 HE 后台信息如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server IPv4 Address: 74.82.46.6
Client IPv4 Address: 162.43.91.133
Server IPv6 Address: 2001:470:23:543::1/64
Client IPv6 Address: 2001:470:23:543::2/64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么脚本开头应改成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CLIENT4=&quot;162.43.91.133&quot;
SERVER4=&quot;74.82.46.6&quot;
CLIENT6=&quot;2001:470:23:543::2&quot;
SERVER6=&quot;2001:470:23:543::1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x /root/install-he-ipv6.sh
/root/install-he-ipv6.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;五、测试 IPv6 是否成功&lt;/h2&gt;
&lt;p&gt;先测试纯 IPv6 地址，不依赖 DNS：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping -6 -c 3 2001:4860:4860::8888
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再测试域名解析与 IPv6 出口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;getent hosts ipv6.google.com
ping -6 -c 3 ipv6.google.com
curl -6 https://ifconfig.co
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 &lt;code&gt;curl -6 https://ifconfig.co&lt;/code&gt; 返回你的 HE Client IPv6，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2001:470:23:543::2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明 IPv6 已经配置成功。&lt;/p&gt;
&lt;p&gt;:::tip
有些情况下，&lt;code&gt;ping -6 HE 的 Server IPv6&lt;/code&gt; 可能不回包，但外网 IPv6 是通的。&lt;/p&gt;
&lt;p&gt;最终建议以这两个结果为准：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping -6 -c 3 2001:4860:4860::8888
curl -6 https://ifconfig.co
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h2&gt;六、DNS 解析失败时的处理&lt;/h2&gt;
&lt;p&gt;如果出现这种情况：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping -6 2001:4860:4860::8888 能通
ping -6 ipv6.google.com 不通
curl -6 https://ifconfig.co 提示 Could not resolve host
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;说明 IPv6 已经通了，问题只是 DNS。&lt;/p&gt;
&lt;p&gt;可以直接写一个静态 &lt;code&gt;/etc/resolv.conf&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -f /etc/resolv.conf

cat &amp;gt; /etc/resolv.conf &amp;lt;&amp;lt;&apos;EOF&apos;
nameserver 2001:470:20::2
nameserver 2606:4700:4700::1111
nameserver 2001:4860:4860::8888
nameserver 74.82.42.42
nameserver 1.1.1.1
nameserver 8.8.8.8
options timeout:2 attempts:2
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后重新测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;getent hosts ipv6.google.com
ping -6 -c 3 ipv6.google.com
curl -6 https://ifconfig.co
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;七、查看运行状态&lt;/h2&gt;
&lt;p&gt;查看隧道：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip tunnel show he-ipv6
ip -6 addr show dev he-ipv6
ip -6 route
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 watchdog 定时器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl status he-ipv6-watchdog.timer --no-pager
systemctl list-timers | grep he-ipv6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 watchdog 日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;journalctl -u he-ipv6-watchdog.service -n 50 --no-pager
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;手动重建隧道：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ifdown --force he-ipv6 2&amp;gt;/dev/null || true
ip tunnel del he-ipv6 2&amp;gt;/dev/null || true
ifup he-ipv6
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;八、故障排查&lt;/h2&gt;
&lt;h3&gt;1. 提示 Network is unreachable&lt;/h3&gt;
&lt;p&gt;说明本机没有 IPv6 默认路由。&lt;/p&gt;
&lt;p&gt;检查：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip -6 route
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正常应看到类似：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;default via 2001:470:23:543::1 dev he-ipv6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有，重新拉起隧道：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ifdown --force he-ipv6 2&amp;gt;/dev/null || true
ip tunnel del he-ipv6 2&amp;gt;/dev/null || true
ifup he-ipv6
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. IPv6 路由有了，但 ping 外网 100% 丢包&lt;/h3&gt;
&lt;p&gt;先确认 IPv4 出口是否和 HE 后台一致：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip route get HE_SERVER_IPV4
curl -4 https://ifconfig.co
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip route get 74.82.46.6
curl -4 https://ifconfig.co
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 &lt;code&gt;curl -4&lt;/code&gt; 返回的 IPv4 不是 HE 后台的 &lt;code&gt;Client IPv4 Address&lt;/code&gt;，需要去 HE 后台更新 Client IPv4。&lt;/p&gt;
&lt;h3&gt;3. 抓包确认 protocol 41 是否有回包&lt;/h3&gt;
&lt;p&gt;安装 tcpdump 后可以抓包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt install -y tcpdump

tcpdump -ni any &apos;ip proto 41 or host HE_SERVER_IPV4&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另开一个窗口执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ping -6 -c 3 HE_SERVER_IPV6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果只看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Out IP CLIENT_IPV4 &amp;gt; SERVER_IPV4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;没有看到：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;In IP SERVER_IPV4 &amp;gt; CLIENT_IPV4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常说明 HE 后台 endpoint 没生效，或者上游网络没有把 protocol 41 回包送回来。&lt;/p&gt;
&lt;p&gt;可以尝试在 HE 后台重新保存 &lt;code&gt;Client IPv4 Address&lt;/code&gt;，或者删除重建 tunnel。&lt;/p&gt;
&lt;h2&gt;九、卸载脚本&lt;/h2&gt;
&lt;p&gt;如果以后不用 HE IPv6，可以执行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl disable --now he-ipv6-watchdog.timer 2&amp;gt;/dev/null || true
rm -f /etc/systemd/system/he-ipv6-watchdog.service
rm -f /etc/systemd/system/he-ipv6-watchdog.timer
rm -f /usr/local/sbin/he-ipv6-watchdog.sh
rm -f /etc/network/interfaces.d/he-ipv6
systemctl daemon-reload

ifdown --force he-ipv6 2&amp;gt;/dev/null || true
ip tunnel del he-ipv6 2&amp;gt;/dev/null || true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;十、关于 Routed /64 和 LXC 容器&lt;/h2&gt;
&lt;p&gt;HE 一般会给两段 IPv6：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Tunnel /64：用于 he-ipv6 隧道本身
Routed /64：用于分配给网站、Docker、LXC、虚拟机等业务
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Tunnel Client IPv6: 2001:470:23:543::2
Routed /64:         2001:470:24:543::/64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果以后要给 LXC 容器分配公网 IPv6，建议使用 &lt;code&gt;Routed /64&lt;/code&gt;，不要直接使用 tunnel 的 &lt;code&gt;/64&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;宿主机网关：2001:470:24:543::1
容器 101： 2001:470:24:543::101
容器 102： 2001:470:24:543::102
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;本文方案的核心是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Debian 12 纯 IPv4 VPS
        ↓
HE Tunnelbroker 6in4
        ↓
/etc/network/interfaces.d/he-ipv6
        ↓
systemd timer 定时检查并自动恢复
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它的优点是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不重启主网络&lt;/li&gt;
&lt;li&gt;不接管主网卡&lt;/li&gt;
&lt;li&gt;SSH 断连风险低&lt;/li&gt;
&lt;li&gt;重启后可自动恢复&lt;/li&gt;
&lt;li&gt;隧道异常时会定时自修复&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要注意的是：DD 重装系统会清空系统文件，所以重装后需要重新执行本文的一键脚本。&lt;/p&gt;
</content:encoded></item><item><title>Linux手动安装3x-ui</title><link>https://blog.yizong.de/posts/3x-ui/</link><guid isPermaLink="true">https://blog.yizong.de/posts/3x-ui/</guid><description>linux无视系统限制手动安装3x-ui</description><pubDate>Thu, 02 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::important
请注意，以下命令适用于基于&lt;strong&gt;Linux&lt;/strong&gt;的系统。对于其他操作系统，操作命令可能有所不同。
&amp;lt;br /&amp;gt;本教程适用Alpine、Debian、Ubuntu......
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;操作环境&lt;/strong&gt;：Alpine3.20 LXC 1C128M2G/nat-ipv4
&amp;lt;br/&amp;gt;&lt;strong&gt;目标&lt;/strong&gt;：手动安装3x-ui&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第一步：更新LXC环境&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;apk update
apk add curl wget unzip systemd
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第二步：安装3x-ui&lt;/h2&gt;
&lt;h3&gt;1. 下载3x-ui包，上传到LXC&lt;/h3&gt;
&lt;p&gt;下载地址：https://pan.yizong.de/BlogAPP/xui.tar.gz
&amp;lt;br/&amp;gt;该3x-ui包为手动编译，不依赖 glibc
&amp;lt;br/&amp;gt;下载后通过ssh工具上传到/opt目录&lt;/p&gt;
&lt;h3&gt;2. 开始安装&lt;/h3&gt;
&lt;h4&gt;解压，并赋予可执行权限&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p /opt/3x-ui
mkdir -p /opt/3x-ui/bin
tar -xvzf /opt/xui.tar.gz -C /opt/3x-ui
cd /opt/3x-ui
chmod +x x-ui
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;初始化&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; /opt/3x-ui/bin/config.json
{
  &quot;log&quot;: { &quot;loglevel&quot;: &quot;info&quot; },
  &quot;inbounds&quot;: [
    {
      &quot;port&quot;: 1080,
      &quot;protocol&quot;: &quot;socks&quot;,
      &quot;settings&quot;: {
        &quot;auth&quot;: &quot;noauth&quot;,
        &quot;udp&quot;: true,
        &quot;ip&quot;: &quot;127.0.0.1&quot;
      }
    }
  ],
  &quot;outbounds&quot;: [
    {
      &quot;protocol&quot;: &quot;freedom&quot;,
      &quot;settings&quot;: {}
    }
  ]
}
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;更改面板端口&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/3x-ui
./x-ui setting -port 30000
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;开始运行3x-ui&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/3x-ui
./x-ui
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;后台运行&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;nohup ./x-ui run &amp;gt; /var/log/x-ui.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::tip
如果你的系统没有systemctl就不用执行下面这两步
:::&lt;/p&gt;
&lt;h4&gt;开机自启&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/systemd/system/x-ui.service
[Unit]
Description=3-XUI Panel
After=network.target

[Service]
Type=simple
ExecStart=/opt/3x-ui/x-ui run
WorkingDirectory=/opt/3x-ui
Restart=always

[Install]
WantedBy=multi-user.target
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;启用并启动服务&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reexec
systemctl enable --now x-ui
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第三步：访问并设置面板&lt;/h2&gt;
&lt;p&gt;浏览器访问：http://你的IP:30000
&amp;lt;br/&amp;gt;用户admin/密码admin
&lt;strong&gt;请立即修改密码&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1.安装xray&lt;/h3&gt;
&lt;p&gt;点击面板系统状态-xray-后面的版本号，选择一个点击&lt;/p&gt;
&lt;h3&gt;2.更新geoip.dat&lt;/h3&gt;
&lt;p&gt;面板系统状态有个扳手，点进去只更新&lt;strong&gt;geoip.dat&lt;/strong&gt;，&lt;strong&gt;其余不要更新&lt;/strong&gt;
&amp;lt;br/&amp;gt;重启xray和面板即可正常使用&lt;/p&gt;
</content:encoded></item><item><title>VPS DD Windows10精简版</title><link>https://blog.yizong.de/posts/dd/</link><guid isPermaLink="true">https://blog.yizong.de/posts/dd/</guid><description>让VPS装上win</description><pubDate>Sat, 16 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::important
请注意，以下命令适用于基于&lt;strong&gt;Linux&lt;/strong&gt;的系统。对于其他操作系统，操作命令可能有所不同。
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;操作环境&lt;/strong&gt;：Debian12 KVM VPS 2C2G10G/ipv4
&lt;strong&gt;目标&lt;/strong&gt;：dd win10精简版&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第一步：更新VPS环境&lt;/h2&gt;
&lt;p&gt;Ubuntu/Debian&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt update -y &amp;amp;&amp;amp; apt install -y curl socat wget sudo
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;第二步：开始dd win&lt;/h2&gt;
&lt;h3&gt;1. 利用&lt;a href=&quot;https://github.com/bin456789/reinstall&quot;&gt;bin456789&lt;/a&gt;大佬脚本&lt;/h3&gt;
&lt;p&gt;国外服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O reinstall.sh $_
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;国内服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -O https://cnb.cool/bin456789/reinstall/-/git/raw/main/reinstall.sh || wget -O reinstall.sh $_
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 选择&lt;a href=&quot;https://dd.wx.mk/&quot;&gt;dd包&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;功能 1: 安装 Linux(不需要直接看功能2)&lt;/p&gt;
&lt;p&gt;用户名 &lt;code&gt;root&lt;/code&gt; 默认密码 &lt;code&gt;123@@@&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash reinstall.sh anolis      7|8|23
                  rocky       8|9|10
                  oracle      8|9
                  almalinux   8|9|10
                  opencloudos 8|9|23
                  centos      9|10
                  fedora      41|42
                  nixos       25.05
                  debian      9|10|11|12|13
                  opensuse    15.6|tumbleweed
                  alpine      3.19|3.20|3.21|3.22
                  openeuler   20.03|22.03|24.03|25.03
                  ubuntu      16.04|18.04|20.04|22.04|24.04|25.04 [--minimal]
                  kali
                  arch
                  gentoo
                  aosc
                  fnos
                  redhat      --img=&quot;http://access.cdn.redhat.com/xxx.qcow2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;功能 2: DD&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持 &lt;code&gt;raw&lt;/code&gt; &lt;code&gt;vhd&lt;/code&gt; 格式的镜像（未压缩，或者压缩成 &lt;code&gt;.gz&lt;/code&gt; &lt;code&gt;.xz&lt;/code&gt; &lt;code&gt;.zst&lt;/code&gt; &lt;code&gt;.tar&lt;/code&gt; &lt;code&gt;.tar.gz&lt;/code&gt; &lt;code&gt;.tar.xz&lt;/code&gt; &lt;code&gt;.tar.zst&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;DD Windows 镜像时，会自动扩展系统盘，静态 IP 的机器会配置好 IP，可能首次开机几分钟后才生效&lt;/li&gt;
&lt;li&gt;DD Linux 镜像时，&lt;strong&gt;不会&lt;/strong&gt;修改镜像的任何内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;镜像网站：https://dd.wx.mk/&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash reinstall.sh dd --img &quot;https://example.com/xxx.xz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我用的镜像是https://dd.wx.mk/cxthhhhh/Disk_Windows_10_x64_Lite_by_CXT_v1.0.vhd.gz&lt;/p&gt;
&lt;p&gt;最后输入&lt;code&gt;reboot&lt;/code&gt;开始dd&lt;/p&gt;
&lt;p&gt;:::tip
可通过多种方式（SSH、HTTP 80 端口、商家后台 VNC、串行控制台）查看安装进度。
&amp;lt;br /&amp;gt;即使安装过程出错，也能通过 SSH 运行 &lt;code&gt;/trans.sh alpine&lt;/code&gt; 安装到 Alpine。
:::&lt;/p&gt;
&lt;h2&gt;最后：dd 成功&lt;/h2&gt;
&lt;p&gt;利用win远程连接输入用户名Administrator/密码cxthhhhh.com
记得一定要改密码&lt;/p&gt;
</content:encoded></item><item><title>部署SunPanel到VPS</title><link>https://blog.yizong.de/posts/sunpanel/</link><guid isPermaLink="true">https://blog.yizong.de/posts/sunpanel/</guid><description>在Debian12上部署Sunpanel v1.3.0并使用Caddy反向代理。</description><pubDate>Sat, 28 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;在Debian12上部署Sunpanel v1.3.0并使用Caddy反向代理&lt;/h1&gt;
&lt;p&gt;:::important
Sunpanel v1.3.0 为最后一个免捐赠版本，如需使用最新版请访问作者GitHub查看
:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;项目位置&lt;/strong&gt;：https://github.com/hslr-s/sun-panel&lt;/p&gt;
&lt;h1&gt;第一步：系统环境准备&lt;/h1&gt;
&lt;p&gt;更新系统软件包并安装必要工具&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y
sudo apt install -y curl tar vim
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第二步：部署 Sunpanel v1.3.0&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;准备工作目录&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo mkdir -p /opt/sunpanel
cd /opt/sunpanel
sudo rm -rf ./*
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;下载 Sunpanel v1.3.0 二进制文件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo curl -L -o sunpanel.tar.gz https://github.com/hslr-s/sun-panel/releases/download/v1.3.0/sun-panel_v1.3.0_linux_amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;解压缩二进制包&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo tar -zxvf sunpanel.tar.gz 
sudo rm sunpanel.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;确认目录名称&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;ls -l
# 应该能看到一个目录：sun-panel_v1.3.0_linux_amd64
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第三步：配置 Systemd 服务&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/systemd/system/sunpanel.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;粘贴以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=Sunpanel Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/opt/sunpanel/sun-panel_v1.3.0_linux_amd64
ExecStart=/opt/sunpanel/sun-panel_v1.3.0_linux_amd64/sun-panel
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第四步：启动服务并排除端口冲突（大概率不会出现此情况）&lt;/h1&gt;
&lt;p&gt;启动服务并设置开机自启&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo systemctl start sunpanel
sudo systemctl enable sunpanel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看运行状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status sunpanel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果运行失败（复制问ChatGPT）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查找是否端口占用
sudo ss -tulnp | grep 3002

# 终止冲突进程
sudo kill -9 &amp;lt;PID&amp;gt;

# 再次启动
sudo systemctl start sunpanel
sudo systemctl status sunpanel
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第五步：安装 Caddy&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;# 安装 Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf &apos;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&apos; | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
echo &quot;deb [signed-by=/usr/share/keyrings/caddy-stable-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/stable/debian any-version main&quot; | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install -y caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;第六步：配置 Caddy 反向代理&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;sudo vim /etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;清空原内容并填写如下配置（请将 sun.yourdomain.com 替换为你的域名）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sun.yourdomain.com {
    reverse_proxy localhost:3002
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重载 Caddy 配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl reload caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;🎉 最终访问&lt;/h1&gt;
&lt;p&gt;利用cloudflare、edgeone等部署域名
打开浏览器，访问你的域名，你应该可以看到 Sunpanel 的登录界面&lt;/p&gt;
&lt;p&gt;建议定期备份database文件&lt;/p&gt;
</content:encoded></item><item><title>哪吒面板自动上传GitHub仓库备份</title><link>https://blog.yizong.de/posts/nezha-backup/</link><guid isPermaLink="true">https://blog.yizong.de/posts/nezha-backup/</guid><description>哪吒面板每天自动打包备份到GitHub脚本</description><pubDate>Mon, 16 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;此脚本为 Nezha面板 每日自动备份到 GitHub 并通过 Telegram 通知&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;操作环境：Debian12 VPS &lt;strong&gt;nezha非docker安装&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：每天凌晨 3:00（北京时间）自动&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打包 &lt;code&gt;/opt/nezha&lt;/code&gt; 为 &lt;code&gt;.tar.gz&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;上传到 GitHub 仓库&lt;/li&gt;
&lt;li&gt;自动清理 7 天前的旧备份&lt;/li&gt;
&lt;li&gt;通过 Telegram Bot 推送成功/失败通知&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2&gt;第一步：准备工作&lt;/h2&gt;
&lt;h3&gt;1. 安装所需软件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install git zip curl -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 获取 GitHub Token 并新建仓库&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;打开 &lt;a href=&quot;https://github.com/settings/tokens&quot;&gt;https://github.com/settings/tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;创建一个  &lt;strong&gt;Classic Token&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;勾选权限：✅ &lt;code&gt;repo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;复制 Token（如：&lt;code&gt;ghp_xxxxxxxxxxxxxxxxxxxxxxxx&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;新建一个仓库，命名为 &lt;code&gt;nezha-backup&lt;/code&gt;（建议私有）&lt;/li&gt;
&lt;li&gt;仓库地址形如：https://github.com/&amp;lt;你的GitHub用户名&amp;gt;/nezha-backup.git&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3. 获取 Telegram Bot Token 和 Chat ID&lt;/h3&gt;
&lt;h4&gt;创建 Telegram Bot：&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;搜索 &lt;code&gt;@BotFather&lt;/code&gt;，发送 &lt;code&gt;/newbot&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;设置名称和用户名，获取 Bot Token&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;获取 Chat ID：&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;给你的 Bot 发一条消息&lt;/li&gt;
&lt;li&gt;访问：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;https://api.telegram.org/bot&amp;lt;你的BotToken&amp;gt;/getUpdates
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;找到 &lt;code&gt;&quot;chat&quot;:{&quot;id&quot;:xxxxx,...}&lt;/code&gt;，这个 &lt;code&gt;id&lt;/code&gt; 就是 Chat ID&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;第二步：创建备份脚本&lt;/h2&gt;
&lt;h3&gt;1. 创建脚本文件&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;vim /root/nezha_backup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 粘贴以下内容（⚠️ 替换标注内容）&lt;/h3&gt;
&lt;p&gt;#!/bin/bash
set -Eeuo pipefail&lt;/p&gt;
&lt;h1&gt;===== cron 环境修正 =====&lt;/h1&gt;
&lt;p&gt;export SHELL=/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export HOME=/root
export LANG=C.UTF-8&lt;/p&gt;
&lt;h3&gt;====== 用户配置 START ======&lt;/h3&gt;
&lt;p&gt;GITHUB_USER=&quot;xxxxxxx&quot;
GITHUB_REPO=&quot;nezha-backup&quot;
GITHUB_TOKEN=&quot;ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;
BOT_TOKEN=&quot;xxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;
CHAT_ID=&quot;xxxxxxx&quot;
BACKUP_DIR=&quot;/opt/nezha&quot;
KEEP_DAYS=7&lt;/p&gt;
&lt;h3&gt;====== 用户配置 END ======&lt;/h3&gt;
&lt;p&gt;WORKDIR=&quot;/root/nezha-backup&quot;
DATE=$(date +%F)
TARFILE=&quot;nezha-backup-$DATE.tar.gz&quot;&lt;/p&gt;
&lt;h1&gt;绝对路径命令&lt;/h1&gt;
&lt;p&gt;GIT=$(command -v git)
CURL=$(command -v curl)
FIND=$(command -v find)
TAR=$(command -v tar)&lt;/p&gt;
&lt;p&gt;send_telegram() {
local msg=&quot;$1&quot;
$CURL -s -X POST &quot;https://api.telegram.org/bot${BOT_TOKEN}/sendMessage&quot; &lt;br /&gt;
-d &quot;chat_id=${CHAT_ID}&quot; &lt;br /&gt;
-d &quot;parse_mode=Markdown&quot; &lt;br /&gt;
-d &quot;text=${msg}&quot; &amp;gt;/dev/null || true
}&lt;/p&gt;
&lt;p&gt;echo &quot;[INFO] 初始化仓库...&quot;
if [ ! -d &quot;$WORKDIR/.git&quot; ]; then
rm -rf &quot;$WORKDIR&quot;
mkdir -p &quot;$WORKDIR&quot;
$GIT clone &quot;https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/${GITHUB_REPO}.git&quot; &quot;$WORKDIR&quot; || {
send_telegram &quot;⚠️ &lt;em&gt;Nezha 备份失败&lt;/em&gt;：无法克隆仓库&quot;
exit 1
}
cd &quot;$WORKDIR&quot; || exit 1
$GIT config user.name &quot;$GITHUB_USER&quot;
$GIT config user.email &quot;${GITHUB_USER}@users.noreply.github.com&quot;
$GIT config --global --add safe.directory &quot;$WORKDIR&quot; || true&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 检查仓库是否为空（忽略 .git 目录）
if [ -z &quot;$($FIND &quot;$WORKDIR&quot; -mindepth 1 -maxdepth 1 -not -name &apos;.git&apos; -print -quit)&quot; ]; then
    echo &quot;# Nezha Backup Repo&quot; &amp;gt; README.md
    $GIT add README.md
    $GIT commit -m &quot;init repo&quot;
    $GIT branch -M main
    $GIT push -u origin main
    echo &quot;[INFO] 已完成 GitHub 仓库初始化&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;else
cd &quot;$WORKDIR&quot; || exit 1
$GIT config --global --add safe.directory &quot;$WORKDIR&quot; || true
$GIT pull origin main &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true
fi&lt;/p&gt;
&lt;h1&gt;先确保备份目录存在&lt;/h1&gt;
&lt;p&gt;if [ ! -d &quot;$BACKUP_DIR&quot; ]; then
send_telegram &quot;⚠️ &lt;em&gt;Nezha 备份失败&lt;/em&gt;：备份目录不存在：$BACKUP_DIR&quot;
exit 1
fi&lt;/p&gt;
&lt;p&gt;echo &quot;[INFO] 打包 $BACKUP_DIR...&quot;
ERRFILE=&quot;/tmp/nezha-tar-$DATE.err&quot;&lt;/p&gt;
&lt;h1&gt;直接输出到 WORKDIR，避免 /tmp-&amp;gt;mv&lt;/h1&gt;
&lt;p&gt;set +e
$TAR --warning=no-file-changed -czf &quot;$WORKDIR/$TARFILE&quot; -C &quot;$BACKUP_DIR&quot; . 2&amp;gt;&quot;$ERRFILE&quot;
tar_ec=$?
set -e&lt;/p&gt;
&lt;p&gt;if [ $tar_ec -ne 0 ]; then
if [ $tar_ec -eq 1 ]; then
# 1 多为非致命警告（例如文件在读时被改动），忽略并继续
echo &quot;[WARN] tar 返回 1（警告），继续。最后几行：&quot;
tail -n 5 &quot;$ERRFILE&quot; || true
else
# &amp;gt;=2 视为失败
tailmsg=$(tail -n 10 &quot;$ERRFILE&quot; 2&amp;gt;/dev/null || true)
send_telegram &quot;⚠️ &lt;em&gt;Nezha 备份失败&lt;/em&gt;：打包错误（exit $tar_ec）
$tailmsg&quot;
exit 1
fi
fi&lt;/p&gt;
&lt;p&gt;$GIT add .&lt;/p&gt;
&lt;h1&gt;删除超过 KEEP_DAYS 的旧备份&lt;/h1&gt;
&lt;p&gt;echo &quot;[INFO] 删除超过 $KEEP_DAYS 天的旧备份...&quot;
$FIND &quot;$WORKDIR&quot; -name &quot;nezha-backup-*.tar.gz&quot; -type f -mtime +$KEEP_DAYS -exec $GIT rm -f {} ; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true&lt;/p&gt;
&lt;h1&gt;提交并推送（只有变更时才提交）&lt;/h1&gt;
&lt;p&gt;if $GIT diff --cached --quiet; then
echo &quot;[INFO] 没有新的备份文件需要提交&quot;
else
$GIT commit -m &quot;Backup on $DATE&quot;
$GIT push origin main || {
send_telegram &quot;⚠️ &lt;em&gt;Nezha 备份失败&lt;/em&gt;：推送错误&quot;
exit 1
}
send_telegram &quot;🎉 &lt;em&gt;Nezha 备份成功！&lt;/em&gt; 已保存：$DATE，已自动清理超过 ${KEEP_DAYS} 天的旧备份&quot;
echo &quot;[INFO] 备份成功&quot;
fi&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
---

## 第三步：设置权限

```bash
chmod +x /root/nezha_backup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;第四步：添加定时任务（每天北京时间凌晨 3 点）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;添加以下内容（北京时间凌晨 3 点 ）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 3 * * * env -i HOME=/root /bin/bash /root/nezha_backup.sh &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;保存并退出（vim：:wq；nano：Ctrl+O → Ctrl+X）&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;第五步：手动运行测试&lt;/h2&gt;
&lt;p&gt;手动运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash /root/nezha_backup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;假装corn运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;env -i /bin/bash -c &apos;HOME=/root /bin/bash /root/nezha_backup.sh&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;✔️ 检查点&lt;/p&gt;
&lt;p&gt;GitHub 仓库出现 nezha-backup-YYYY-MM-DD.tar.gz&lt;/p&gt;
&lt;p&gt;Telegram 收到「备份成功」通知&lt;/p&gt;
</content:encoded></item><item><title>1秒卸载哪吒探针v1</title><link>https://blog.yizong.de/posts/nezha-agent/</link><guid isPermaLink="true">https://blog.yizong.de/posts/nezha-agent/</guid><description>卸载哪吒探针v1</description><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;要卸载 &lt;strong&gt;哪吒探针V1&lt;/strong&gt; 的监控端(Agent)，请按照以下步骤操作：&lt;/p&gt;
&lt;p&gt;:::important
请注意，以下命令适用于基于&lt;strong&gt;Linux&lt;/strong&gt;的系统。对于其他操作系统，操作命令可能有所不同。
:::&lt;/p&gt;
&lt;h2&gt;一. 一键卸载(1s完成)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cd /opt/nezha/agent/ &amp;amp;&amp;amp; ./nezha-agent service uninstall &amp;amp;&amp;amp; cd /root &amp;amp;&amp;amp; rm -rf /opt/nezha/agent/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果安装了多个服务并想要全部卸载，可以使用 Agent 安装脚本的卸载功能：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./agent.sh uninstall
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二. 手动卸载&lt;/h2&gt;
&lt;h3&gt;1. 停止 Agent 服务&lt;/h3&gt;
&lt;p&gt;首先，停止正在运行的 &lt;code&gt;nezha-agent&lt;/code&gt; 服务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl stop nezha-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 禁用开机自启&lt;/h3&gt;
&lt;p&gt;防止服务在系统启动时自动运行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl disable nezha-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 删除服务文件&lt;/h3&gt;
&lt;p&gt;移除 &lt;code&gt;nezha-agent&lt;/code&gt; 的服务配置文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo rm /etc/systemd/system/nezha-agent.service
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. 删除 Agent 文件&lt;/h3&gt;
&lt;p&gt;删除安装目录中的 Agent 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo rm -rf /opt/nezha/agent
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 重新加载 systemd 配置&lt;/h3&gt;
&lt;p&gt;确保系统服务管理器加载最新的配置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 检查残留进程&lt;/h3&gt;
&lt;p&gt;确认没有遗留的 &lt;code&gt;nezha-agent&lt;/code&gt; 进程在运行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ps -ef | grep nezha-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果发现仍有相关进程运行，请使用 &lt;code&gt;kill&lt;/code&gt; 命令终止。&lt;/p&gt;
&lt;h3&gt;7. 删除日志文件（可选）&lt;/h3&gt;
&lt;p&gt;如果需要，您可以删除与 &lt;code&gt;nezha-agent&lt;/code&gt; 相关的日志文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo rm -rf /var/log/nezha
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三. 常用指令&lt;/h2&gt;
&lt;p&gt;停止agent指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl stop nezha-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启agent指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart nezha-agent
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看agent状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status nezha-agent
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>搭建/卸载哪吒V1(CF-CDN)，适合纯V4/V6的VPS</title><link>https://blog.yizong.de/posts/nezha-dashboard/</link><guid isPermaLink="true">https://blog.yizong.de/posts/nezha-dashboard/</guid><description>搭建/卸载哪吒面板监控VPS状态</description><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;本文内容整理至&lt;a href=&quot;https://www.nodeseek.com/&quot;&gt;Nodeseek论坛&lt;/a&gt;与&lt;strong&gt;个人实践&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;:::important
请注意，以下命令适用于基于&lt;strong&gt;Linux&lt;/strong&gt;的系统。对于其他操作系统，操作命令可能有所不同。
:::&lt;/p&gt;
&lt;h2&gt;一. &lt;a href=&quot;https://dash.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt;配置&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;进入域名里的DNS-记录，添加记录，设置子域名前缀，ipv4添加A记录，ipv6添加AAAA记录，开启小黄云&lt;/li&gt;
&lt;li&gt;进入SSL/TLS-概述，将加密模式改成完全(严格)&lt;/li&gt;
&lt;li&gt;进入SSL/TLS-源服务器，以域名example.com举例，创建一个*.example.com的证书，将源证书的代码和密钥的代码分别保存好&lt;/li&gt;
&lt;li&gt;进入网络，开启&lt;strong&gt;Websockets&lt;/strong&gt;和&lt;strong&gt;gRPC&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;二. 安装哪吒面板&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;纯v6小鸡必需要warp&lt;/strong&gt;创建ipv4出站才能下载代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget -N https://gitlab.com/fscarmen/warp/-/raw/main/menu.sh &amp;amp;&amp;amp; bash menu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行安装代码，建议端口默认&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -L https://raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh &amp;amp;&amp;amp; chmod +x nezha.sh &amp;amp;&amp;amp; sudo ./nezha.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三. 安装Caddy&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;执行安装脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf &apos;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&apos; | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf &apos;https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt&apos; | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建并保存证书文件，此处的&lt;strong&gt;example可以不更改&lt;/strong&gt;，以下两行代码分别执行，然后将保存的代码粘贴进去，并输入:wq保存退出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/caddy/example.pem    # 公钥
vim /etc/caddy/example.key    # 私钥
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;配置Caddyfile，执行以下代码打开配置文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;输入%d清空，并粘贴如下代码，其中第一行的&lt;strong&gt;nezha.example.com&lt;/strong&gt;需要替换成实际解析的子域名，证书路径如果第2步保存的文件名没改，这里也不用改，最后输入:wq保存退出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nezha.example.com {
    reverse_proxy /proto.NezhaService/* h2c://127.0.0.1:8008
    tls /etc/caddy/example.pem /etc/caddy/example.key
    reverse_proxy /* 127.0.0.1:8008
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;启用并启动Caddy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; systemctl enable caddy
 systemctl start caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果先安装的Caddy后安装的面板，这里需要重启下Caddy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl reload caddy
systemctl restart caddy
sudo systemctl status caddy
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;四. 面板设置与添加探针&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通过&lt;strong&gt;nezha.example.com/dashboard&lt;/strong&gt;进入后台，默认账号密码都是admin，登录后添加新用户和密码，退出登录并用新账号密码登录，再进入设置删掉admin账号&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将&lt;strong&gt;nezha.example.com&lt;/strong&gt;添加在仪表板服务器域名/IP(无 CDN)中并保存&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在其他小鸡新增探针时，粘贴的代码里需要修改如下位置，也可以在面板后台设置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;修改 NZ_SERVER=nezha.example.com:443
开启 NZ_TLS=true
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;五. 面板卸载（仅适用&lt;strong&gt;Systemd安装&lt;/strong&gt;）&lt;/h2&gt;
&lt;h3&gt;1. 使用安装代码卸载&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;curl -L https://raw.githubusercontent.com/nezhahq/scripts/refs/heads/main/install.sh -o nezha.sh &amp;amp;&amp;amp; chmod +x nezha.sh &amp;amp;&amp;amp; sudo ./nezha.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 手动卸载&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;停止服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl stop nezha-dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;禁用开机自启：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl disable nezha-dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除服务文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm /etc/systemd/system/nezha-dashboard.service
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;重新加载 Systemd 配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除程序文件：
如果你的面板安装在 /opt/nezha/dashboard：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf /opt/nezha/dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除日志文件（如果有）：
如果日志保存在 /var/log/nezha，可以删除：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rm -rf /var/log/nezha
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;六. 常用指令&lt;/h2&gt;
&lt;p&gt;停止panel指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl stop nezha-dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启panel指令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl restart nezha-dashboard
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看panel状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl status nezha-dashboard
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Cloudflare CDN 全端口回源设置</title><link>https://blog.yizong.de/posts/ipv6-cdn/</link><guid isPermaLink="true">https://blog.yizong.de/posts/ipv6-cdn/</guid><description>IPv6only使用VMess/Vless</description><pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;准备工作&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;一个已经转入CloudFlare的，且是全功能的域名，千万不要使用双向解析的域名&lt;/li&gt;
&lt;li&gt;一台VPS(IPv6和IPv4都可以)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ssh.090227.xyz/&quot;&gt;WebSSH&lt;/a&gt; (让没有IPv6环境的小白朋友也可以连上VPS的SSH)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h1&gt;部署开始&lt;/h1&gt;
&lt;h2&gt;1. 通过WebSSH安装X-ui&lt;/h2&gt;
&lt;p&gt;打开 &lt;a href=&quot;https://ssh.090227.xyz/&quot;&gt;WebSSH&lt;/a&gt; ，&lt;code&gt;Hostname / 主机&lt;/code&gt;填入你VPS的IP即可，IPv6无需修改IP格式直接填入即可，&lt;br /&gt;
然后填入你VPS的&lt;code&gt;Username / 用户名&lt;/code&gt;和&lt;code&gt;Password / 密码&lt;/code&gt;后点击&lt;code&gt;Connect&lt;/code&gt;连上你的VPS&lt;/p&gt;
&lt;p&gt;连上机器后安装X-ui，这里就使用&lt;a href=&quot;https://github.com/yonggekkk/x-ui-yg&quot;&gt;甬哥的X-ui脚本&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -Ls https://raw.githubusercontent.com/yonggekkk/x-ui-yg/main/install.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意: 如果你的机器未安装BBR，请&lt;strong&gt;优先安装BBR&lt;/strong&gt;后再展开后续的工作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;X-ui面板端口&lt;/strong&gt;设为&lt;code&gt;2052&lt;/code&gt;，CF支持HTTP回源端口如下：&lt;code&gt;80&lt;/code&gt;、&lt;code&gt;8080&lt;/code&gt;、&lt;code&gt;8880&lt;/code&gt;、&lt;code&gt;2052&lt;/code&gt;、&lt;code&gt;2082&lt;/code&gt;、&lt;code&gt;2086&lt;/code&gt;、&lt;code&gt;2095&lt;/code&gt;，推荐使用除了&lt;code&gt;80&lt;/code&gt;之外的端口&lt;/p&gt;
&lt;p&gt;X-ui面板&lt;strong&gt;用户名&lt;/strong&gt;和&lt;strong&gt;密码&lt;/strong&gt;&lt;code&gt;随意即可&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意: &lt;strong&gt;如果机器出口没有IPv4，可通过&lt;/strong&gt;安装WARP&lt;/strong&gt;来给机器&lt;strong&gt;添加IPv4出口&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 设置域名&lt;/h2&gt;
&lt;p&gt;打开 CloudFlare， 选中你要使用的域名，以下例子使用&lt;strong&gt;google.com&lt;/strong&gt;域名举例；&lt;/p&gt;
&lt;h3&gt;2.1. 进入&lt;code&gt;SSL/TLS&lt;/code&gt; &amp;gt; &lt;code&gt;概述&lt;/code&gt; &amp;gt; &lt;code&gt;SSL/TLS 加密模式&lt;/code&gt;为 &lt;code&gt;完全&lt;/code&gt;&lt;/h3&gt;
&lt;h3&gt;2.2. 进入&lt;code&gt;SSL/TLS&lt;/code&gt; &amp;gt; &lt;code&gt;边缘证书&lt;/code&gt; &amp;gt; &lt;strong&gt;关闭&lt;/strong&gt; &lt;code&gt;始终使用 HTTPS&lt;/code&gt; 和 &lt;code&gt;自动 HTTPS 重写&lt;/code&gt;&lt;/h3&gt;
&lt;h3&gt;2.3. 进入&lt;code&gt;SSL/TLS&lt;/code&gt; &amp;gt; &lt;code&gt;源服务器&lt;/code&gt; &amp;gt; &lt;strong&gt;创建证书&lt;/strong&gt; 将&lt;code&gt;证书&lt;/code&gt;和&lt;code&gt;密钥&lt;/code&gt; &lt;strong&gt;复制出来！保存好！！！&lt;/strong&gt;&lt;/h3&gt;
&lt;h3&gt;2.4. 进入&lt;code&gt;DNS&lt;/code&gt; &amp;gt; &lt;code&gt;记录&lt;/code&gt; 添加2条 &lt;strong&gt;AAAA记录&lt;/strong&gt;， &lt;code&gt;xui.google.com&lt;/code&gt;和&lt;code&gt;vmess.google.com&lt;/code&gt;，地址就填入你的小鸡IPv6即可 ，其中&lt;code&gt;xui&lt;/code&gt;必须&lt;strong&gt;开启小黄云&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注意: &lt;strong&gt;&lt;code&gt;vmess.google.com&lt;/code&gt;将会作为你VMess节点的&lt;/strong&gt;伪装域名&lt;/strong&gt;使用&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 登录X-ui面板，设置VMess节点&lt;/h2&gt;
&lt;p&gt;打开 &lt;code&gt;http://xui.google.com:2052&lt;/code&gt;， 填入你设置的X-ui面板&lt;strong&gt;用户名&lt;/strong&gt;和&lt;strong&gt;密码&lt;/strong&gt;后点击登录即可&lt;/p&gt;
&lt;h3&gt;3.1. 添加节点VMess-ws noTLS节点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;协议&lt;/strong&gt;: &lt;code&gt;vmess&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;端口&lt;/strong&gt;: &lt;code&gt;随意&lt;/code&gt; &lt;strong&gt;复制下来备用&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;uuid&lt;/strong&gt;: &lt;strong&gt;复制下来备用&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;传输协议: &lt;code&gt;ws&lt;/code&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;TLS&lt;/strong&gt;: &lt;code&gt;关闭&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;点击&lt;code&gt;添加&lt;/code&gt;完成&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;3.2. 添加节点VMess-ws-TLS节点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;协议&lt;/strong&gt;: &lt;code&gt;vmess&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;端口&lt;/strong&gt;: &lt;code&gt;随意&lt;/code&gt; &lt;strong&gt;复制下来备用&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;uuid&lt;/strong&gt;: 将&lt;strong&gt;第3.1步&lt;/strong&gt;复制下来的&lt;code&gt;uuid&lt;/code&gt;粘贴在此处&lt;br /&gt;
&lt;strong&gt;传输协议: &lt;code&gt;ws&lt;/code&gt;&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;TLS&lt;/strong&gt;: &lt;code&gt;开启&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;证书方式&lt;/strong&gt;: &lt;code&gt;certificate file content&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;公钥内容&lt;/strong&gt;: 填入&lt;strong&gt;第2.3步&lt;/strong&gt;复制下来的&lt;code&gt;证书&lt;/code&gt;内容&lt;br /&gt;
&lt;strong&gt;密钥内容&lt;/strong&gt;: 填入&lt;strong&gt;第2.3步&lt;/strong&gt;复制下来的&lt;code&gt;密钥&lt;/code&gt;内容&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;点击&lt;code&gt;添加&lt;/code&gt;完成&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 设置CF的&lt;strong&gt;Origin Rules&lt;/strong&gt;回源规则&lt;/h2&gt;
&lt;p&gt;打开 CloudFlare， 选中你要使用的域名&lt;/p&gt;
&lt;h3&gt;4.1. 进入&lt;code&gt;规则&lt;/code&gt; &amp;gt; &lt;code&gt;Origin Rules&lt;/code&gt; &amp;gt; &lt;code&gt;创建规则&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;规则名称&lt;/strong&gt;: &lt;code&gt;noTLS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;字段&lt;/strong&gt;: &lt;code&gt;主机名&lt;/code&gt; &amp;gt; &lt;code&gt;等于&lt;/code&gt; &amp;gt; &lt;code&gt;vmess.google.com&lt;/code&gt; &amp;gt; &lt;code&gt;And&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;字段&lt;/strong&gt;: &lt;code&gt;SSL/HTTPS&lt;/code&gt; &amp;gt; &lt;code&gt;关闭&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标端口&lt;/strong&gt;: &lt;code&gt;重写到&lt;/code&gt; &amp;gt; 填入&lt;strong&gt;第3.1步&lt;/strong&gt;复制下来的&lt;code&gt;noTLS端口&lt;/code&gt;内容&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;点击&lt;code&gt;部署&lt;/code&gt;完成&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;4.2. 进入&lt;code&gt;规则&lt;/code&gt; &amp;gt; &lt;code&gt;Origin Rules&lt;/code&gt; &amp;gt; &lt;code&gt;创建规则&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;规则名称&lt;/strong&gt;: &lt;code&gt;TLS&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;字段&lt;/strong&gt;: &lt;code&gt;主机名&lt;/code&gt; &amp;gt; &lt;code&gt;等于&lt;/code&gt; &amp;gt; &lt;code&gt;vmess.google.com&lt;/code&gt; &amp;gt; &lt;code&gt;And&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;字段&lt;/strong&gt;: &lt;code&gt;SSL/HTTPS&lt;/code&gt; &amp;gt; &lt;code&gt;开启&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标端口&lt;/strong&gt;: &lt;code&gt;重写到&lt;/code&gt; &amp;gt; 填入&lt;strong&gt;第3.2步&lt;/strong&gt;复制下来的&lt;code&gt;TLS端口&lt;/code&gt;内容&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;点击&lt;code&gt;部署&lt;/code&gt;完成&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 填入你VMess节点信息，完成优选订阅&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;https://VMess.cmliussss.net/sub?cc=[VMess服务名字]&amp;amp;amp;host=[你的VMess域名]&amp;amp;amp;uuid=[你的UUID]&amp;amp;amp;path=[你的ws路径]&amp;amp;amp;alterid=[额外ID]&amp;amp;amp;security=[加密方式]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;host 必填项&lt;/strong&gt;，你VMess节点的&lt;code&gt;伪装域名&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;uuid 必填项&lt;/strong&gt;，你VMess节点的&lt;code&gt;uuid&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;path 非必填项&lt;/strong&gt;，你VMess节点的&lt;code&gt;路径&lt;/code&gt;，默认&lt;code&gt;\&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cc 非必填项&lt;/strong&gt;，可能会影响使用在线订阅转换,推荐使用地区代号，例如HK、SG、US&lt;br /&gt;
&lt;strong&gt;alterid 非必填项&lt;/strong&gt;，默认&lt;code&gt;0&lt;/code&gt;&lt;br /&gt;
&lt;strong&gt;security 非必填项&lt;/strong&gt;，默认&lt;code&gt;auto&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快速订阅方式&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://VMess.cmliussss.net/sub?host=[你的VMess域名]&amp;amp;amp;uuid=[你的UUID]&amp;amp;amp;path=[你的ws路径]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如: &lt;a href=&quot;https://vmess.cmliussss.net/sub?host=obdii.cfd&amp;amp;uuid=05641cf5-58d2-4ba4-a9f1-b3cda0b1fb1d&amp;amp;path=/linkws&quot;&gt;https://VMess.cmliussss.net/sub?host=obdii.cfd&amp;amp;uuid=05641cf5-58d2-4ba4-a9f1-b3cda0b1fb1d&amp;amp;path=/linkws&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.cmliussss.com/p/CM19/&quot;&gt;https://blog.cmliussss.com/p/CM19/&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>Openwrt防检测固件轻松上手(简略)</title><link>https://blog.yizong.de/posts/openwrt2/</link><guid isPermaLink="true">https://blog.yizong.de/posts/openwrt2/</guid><description>用于校园网防检测等功能。</description><pubDate>Mon, 18 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Openwrt实现校园网防检测&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/88e47729ebe964e265c5347cf732f6ef.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/88e47729ebe964e265c5347cf732f6ef.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;自编译&lt;em&gt;Xiaomi Redmi Router AC2100&lt;/em&gt;路由器固件，可用于&lt;strong&gt;STBU校园网&lt;/strong&gt;防检测等功能。&lt;/p&gt;
&lt;p&gt;本文将对新拿到已刷好固件的朋友们出一期简略的&lt;strong&gt;PC端&lt;/strong&gt;配置教程，方便大家使用到基础功能。&lt;/p&gt;
&lt;p&gt;:::important
请低调使用本路由器，最好放在不显眼的位置，否则后果自负
:::&lt;/p&gt;
&lt;p&gt;如果想要详细的使用教程&lt;a href=&quot;https://hxm.baby/2024/11/08/openwrt1/&quot;&gt;请点击这里&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;一. 固件基本使用教程&lt;/h1&gt;
&lt;h2&gt;1.1 路由器启动和接入网线&lt;/h2&gt;
&lt;p&gt;​        先连接电源线，&lt;strong&gt;不要连接网线&lt;/strong&gt;，等待路由器灯&lt;strong&gt;从蓝变黄闪再变蓝&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{% note warning flat %}现在连网线可能会导致校园网被检测哦{% endnote %}&lt;/p&gt;
&lt;p&gt;​        &lt;strong&gt;连接WiFi，初始WiFi名为openwrt(有两个二选一)，密码为password(这一步推荐使用电脑)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​        通过后面&lt;em&gt;1.5&lt;/em&gt;的设置完成后，再接入已连接校园网的网线到&lt;strong&gt;wan口&lt;/strong&gt; &lt;em&gt;（靠近电源线的口）&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;1.2 进入固件后台管理地址192.168.1.1&lt;/h2&gt;
&lt;p&gt;​        推荐使用系统自带的edge浏览器，直接输入192.168.1.1&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/5bc3b473bf7cf6e5a99928eeced8f62a.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/5bc3b473bf7cf6e5a99928eeced8f62a.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.3 授权进入&lt;/h2&gt;
&lt;p&gt;​        默认用户名为root&lt;/p&gt;
&lt;p&gt;​        初始密码为password&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/e96d075286198f95306f212a508a6a40.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/e96d075286198f95306f212a508a6a40.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.4 进入Openwrt主界面简介&lt;/h2&gt;
&lt;p&gt;​        概览含一些路由的基本信息&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/599c282dd5cb5e45e99283346ca4a2de.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/599c282dd5cb5e45e99283346ca4a2de.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        往下可以看见当前&lt;strong&gt;在线的用户&lt;/strong&gt;数量和连接WiFi的主机名及IP地址&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/2774c65c3f77d1601b3afcdf47b21741.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1.5 开始基本设置&lt;/h2&gt;
&lt;p&gt;​        网络-无线，初始WiFi名为openwrt有两个，密码为password&lt;/p&gt;
&lt;p&gt;​        设置WiFi名称和密码，上面是2.4G，下面是5G，点基本设置进入&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/ba584e4e54f5b68ef396b813c4dcf58f.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        优先&lt;strong&gt;使用5G信号&lt;/strong&gt;，本人300M宽带能跑到340M左右&lt;/p&gt;
&lt;p&gt;​        SSID设置WiFi名，密码栏设置WiFi密码，图片是5G信号设置，&lt;strong&gt;2.4G不用也要点基本设置进去设置密码&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/7eac2411986fbe1da41dd07f7d2539c1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        &lt;strong&gt;设置好后就可以重连到你的WiFi啦！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;1.6 接入校园网&lt;/h2&gt;
&lt;p&gt;​        &lt;strong&gt;保证&lt;em&gt;1.5&lt;/em&gt;的名称和密码设置无误&lt;/strong&gt;后，再接入已连接校园网的网线到&lt;strong&gt;wan口&lt;/strong&gt; &lt;em&gt;（靠近电源线的口）&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;​        自动弹出或自己1.1.1.1进入认证页面，登陆自己的宽带&lt;/p&gt;
&lt;p&gt;​        宽带有问题请加认证页面左下角群，&lt;strong&gt;自行找管理人员解决&lt;/strong&gt;，本人无法解决&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/fd5c2611c0b1ddb8264b11168005ec35.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;二.注意事项&lt;/h1&gt;
&lt;p&gt;​    被检测到五分钟后会自动恢复正常，或者等待五分钟去校园网系统重新登陆&lt;/p&gt;
&lt;p&gt;​    本路由实测最高连接14台设备没被检测，但是100M宽带建议&lt;strong&gt;不超过4台设备&lt;/strong&gt;，200M和300M宽带建议&lt;strong&gt;不超过6台设备&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​    多设备同时接入与断开路由有高机率被检测，比如室友集体下课回寝，或集体出去上课时&lt;/p&gt;
&lt;p&gt;​    不建议连接此破解WiFi使用翻墙代理软件，小概率被检测共享&lt;/p&gt;
&lt;p&gt;​    &lt;strong&gt;建议将路由器放在通风散热且隐蔽处&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​    后期会整理大家使用当中存在的问题尝试出一期解决方案，有什么问题评论区留言哦！&lt;/p&gt;
</content:encoded></item><item><title>利用Tampermonkey实现学习通刷课</title><link>https://blog.yizong.de/posts/tampermonkey/</link><guid isPermaLink="true">https://blog.yizong.de/posts/tampermonkey/</guid><description>解决水课学习刷视频一直等着浪费时间的烦恼。</description><pubDate>Thu, 14 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/649585414300c466713026195577b655.png&quot; alt=&quot;image-刷课1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;解决&lt;strong&gt;水课&lt;/strong&gt;学习刷视频一直等着浪费时间的烦恼。&lt;/p&gt;
&lt;p&gt;本文使用Windows内置&lt;strong&gt;Edge浏览器&lt;/strong&gt;进行演示，建议刷课就用这个浏览器。&lt;/p&gt;
&lt;h1&gt;一、安装油猴(Tampermonkey)&lt;/h1&gt;
&lt;p&gt;可以直接访问 &lt;a href=&quot;https://microsoftedge.microsoft.com/addons/detail/tampermonkey/iikmkjmpaadaobahmlepeloendndfphd&quot;&gt;Tampermonkey – Microsoft Edge Addons&lt;/a&gt; 安装&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/dcfa02093cc64516521f2bc7220b0721.png&quot; alt=&quot;image-20241114010242920&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果使用的其它浏览器，可以进入&lt;a href=&quot;https://www.tampermonkey.net/&quot;&gt;Tampermonkey官网&lt;/a&gt;进行下载安装&lt;/p&gt;
&lt;p&gt;{% note danger flat %}仅支持Chrome、Microsoft Edge、Safari、Opera Next和Firefox浏览器{% endnote %}&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/b2567f1814c9bebd90cbbd6666c4ccdb.png&quot; alt=&quot;image-20241114010412236&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;二、安装脚本&lt;/h1&gt;
&lt;h2&gt;1. 取消隐藏Tampermonkey图标&lt;/h2&gt;
&lt;p&gt;设置在工具栏显示，放在外面这样比较方便脚本开关。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/bdf748bc9ee51905500c0108a701e06f.png&quot; alt=&quot;image-20241114010714162&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;2. 进入脚本网站&lt;/h2&gt;
&lt;p&gt;脚本网站有很多，我推荐使用&lt;a href=&quot;https://greasyfork.org/zh-CN&quot;&gt;Greasy Fork - 安全且实用的用户脚本站&lt;/a&gt;，里面可用的脚本比较多。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/eae59071a7b94083e8d629d238cb82ee.png&quot; alt=&quot;image-20241114011105044&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;3. 安装学习通脚本&lt;/h2&gt;
&lt;p&gt;直接搜索&lt;strong&gt;学习通/超星&lt;/strong&gt;，看见顺眼的点进去安装脚本就行，不过有些可能已经失效了，这里我给大家推荐一个脚本（2024/11/14测试可用）&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://greasyfork.org/zh-CN/scripts/469522-%E8%B6%85%E6%98%9F%E5%AD%A6%E4%B9%A0%E9%80%9A%E4%B9%9D%E4%B9%9D%E5%8A%A9%E6%89%8B-%E4%B8%80%E9%94%AE%E5%90%AF%E5%8A%A8-%E6%9C%80%E5%B0%8F%E5%8C%96%E8%BF%90%E8%A1%8C&quot;&gt;超星学习通九九助手[一键启动][最小化运行]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/836676c57e17a414b4d87c0e03bc6266.png&quot; alt=&quot;image-20241114011519092&quot; /&gt;&lt;/p&gt;
&lt;p&gt;直接一直&lt;strong&gt;点安装&lt;/strong&gt;就行&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/f5e866faf7b02f246713b3a9b205be8b.png&quot; alt=&quot;image-20241114011614942&quot; /&gt;&lt;/p&gt;
&lt;p&gt;安装完后页面会消失，打开学习通，点击油猴如果是这样显示的，那就恭喜你安装成功啦！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/9d6376b46d60cb099538050d6932478f.png&quot; alt=&quot;刷课3&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;三、开始刷课&lt;/h1&gt;
&lt;h2&gt;1. 打开超星学习通网站&lt;/h2&gt;
&lt;p&gt;点去进你要刷的课程，会有弹窗出现。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/af54a6b8097fdf8ed9d3226257f8eb6d.png&quot; alt=&quot;image-20241114012325293&quot; /&gt;&lt;/p&gt;
&lt;p&gt;{% note warning flat %}Warning 如果没有出现弹窗，那就再按下面图片的操作试试，如果出现弹窗请直接跳过{% endnote %}&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/79af526bbb2bfb3f4dbe58cf393b6628.png&quot; alt=&quot;屏幕截图 2024-11-15 023745&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;必须要打开开发人员模式&lt;/strong&gt;，如果没开脚本不能正常运行。&lt;/p&gt;
&lt;p&gt;如果已经打开了开发人员模式，刷新网页后还未出现弹窗，那就可以再把下面&lt;strong&gt;三个框勾选上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/acc42ff379020bf2f9cfc599ded49bda.png&quot; alt=&quot;屏幕截图 2024-11-15 024020&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样基本上就大功告成啦！&lt;/p&gt;
&lt;h2&gt;2. 可以根据自己的需求进行点击&lt;/h2&gt;
&lt;p&gt;作业和考试板块应该是要付费的，有需要的可以自行研究一下。&lt;/p&gt;
&lt;p&gt;进入课程，界面大概就长这样，可以自行选择怎么去刷。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/aa2f5a0e5163494dd17f79c9fb27d9f1.png&quot; alt=&quot;屏幕截图 2024-11-14 012616&quot; /&gt;&lt;/p&gt;
&lt;p&gt;{% note danger flat %}视频倍速播放和夜间刷课都有可能导致学习记录被清空哦。{% endnote %}&lt;/p&gt;
&lt;h2&gt;3. 刷课结束&lt;/h2&gt;
&lt;p&gt;全部刷好后，叉掉此页面&lt;strong&gt;回去检查&lt;/strong&gt;一下吧，看看进度有没有都已经完成了。&lt;/p&gt;
&lt;h1&gt;注意事项&lt;/h1&gt;
&lt;p&gt;不使用刷课的时候右上角可以手动关闭插件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;若脚本失效可在评论区反馈，我再去寻找新的可用方案。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>ios17.0+侧载APP</title><link>https://blog.yizong.de/posts/ios-nb/</link><guid isPermaLink="true">https://blog.yizong.de/posts/ios-nb/</guid><description>IOS侧载APP，侧载方案—NB全能助手</description><pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;应用介绍&lt;/h1&gt;
&lt;p&gt;众所周知 iPhone 不能自由安装第三方应用，只能通过 App Store 商店来下载。那么怎么才能在 iPhone 上安装非 App Store 上的应用？&lt;/p&gt;
&lt;p&gt;其实可以用苹果签名的方式来实现，一般提供给 APP 内测使用。证书分为个人证书跟企业证书。其中企业证书需要付费，而个人证书虽然免费，但申请注册开发者后，只提供了 7 天有效期，所以需要续签，另外也限制了一个设备最多安装三个应用。&lt;/p&gt;
&lt;p&gt;所以为了方便个人证书签名，目前市面上有不少 IPA 签名工具，例如牛蛙、巨魔等等。而今天要分享的是超好用的&lt;a href=&quot;https://nbtool8.com/&quot;&gt;NB助手&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;号称完全免费、无广告，支持在线手机端完成签名和续期。&lt;/p&gt;
&lt;p&gt;官网视频安装教程可以参考：
&amp;lt;div class=&quot;video-container&quot;&amp;gt;
&amp;lt;iframe src=&quot;//player.bilibili.com/player.html?isOutside=true&amp;amp;aid=113145142248525&amp;amp;bvid=BV1Y54ReTEfn&amp;amp;cid=25878724690&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;style&amp;gt;
.video-container {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 aspect ratio (height/width = 9/16 * 100%) */
}&lt;/p&gt;
&lt;p&gt;.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
&amp;lt;/style&amp;gt;&lt;/p&gt;
&lt;h1&gt;一、前期准备&lt;/h1&gt;
&lt;p&gt;①&lt;strong&gt;Win系统&lt;/strong&gt;电脑一台，安装成功后续不再使用电脑；&lt;/p&gt;
&lt;p&gt;②IOS16以上版本系统请打开手机的&lt;strong&gt;开发者模式&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;如何打开开发者模式？&lt;/p&gt;
&lt;p&gt;操作流程：进入手机的&lt;strong&gt;设置&lt;/strong&gt;-&lt;strong&gt;隐私与安全性&lt;/strong&gt;-滑到底部&lt;strong&gt;开发者模式&lt;/strong&gt;-开启&lt;strong&gt;开发者模式&lt;/strong&gt;即可；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/799f815267cb708a4efadffe565432ee.png&quot; alt=&quot;nb助手2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注：如果你再隐私与安全中还是没有找到开发者模式，那只能使用电脑版的爱思助手工具箱中的虚拟定位，位置随意，虚拟定位成功后就再次到手机去找开发者，就可以看到了。&lt;/p&gt;
&lt;h1&gt;二、电脑操作&lt;/h1&gt;
&lt;p&gt;①电脑浏览器打开NB助手官网：&lt;a href=&quot;https://nbtool8.com/&quot;&gt;nbtool8.com&lt;/a&gt;、或者直接点后面的链接&lt;a href=&quot;https://www.lanzout.com/nbtoolwindows&quot;&gt;Win下载链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;②点击&lt;strong&gt;下载iTunes&lt;/strong&gt;，选择最新版本，安装后打开；&lt;/p&gt;
&lt;p&gt;③点击&lt;strong&gt;下载Windows版&lt;/strong&gt;，安装后打开；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/c78ffbbb25d4e4ca36250d604a6d02a7.png&quot; alt=&quot;nb助手3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/3280c96f46b4760f68d9d2ea3ba643ab.png&quot; alt=&quot;nb助手4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;④当前软件中的UDID是空白的，现在请用你的数据线链接手机到电脑上，普通充电线无反应，最好使用原装线，连接后看软件的UDID是否有值，有代表成功，没有请换线或重启电脑多次尝试；&lt;/p&gt;
&lt;p&gt;⑤点击&lt;strong&gt;立即安装&lt;/strong&gt;，此时按钮会变成&lt;strong&gt;安装中&lt;/strong&gt;，当进度条走完后，看进度条右侧，显示&lt;strong&gt;安装成功&lt;/strong&gt;代表成功，如果不成功，还是数据线的问题不稳定；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/46d97fbf5b4e523a8a5bfdec9a587ad6.png&quot; alt=&quot;nb5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/0d7a0f6ff72936ad2309defcbb5460ba.png&quot; alt=&quot;nb6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;⑥此时电脑就没用了，以后也不用了，只是安装使用一次电脑，接下来我们打开手机，看桌面，会多出一个软件&lt;strong&gt;NB助手&lt;/strong&gt;，恭喜你，安装成功！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/b26b85571d8c8e5c94008a279547832c.png&quot; alt=&quot;nb7&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以下为安装NB助手失败的常见问题与解决办法，如果你&lt;strong&gt;安装成功，可跳过此解答&lt;/strong&gt;！&lt;/p&gt;
&lt;p&gt;问题1：安装NB助手提示：ipa下载失败？&lt;/p&gt;
&lt;p&gt;回答1：在电脑上打开C:\Users(或用户)\Administrator(或你的用户名)\AppData\Roaming\NBTool，里面有个NBTool目录，将它删除，然后用360卫士或腾讯管家清理一下电脑缓存垃圾，重新进入官网nbtool8.com下载NB助手尝试安装。注意：如果未发现AppData文件夹，需要先打开电脑的显示隐藏文件夹选项，具体方法请自行百度。&lt;/p&gt;
&lt;p&gt;问题2：安装NB助手时已安装iTunes或爱思助手，缺还提示安装iTunes？&lt;/p&gt;
&lt;p&gt;回答2：第一步：手机连接电脑，重新下载iTunes苹果驱动或者爱思助手；等待驱动安装完成； 第二步：打开NB助手电脑版，点击安装；（保持手机连接电脑状态，需解锁屏幕并点击信任此电脑） 第三步：手机内打开NB助手app，开始使用。&lt;/p&gt;
&lt;p&gt;问题3：安装NB助手时显示：请设置锁屏密码？&lt;/p&gt;
&lt;p&gt;回答3：更新 iTunes 或 苹果驱动，手机重设锁屏密码，apple id设置必须打开 双重认证 ，解锁手机后再插入数据线连接电脑。&lt;/p&gt;
&lt;p&gt;问题4：电脑显示下载成功，手机上却没有NB助手的图标？&lt;/p&gt;
&lt;p&gt;回答4：请重装安装NB助手或重新启动手机解决，99%的问题重启手机都可以解决。&lt;/p&gt;
&lt;p&gt;问题5：手机设置里没有“开发者模式”选项？&lt;/p&gt;
&lt;p&gt;开发者模式一般在设置-隐私与安全-开发者内开启。如果手机上没有开发者模式，可将手机连接电脑，重装一下应用。&lt;/p&gt;
&lt;h1&gt;三、手机操作&lt;/h1&gt;
&lt;h2&gt;1、导入个人ID证书&lt;/h2&gt;
&lt;p&gt;①手机打开NB助手，提示联网默认选&lt;strong&gt;第一个无线局域网与蜂窝网络&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;②提示需要VPN功能，点击&lt;strong&gt;确定&lt;/strong&gt;，再次提示添加VPN配置，点击&lt;strong&gt;允许&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;③自动跳转后，输入&lt;strong&gt;锁屏密码&lt;/strong&gt;进行解锁，输入完密码后会自动返回到NB助手；&lt;/p&gt;
&lt;p&gt;④返回桌面，打开设置，点击&lt;strong&gt;VPN&lt;/strong&gt;，查看是否选择&lt;strong&gt;NB Tool&lt;/strong&gt;,没选择请选择，以及顶部开启状态，没开启请手动开启，&lt;strong&gt;切记以后每次使用NB助手签名/安装/续签/都要选择与开启&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;⑤再次打开NB助手，右下角点击&lt;strong&gt;我的&lt;/strong&gt;，提示使用位置，点击&lt;strong&gt;使用APP时允许&lt;/strong&gt;，点击语言，如果你是大陆用户，选择简体中文，其他地区请自行选择；&lt;/p&gt;
&lt;p&gt;⑥点击底部的&lt;strong&gt;应用&amp;amp;证书&lt;/strong&gt;，再点击&lt;strong&gt;证书&lt;/strong&gt;，再点右上角的 &lt;strong&gt;+&lt;/strong&gt; 号，选择&lt;strong&gt;导入Apple ID&lt;/strong&gt;，输入你的苹果ID账号与密码；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/3ef7856c8c4bd656dbac263cda3bc18f.png&quot; alt=&quot;nb8&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;填写后如果开启了双重验证，会提示输入验证码， 请填写验证码进去&lt;/p&gt;
&lt;p&gt;{% note danger flat %}温馨提示{% endnote %}&lt;/p&gt;
&lt;p&gt;{% span red, iOS 16体统以上用户，首次登陆时账号大概率会触发安全机制，不要紧张，修改一下自己ID密码即可，以后就不会了登录成功后，会显示出你的苹果ID证书 %}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2、导入iPA文件包&lt;/h2&gt;
&lt;p&gt;先下载iPA文件包，再导入到NB助手APP中，iPA文件包的下载，可在&lt;a href=&quot;https://ipa.store/&quot;&gt;iPA商店&lt;/a&gt;进行下载，每天每个免费用户可以下载3个iPA文件包，里面软件包都能正常使用，本人极力推荐；&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;导入ipa文件的二种方式：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第一种：找到手机自带的&lt;strong&gt;文件APP&lt;/strong&gt;，找到你下载的ipa包，点击后选择&lt;strong&gt;共享&lt;/strong&gt;，选择&lt;strong&gt;NB助手&lt;/strong&gt;，此时ipa文件包会自动导入到NB助手中，打开NB助手点击底部的&lt;strong&gt;应用&amp;amp;证书&lt;/strong&gt;即可看到；&lt;/p&gt;
&lt;p&gt;第二种：在NB助手软件中，点击底部的&lt;strong&gt;应用&amp;amp;证书&lt;/strong&gt;，再点击右上角的 &lt;strong&gt;+&lt;/strong&gt; 号，此时会自动打开手机自带的 &lt;strong&gt;文件&lt;/strong&gt; APP，可以点击右下角的 &lt;strong&gt;浏览&lt;/strong&gt; ，进行寻找你下载的ipa文件，找到后点击，会自动导入到NB助手中；&lt;/p&gt;
&lt;p&gt;①本教程以&lt;strong&gt;Spotify&lt;/strong&gt;ipa文件举例，导入后是这样的，如图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/670215865dc4fba7dbcc33692ad709b1.png&quot; alt=&quot;nb9&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注：底部他有时会显示红色的文字，描述文件过期什么的，不管他，毕竟我们需要重新签名进行使用；&lt;/p&gt;
&lt;p&gt;②此时点击你要安装的ipa文件名称，弹出对话框，选择&lt;strong&gt;签名安装/导出&lt;/strong&gt;，进入签名页面；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/4a48a6fb8829a8bd6c187e7ba453b943.png&quot; alt=&quot;nb10&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;版本号：默认&lt;/li&gt;
&lt;li&gt;Build：默认&lt;/li&gt;
&lt;li&gt;最低系统要求：默认&lt;strong&gt;不超过你手机的系统&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Icon：默认&lt;/li&gt;
&lt;li&gt;插件签名：可开启可关闭，如果知道带有越狱或动态依赖库可开启&lt;/li&gt;
&lt;li&gt;注入动态库：新手不懂插件可以不选，如果有人告诉你需要注入就选择插件进行注入&lt;/li&gt;
&lt;li&gt;移除动态库：新手可以不选，如果有人告诉你需要移除旧选择插件进行移除&lt;/li&gt;
&lt;li&gt;证书：选择你前面登录账户的个人ID账号证书&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;至于左下角的&lt;strong&gt;编辑Info.plist&lt;/strong&gt;新手可以不点，如果有人告诉你需要编辑请根据要求进行编辑；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/b9efa184ca3a6f67fa9418d275a5b94e.png&quot; alt=&quot;nb11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;③全部操作完后，点击右下角的&lt;strong&gt;签名&amp;amp;安装&lt;/strong&gt;，进入黑色的DOS界面，会自动进行签名与安装，提示需要VPN功能，点击&lt;strong&gt;确定&lt;/strong&gt;，再次提示添加VPN配置，点击&lt;strong&gt;允许&lt;/strong&gt;；自动跳转后，输入&lt;strong&gt;锁屏密码&lt;/strong&gt;进行解锁，输入完密码后会自动返回到NB助手；提示安装成功，点击&lt;strong&gt;信任 xxxx&lt;/strong&gt;，这里的xxx显示的是你的个人ID账号，会自动跳转到浏览器，页面显示空白或无法访问此页面，不用去管他，返回桌面，即可看到我们安装的ipa文件包已经成功；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/7e67db7f0b6c1073b200f9a4c587b5bb.png&quot; alt=&quot;nb12&quot; /&gt;&lt;/p&gt;
&lt;p&gt;④我们点击安装的应用，是无法打开的，会提示&lt;strong&gt;未受信任的开发者&lt;/strong&gt;，此时打开手机的&lt;strong&gt;设置&lt;/strong&gt;-滑到底部找到&lt;strong&gt;VPN与设备管理&lt;/strong&gt;，找到你签名登录的个人ID账号，点击进去，点击&lt;strong&gt;验证&lt;/strong&gt;即可，此时应用就可以打开；&lt;/p&gt;
&lt;h2&gt;3、续签重装&lt;/h2&gt;
&lt;p&gt;首先要知道我们选择的是&lt;strong&gt;导入Apple ID&lt;/strong&gt;，也就是常说的&lt;strong&gt;个人ID证书&lt;/strong&gt;，个人ID证书有效期为&lt;strong&gt;7天&lt;/strong&gt;，过期后你所安装的应用或游戏是打不开的，会提示&lt;strong&gt;不可用&lt;/strong&gt;，或&lt;strong&gt;无法验证证书&lt;/strong&gt;，此时需要续签/重装，重装是自动覆盖安装，数据不会丢失&lt;/p&gt;
&lt;p&gt;当你遇到此问题时，需要你打开NB助手，点击底部&lt;strong&gt;应用&amp;amp;证书&lt;/strong&gt;，顶部点击&lt;strong&gt;已安装&lt;/strong&gt;，会显示出你已签名安装的应用，右侧还能看到到期还有几天，此时我们点击快到期/已到期的应用，弹窗对话框选择&lt;strong&gt;续签&lt;/strong&gt;即可；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/313669aae431cc5b2ab5fe3c10386cde.png&quot; alt=&quot;nb13&quot; /&gt;&lt;/p&gt;
&lt;p&gt;注：如果你应用多，你也可以点击底部的&lt;strong&gt;一键续签不掉数据&lt;/strong&gt;，进入DOS，直到最后显示&lt;strong&gt;All renew complete&lt;/strong&gt;代表所有应用续签成功；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/53aaef304077d297dce7adbd66f18db2.webp&quot; alt=&quot;nb14&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;最后说明：&lt;/h1&gt;
&lt;p&gt;1、ID自签：一次有效期是7天（这是苹果公司规定的），非常安全且免费，可以无限续签，自己花钱买的证书也会随时掉签&lt;/p&gt;
&lt;p&gt;2、ID自签：一个账号可以签名10个软件，但是仅可以&lt;strong&gt;同时安装3个软件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3、当已签名中的应用未到期或已到期都是可以提前续签的！&lt;/p&gt;
</content:encoded></item><item><title>Openwrt防检测固件使用教程(详细)</title><link>https://blog.yizong.de/posts/openwrt1/</link><guid isPermaLink="true">https://blog.yizong.de/posts/openwrt1/</guid><description>用于校园网防检测等功能。</description><pubDate>Fri, 08 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Openwrt实现校园网防检测&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/88e47729ebe964e265c5347cf732f6ef.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/88e47729ebe964e265c5347cf732f6ef.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;自编译&lt;em&gt;Xiaomi Redmi Router AC2100&lt;/em&gt;路由器固件，可用于&lt;strong&gt;STBU校园网&lt;/strong&gt;防检测等功能。&lt;/p&gt;
&lt;p&gt;本文将对新拿到已刷好固件的朋友们出一期详细的&lt;strong&gt;PC端&lt;/strong&gt;配置教程，方便大家使用到更多功能。&lt;/p&gt;
&lt;p&gt;:::important
请低调使用本路由器，最好放在不显眼的位置，否则后果自负
:::&lt;/p&gt;
&lt;h2&gt;一.  固件构成&lt;/h2&gt;
&lt;h3&gt;1.1 检测类型&lt;/h3&gt;
&lt;p&gt;​            1.基于 IPv4 数据包包头内的 &lt;em&gt;TTL 字段&lt;/em&gt;的检测（固定TTL）&lt;/p&gt;
&lt;p&gt;​            2.基于 HTTP 数据包请求头内的 &lt;em&gt;User-Agent 字段&lt;/em&gt;的检测(使用UA2F)&lt;/p&gt;
&lt;p&gt;​            3.基于 IPv4 数据包包头内的 &lt;em&gt;Identification 字段&lt;/em&gt;的检测（rkp-ipid 设置 IPID）&lt;/p&gt;
&lt;p&gt;​            4.基于网络协议栈时钟偏移的检测技术（统一时间）&lt;/p&gt;
&lt;p&gt;​            本人测试第2、3条检测STBU好像没有，但是以防万一我还是加在里面了。*&lt;/p&gt;
&lt;h3&gt;1.2 本固件集成的软件包(部分)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;luci-app-ttyd luci-app-timedreboot luci-app-argon-config kmod-rkp-ipid iptables-mod-filter iptables-mod-ipopt iptables-mod-u32 iptables-nft ua2f luci-app-ua2f kmod-ipt-u32 kmod-ipt-ipopt ipset iptables-mod-conntrack-extra luci-compat luci-theme-argon
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;二. 固件基础使用教程&lt;/h2&gt;
&lt;h3&gt;2.1 路由器启动和接入网线&lt;/h3&gt;
&lt;p&gt;​        先连接电源线，&lt;strong&gt;不要连接网线&lt;/strong&gt;，等待路由器灯&lt;strong&gt;从蓝变黄闪再变蓝&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{% note warning flat %}Warning 现在连网线可能会导致校园网被检测哦{% endnote %}&lt;/p&gt;
&lt;p&gt;​        通过后面&lt;em&gt;2.2—2.6&lt;/em&gt;的设置完成后，再接入已连接校园网的网线到&lt;strong&gt;wan口&lt;/strong&gt; &lt;em&gt;（靠近电源线的口）&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;2.2 进入固件后台管理地址192.168.1.1&lt;/h3&gt;
&lt;p&gt;​        推荐使用edge浏览器&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/5bc3b473bf7cf6e5a99928eeced8f62a.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/5bc3b473bf7cf6e5a99928eeced8f62a.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.3 授权进入&lt;/h3&gt;
&lt;p&gt;​        默认用户名为root&lt;/p&gt;
&lt;p&gt;​        密码为password&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/e96d075286198f95306f212a508a6a40.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/e96d075286198f95306f212a508a6a40.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.4 进入Openwrt主界面简介&lt;/h3&gt;
&lt;p&gt;​        概览含一些路由的基本信息&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/599c282dd5cb5e45e99283346ca4a2de.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/599c282dd5cb5e45e99283346ca4a2de.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        往下可以看见当前&lt;strong&gt;在线的用户&lt;/strong&gt;数量和连接WiFi的主机名及IP地址&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/2774c65c3f77d1601b3afcdf47b21741.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.5 检查基本设置&lt;/h3&gt;
&lt;p&gt;​        进入系统，查看本地时间是否和浏览器一致，不一致请&lt;strong&gt;同步浏览器时间&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/f2a7906c54d8585096a71f4fd293d5c4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        管理权可以更改后台登录密码，&lt;strong&gt;不建议修改&lt;/strong&gt;，忘记密码后果自负&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/6e3fe828ed53138f84450be903f6cccf.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        可以选择一个特定时间&lt;strong&gt;定时重启&lt;/strong&gt;，让路由器每天可以休息几十秒&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/6817bc9a2c5c2ff2b86f489f41e0e0fd.png&quot; alt=&quot;https://bd076fc.webp.li/2024/11/6817bc9a2c5c2ff2b86f489f41e0e0fd.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        后台有&lt;strong&gt;重启按钮&lt;/strong&gt;，可以不用去拔电源了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/de41333db5aa52b72b1d829f780ecf6c.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        服务里带有&lt;strong&gt;U2F检测开关&lt;/strong&gt;，实测关了也没有被检测，以防万一还是打开吧&lt;/p&gt;
&lt;p&gt;​        &lt;em&gt;只应用前三个就行&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/c501485d173ffbf882ac8414e3e51ba6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        网络-无线，设置WiFi名称和密码，上面是2.4G，下面是5G，点基本设置进入&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/ba584e4e54f5b68ef396b813c4dcf58f.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        优先&lt;strong&gt;使用5G信号&lt;/strong&gt;，本人200M宽带能跑到260M左右&lt;/p&gt;
&lt;p&gt;​        SSID设置WiFi名，密码栏设置WiFi密码，图片是5G信号设置，2.4G不用也要进去设置&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/7eac2411986fbe1da41dd07f7d2539c1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;​        检查防火墙-自定义规则代码是否一致，修改后重启防火墙&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/4da7adcead1e1c8b60934f9a7ee596ee.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# This file is interpreted as shell script.
# Put your custom iptables rules here, they will
# be executed with each firewall (re-)start.

# Internal uci firewall chains are flushed and recreated on reload, so
# put custom rules into the root chains e.g. INPUT or FORWARD or into the
# special user chains, e.g. input_wan_rule or postrouting_lan_rule.
#
# 通过 rkp-ipid 设置 IPID
iptables -t mangle -N IPID_MOD
iptables -t mangle -A FORWARD -j IPID_MOD
iptables -t mangle -A OUTPUT -j IPID_MOD
iptables -t mangle -A IPID_MOD -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A IPID_MOD -d 127.0.0.0/8 -j RETURN
iptables -t mangle -A IPID_MOD -d 172.16.0.0/12 -j RETURN
iptables -t mangle -A IPID_MOD -d 192.168.0.0/16 -j RETURN
iptables -t mangle -A IPID_MOD -d 255.0.0.0/8 -j RETURN
iptables -t mangle -A IPID_MOD -j MARK --set-xmark 0x10/0x10

# 防时钟偏移检测
iptables -t nat -N ntp_force_local
iptables -t nat -I PREROUTING -p udp --dport 123 -j ntp_force_local
iptables -t nat -A ntp_force_local -d 0.0.0.0/8 -j RETURN
iptables -t nat -A ntp_force_local -d 127.0.0.0/8 -j RETURN
iptables -t nat -A ntp_force_local -d 192.168.0.0/16 -j RETURN
iptables -t nat -A ntp_force_local -s 192.168.0.0/16 -j DNAT --to-destination 192.168.1.1

# 通过 iptables 修改 TTL 值
iptables -t mangle -A POSTROUTING -j TTL --ttl-set 128

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.6 高级功能扩展&lt;/h3&gt;
&lt;p&gt;​        &lt;em&gt;&lt;strong&gt;附加高级功能可能导致运行不稳定，谨慎使用，不使用请关闭&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;​        MentoHUST暂时用不上，&lt;strong&gt;保持关闭&lt;/strong&gt;即可&lt;/p&gt;
&lt;p&gt;​        解锁网易云灰色歌曲打开后连接WiFi可自动解锁，但可能让网易云加载变慢，自行测试使用&lt;/p&gt;
&lt;p&gt;​        UU游戏加速器可登陆自己的账号，自行测试使用&lt;/p&gt;
&lt;p&gt;​        KMS可自动解锁局域网内的Office 365全家桶，自行测试使用&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/0b68a04d6deb9a3a5d00c76dd1ee3916.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;2.7 接入校园网&lt;/h3&gt;
&lt;p&gt;​        &lt;strong&gt;保证&lt;em&gt;2.2—2.6&lt;/em&gt;的设置无误&lt;/strong&gt;后，再接入已连接校园网的网线到&lt;strong&gt;wan口&lt;/strong&gt; &lt;em&gt;（靠近电源线的口）&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;​        自动弹出或自己1.1.1.1进入认证页面，登陆自己的宽带&lt;/p&gt;
&lt;p&gt;​        宽带有问题请加认证页面左下角群，&lt;strong&gt;自行找管理人员解决&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://bd076fc.webp.li/2024/11/fd5c2611c0b1ddb8264b11168005ec35.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;三.注意事项&lt;/h2&gt;
&lt;p&gt;​    被检测到五分钟后会自动恢复正常，或者等待五分钟去校园网系统重新登陆&lt;/p&gt;
&lt;p&gt;​    本路由实测最高连接14台设备没被检测，但是100M宽带建议****不超过4台设备**，200M宽带建议&lt;strong&gt;不超过6台设备&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;​    多设备同时接入与断开路由有高机率被检测，比如室友集体下课回寝，或集体出去上课时&lt;/p&gt;
&lt;p&gt;​    不建议连接此破解WiFi使用翻墙代理软件，小概率被检测共享&lt;/p&gt;
&lt;p&gt;​    后期会整理大家使用当中存在的问题尝试出一期解决方案，有什么问题评论区留言哦！&lt;/p&gt;
</content:encoded></item><item><title>Simple Guides for Fuwari</title><link>https://blog.yizong.de/posts/guide/</link><guid isPermaLink="true">https://blog.yizong.de/posts/guide/</guid><description>How to use this blog template.</description><pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This blog template is built with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. For the things that are not mentioned in this guide, you may find the answers in the &lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Front-matter of Posts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The date the post was published.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short description of the post. Displayed on index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The cover image path of the post.&amp;lt;br/&amp;gt;1. Start with &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt;: Use web image&amp;lt;br/&amp;gt;2. Start with &lt;code&gt;/&lt;/code&gt;: For image in &lt;code&gt;public&lt;/code&gt; dir&amp;lt;br/&amp;gt;3. With none of the prefixes: Relative to the markdown file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The tags of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If this post is still a draft, which won&apos;t be displayed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Where to Place the Post Files&lt;/h2&gt;
&lt;p&gt;Your post files should be placed in &lt;code&gt;src/content/posts/&lt;/code&gt; directory. You can also create sub-directories to better organize your posts and assets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/posts/
├── post-1.md
└── post-2/
    ├── cover.png
    └── index.md
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;This Article is a Draft&lt;/h1&gt;
&lt;p&gt;This article is currently in a draft state and is not published. Therefore, it will not be visible to the general audience. The content is still a work in progress and may require further editing and review.&lt;/p&gt;
&lt;p&gt;When the article is ready for publication, you can update the &quot;draft&quot; field to &quot;false&quot; in the Frontmatter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Draft Example
published: 2024-01-11T04:40:26.381Z
tags: [Markdown, Blogging, Demo]
category: Examples
draft: false
---

Here, we&apos;ll explore how code blocks look using [Expressive Code](https://expressive-code.com/). The provided examples are based on the official documentation, which you can refer to for further details.

## Expressive Code

### Syntax Highlighting

[Syntax Highlighting](https://expressive-code.com/key-features/syntax-highlighting/)

#### Regular syntax highlighting

```js
console.log(&apos;This code is syntax highlighted!&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Rendering ANSI escape sequences&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ANSI colors:
- Regular: [31mRed[0m [32mGreen[0m [33mYellow[0m [34mBlue[0m [35mMagenta[0m [36mCyan[0m
- Bold:    [1;31mRed[0m [1;32mGreen[0m [1;33mYellow[0m [1;34mBlue[0m [1;35mMagenta[0m [1;36mCyan[0m
- Dimmed:  [2;31mRed[0m [2;32mGreen[0m [2;33mYellow[0m [2;34mBlue[0m [2;35mMagenta[0m [2;36mCyan[0m

256 colors (showing colors 160-177):
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165[0m
[38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171[0m
[38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177[0m

Full RGB colors:
[38;2;34;139;34mForestGreen - RGB(34, 139, 34)[0m

Text formatting: [1mBold[0m [2mDimmed[0m [3mItalic[0m [4mUnderline[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Editor &amp;amp; Terminal Frames&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/frames/&quot;&gt;Editor &amp;amp; Terminal Frames&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Code editor frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Title attribute example&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/content/index.html --&amp;gt;
&amp;lt;div&amp;gt;File name comment example&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Terminal frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;This terminal frame has no title&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;Write-Output &quot;This one has a title!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Overriding frame types&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Look ma, no frame!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;# Without overriding, this would be a terminal frame
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Text &amp;amp; Line Markers&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/text-markers/&quot;&gt;Text &amp;amp; Line Markers&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Marking full lines &amp;amp; line ranges&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range &quot;7-8&quot;
// Line 8 - targeted by range &quot;7-8&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting line marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;this line is marked as deleted&apos;)
  // This line and the next one are marked as inserted
  console.log(&apos;this is the second inserted line&apos;)

  return &apos;this line uses the neutral default marker type&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding labels to line markers&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}
  value={value}
  className={buttonClassName}
  disabled={disabled}
  active={active}
&amp;gt;
  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding long labels on their own lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}

  value={value}
  className={buttonClassName}

  disabled={disabled}
  active={active}
&amp;gt;

  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Using diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+this is an actual diff file
-all contents will remain unmodified
 no whitespace will be removed either
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Combining syntax highlighting with diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  function thisIsJavaScript() {
    // This entire block gets highlighted as JavaScript,
    // and we can still add diff markers to it!
-   console.log(&apos;Old code to be removed&apos;)
+   console.log(&apos;New and shiny code!&apos;)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Marking individual text inside lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  // Mark any given text inside lines
  return &apos;Multiple matches of the given text are supported&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Regular expressions&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;The words yes and yep will be marked.&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Escaping forward slashes&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Test&quot; &amp;gt; /home/test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting inline marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;These are inserted and deleted marker types&apos;);
  // The return statement uses the default marker type
  return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Word Wrap&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/word-wrap/&quot;&gt;Word Wrap&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Configuring word wrap per block&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Configuring indentation of wrapped lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent (enabled by default)
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Collapsible Sections&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/collapsible-sections/&quot;&gt;Collapsible Sections&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from &apos;@example/some-boilerplate&apos;
import { evenMoreBoilerplate } from &apos;@example/even-more-boilerplate&apos;

const engine = someBoilerplateEngine(evenMoreBoilerplate())

// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)

function calcFn() {
  // You can have multiple collapsed sections
  const a = 1
  const b = 2
  const c = a + b

  // This will remain visible
  console.log(`Calculation result: ${a} + ${b} = ${c}`)
  return c
}

// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: &apos;End of example boilerplate code&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Line Numbers&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/line-numbers/&quot;&gt;Line Numbers&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Displaying line numbers per block&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// This code block will show line numbers
console.log(&apos;Greetings from line 2!&apos;)
console.log(&apos;I am on line 3&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Line numbers are disabled for this block
console.log(&apos;Hello?&apos;)
console.log(&apos;Sorry, do you know what line I am on?&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Changing the starting line number&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Greetings from line 5!&apos;)
console.log(&apos;I am on line 6&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;GitHub Repository Cards&lt;/h2&gt;
&lt;p&gt;You can add dynamic cards that link to GitHub repositories, on page load, the repository information is pulled from the GitHub API.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Fabrizz/MMM-OnSpotify&quot;}&lt;/p&gt;
&lt;p&gt;Create a GitHub repository card with the code &lt;code&gt;::github{repo=&quot;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;&quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;::github{repo=&quot;saicaca/fuwari&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Admonitions&lt;/h2&gt;
&lt;p&gt;Following types of admonitions are supported: &lt;code&gt;note&lt;/code&gt; &lt;code&gt;tip&lt;/code&gt; &lt;code&gt;important&lt;/code&gt; &lt;code&gt;warning&lt;/code&gt; &lt;code&gt;caution&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::note
Highlights information that users should take into account, even when skimming.
:::&lt;/p&gt;
&lt;p&gt;:::tip
Optional information to help a user be more successful.
:::&lt;/p&gt;
&lt;p&gt;:::important
Crucial information necessary for users to succeed.
:::&lt;/p&gt;
&lt;p&gt;:::warning
Critical content demanding immediate user attention due to potential risks.
:::&lt;/p&gt;
&lt;p&gt;:::caution
Negative potential consequences of an action.
:::&lt;/p&gt;
&lt;h3&gt;Basic Syntax&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;:::note
Highlights information that users should take into account, even when skimming.
:::

:::tip
Optional information to help a user be more successful.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Custom Titles&lt;/h3&gt;
&lt;p&gt;The title of the admonition can be customized.&lt;/p&gt;
&lt;p&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub Syntax&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;a href=&quot;https://github.com/orgs/community/discussions/16925&quot;&gt;The GitHub syntax&lt;/a&gt; is also supported.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!NOTE]
&amp;gt; The GitHub syntax is also supported.

&amp;gt; [!TIP]
&amp;gt; The GitHub syntax is also supported.
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;An h1 header&lt;/h1&gt;
&lt;p&gt;Paragraphs are separated by a blank line.&lt;/p&gt;
&lt;p&gt;2nd paragraph. &lt;em&gt;Italic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, and &lt;code&gt;monospace&lt;/code&gt;. Itemized lists
look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this one&lt;/li&gt;
&lt;li&gt;that one&lt;/li&gt;
&lt;li&gt;the other one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Block quotes are
written like so.&lt;/p&gt;
&lt;p&gt;They can span multiple paragraphs,
if you like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., &quot;it&apos;s all
in chapters 12--14&quot;). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺&lt;/p&gt;
&lt;h2&gt;An h2 header&lt;/h2&gt;
&lt;p&gt;Here&apos;s a numbered list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;first item&lt;/li&gt;
&lt;li&gt;second item&lt;/li&gt;
&lt;li&gt;third item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here&apos;s a code sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define foobar() {
    print &quot;Welcome to flavor country!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(which makes copying &amp;amp; pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time
# Quick, count to ten!
for i in range(10):
    # (but not *too* quick)
    time.sleep(0.5)
    print i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;An h3 header&lt;/h3&gt;
&lt;p&gt;Now a nested list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, get these ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carrots&lt;/li&gt;
&lt;li&gt;celery&lt;/li&gt;
&lt;li&gt;lentils&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boil some water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump everything in the pot and follow
this algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; find wooden spoon
 uncover pot
 stir
 cover pot
 balance wooden spoon precariously on pot handle
 wait 10 minutes
 goto first step (or shut off burner when done)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not bump wooden spoon or it will fall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).&lt;/p&gt;
&lt;p&gt;Here&apos;s a link to &lt;a href=&quot;http://foo.bar&quot;&gt;a website&lt;/a&gt;, to a &lt;a href=&quot;local-doc.html&quot;&gt;local
doc&lt;/a&gt;, and to a &lt;a href=&quot;#an-h2-header&quot;&gt;section heading in the current
doc&lt;/a&gt;. Here&apos;s a footnote [^1].&lt;/p&gt;
&lt;p&gt;[^1]: Footnote text goes here.&lt;/p&gt;
&lt;p&gt;Tables can look like this:&lt;/p&gt;
&lt;p&gt;size material color&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;9 leather brown
10 hemp canvas natural
11 glass transparent&lt;/p&gt;
&lt;p&gt;Table: Shoes, their sizes, and what they&apos;re made of&lt;/p&gt;
&lt;p&gt;(The above is the caption for the table.) Pandoc also supports
multi-line tables:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;keyword text&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;red Sunsets, apples, and
other red or reddish
things.&lt;/p&gt;
&lt;p&gt;green Leaves, grass, frogs
and other things it&apos;s
not easy being.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A horizontal rule follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&apos;s a definition list:&lt;/p&gt;
&lt;p&gt;apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There&apos;s no &quot;e&quot; in tomatoe.&lt;/p&gt;
&lt;p&gt;Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)&lt;/p&gt;
&lt;p&gt;Here&apos;s a &quot;line block&quot;:&lt;/p&gt;
&lt;p&gt;| Line one
| Line too
| Line tree&lt;/p&gt;
&lt;p&gt;and images can be specified like so:&lt;/p&gt;
&lt;p&gt;Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:&lt;/p&gt;
&lt;p&gt;$$I = \int \rho R^{2} dV$$&lt;/p&gt;
&lt;p&gt;$$
\begin{equation*}
\pi
=3.1415926535
;8979323846;2643383279;5028841971;6939937510;5820974944
;5923078164;0628620899;8628034825;3421170679;\ldots
\end{equation*}
$$&lt;/p&gt;
&lt;p&gt;And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: `foo`, *bar*, etc.&lt;/p&gt;
&lt;p&gt;Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Include Video in the Post
published: 2023-10-19
// ...
---

&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Bilibili&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item></channel></rss>