<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>愚蠢的地球人</title><link>https://blog.fairysoft.net/</link><description>Hello, Earth!</description><item><title>Windows Server服务器上原生部署Headplane</title><link>https://blog.fairysoft.net/post/71.html</link><description>&lt;p&gt;Headscale 官方原生并没有提供内置的后台管理界面。官方只专注于开发高性能的命令行工具和控制层服务。 不过，得益于 Headscale 提供了完善的 REST API，开源社区开发了多款优秀的第三方网页管理后台。&lt;/p&gt;&lt;p&gt;Headplane 是目前社区中最受推崇、维护也最活跃的 Web UI 之一。创建用户、审批客户端、创建预登录密钥、审批路由等操作都可以在 Headplane 的图形化的管理界面（Web UI）中完成。&lt;/p&gt;&lt;p&gt;同样的，Headplane 原生也是针对 Linux 环境设计的，不过它是用 Node.js 编写的，理论上也可以在 Windows 上编译运行。以下是我经过踩坑总结出来的编译教程：&lt;/p&gt;&lt;p&gt;先安装 Node.js 的 Windows 版本，然后下载 Headplane 的源代码。&lt;/p&gt;&lt;p&gt;Headplane 官方使用 pnpm 管理依赖。运行以下命令安装：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-powershell&quot;&gt;npm&amp;nbsp;install&amp;nbsp;-g&amp;nbsp;pnpm&lt;/pre&gt;&lt;p&gt;在 C:\headplane 目录下，运行 pnpm 自动下载并安装所有前端与后端依赖组件：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-powershell&quot;&gt;pnpm&amp;nbsp;install&lt;/pre&gt;&lt;p&gt;install 过程中会有一些警告，基本上都是一些可选的依赖组件安装失败了，不影响正常使用，可以忽略。&lt;/p&gt;&lt;p&gt;安装完成后，运行以下命令编译前端和后端代码：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-powershell&quot;&gt;pnpm&amp;nbsp;build&lt;/pre&gt;&lt;p&gt;编译完成后在 C:\headplane\build 目录下会生成前端和后端的编译结果。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;先在 headscale 里面创建一个 API_KEY 供 Headplane 使用：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-powershell&quot;&gt;.\headscale.exe&amp;nbsp;apikeys&amp;nbsp;create&lt;/pre&gt;&lt;p&gt;然后在 C:\headplane 根目录下，新建一个名为 config.yaml 的文件，写入以下基础配置内容：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;server:
&amp;nbsp;&amp;nbsp;host:&amp;nbsp;&amp;quot;127.0.0.1&amp;quot;
&amp;nbsp;&amp;nbsp;port:&amp;nbsp;3003
&amp;nbsp;&amp;nbsp;base_url:&amp;nbsp;&amp;quot;https://headscale.myhost.com&amp;quot;
&amp;nbsp;&amp;nbsp;cookie_secret:&amp;nbsp;&amp;quot;abcdefghijklmnopqrstuvwxyz123456&amp;quot;
&amp;nbsp;&amp;nbsp;cookie_secure:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;cookie_max_age:&amp;nbsp;86400
&amp;nbsp;&amp;nbsp;cookie_domain:&amp;nbsp;&amp;quot;headscale.myhost.com&amp;quot;
&amp;nbsp;&amp;nbsp;data_path:&amp;nbsp;&amp;quot;./data&amp;quot;
headscale:
&amp;nbsp;&amp;nbsp;url:&amp;nbsp;&amp;quot;https://myhost.com:8080&amp;quot;
&amp;nbsp;&amp;nbsp;public_url:&amp;nbsp;&amp;quot;https://myhost.com:8080&amp;quot;
&amp;nbsp;&amp;nbsp;config_path:&amp;nbsp;&amp;quot;C:/Headscale/config.yaml&amp;quot;
&amp;nbsp;&amp;nbsp;api_key:&amp;nbsp;&amp;quot;hskey-api-xxxxxxxx&amp;quot;
&amp;nbsp;&amp;nbsp;config_strict:&amp;nbsp;false
integration:
&amp;nbsp;&amp;nbsp;agent:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pre_authkey:&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;docker:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_label:&amp;nbsp;&amp;quot;me.tale.headplane.target=headscale&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;socket:&amp;nbsp;&amp;quot;unix:///var/run/docker.sock&amp;quot;
&amp;nbsp;&amp;nbsp;kubernetes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;false
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;validate_manifest:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pod_name:&amp;nbsp;&amp;quot;headscale&amp;quot;
&amp;nbsp;&amp;nbsp;proc:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;false&lt;/pre&gt;&lt;p&gt;输入以下命令运行服务：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-powershell&quot;&gt;$env:HEADPLANE_CONFIG_PATH=&amp;quot;$PWD\config.yaml&amp;quot;
node&amp;nbsp;build/server/index.js&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;如果在运行之后报错，找到错误提示，按照提示修改代码，直到能够成功运行。&lt;/p&gt;&lt;p&gt;我在运行过程中遇到了两个错误，分别是：&lt;/p&gt;&lt;p&gt;1. 在 hp-agent.ts 文件中，使用了 getegid 和 geteuid 这两个函数来获取当前进程的用户和组 ID，这两个函数在 Windows 上是不存在的，所以需要修改代码来兼容 Windows。&lt;/p&gt;&lt;p&gt;2. 在 index.ts 文件中，使用了 drizzle 函数来创建数据库连接，传入的参数是一个文件路径，但是在 Windows 上需要将文件路径转换为 URL 格式才能正常使用，所以需要修改代码来兼容 Windows。&lt;/p&gt;&lt;p&gt;我是先修改了两处源代码：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-javascript&quot;&gt;//&amp;nbsp;编辑文件&amp;nbsp;/app/server/hp-agent.ts
///将原来的代码：
import&amp;nbsp;{&amp;nbsp;getegid,&amp;nbsp;geteuid&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;#39;node:process&amp;#39;;
//&amp;nbsp;替换为：
import&amp;nbsp;process&amp;nbsp;from&amp;nbsp;&amp;#39;node:process&amp;#39;;
const&amp;nbsp;getegid&amp;nbsp;=&amp;nbsp;process.getegid&amp;nbsp;?&amp;nbsp;process.getegid&amp;nbsp;:&amp;nbsp;()&amp;nbsp;=&amp;gt;&amp;nbsp;0;
const&amp;nbsp;geteuid&amp;nbsp;=&amp;nbsp;process.geteuid&amp;nbsp;?&amp;nbsp;process.geteuid&amp;nbsp;:&amp;nbsp;()&amp;nbsp;=&amp;gt;&amp;nbsp;0;

//&amp;nbsp;编辑文件&amp;nbsp;/app/server/index.ts
//&amp;nbsp;在文件顶部引入&amp;nbsp;pathToFileURL：
import&amp;nbsp;{&amp;nbsp;pathToFileURL&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;#39;node:url&amp;#39;;
//&amp;nbsp;找到&amp;nbsp;createDbClient&amp;nbsp;函数，将原来的：
const&amp;nbsp;db&amp;nbsp;=&amp;nbsp;drizzle(`file://${realPath}`);
//&amp;nbsp;替换为：
const&amp;nbsp;dbUrl&amp;nbsp;=&amp;nbsp;pathToFileURL(realPath).toString();
const&amp;nbsp;db&amp;nbsp;=&amp;nbsp;drizzle(dbUrl);&lt;/pre&gt;&lt;p&gt;修改完成后重新编译，再运行就没有报错了。&lt;/p&gt;&lt;p&gt;浏览器访问 http://localhost:3003/admin 进入 Headplane 的登录界面，输入之前创建的 API_KEY 点登录就可以进入到管理界面了。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;如果需要远程访问，可以在IIS上设置反向代理，将外部访问的请求转发到 http://localhost:3003 上，这样就可以通过 https://headscale.yourhost.com 来访问 Headplane 的管理界面了。&lt;/p&gt;&lt;p&gt;IIS反向代理规则：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-markup&quot;&gt;&amp;lt;?xml&amp;nbsp;version=&amp;quot;1.0&amp;quot;&amp;nbsp;encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;configuration&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;system.webServer&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;rewrite&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;rules&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;rule&amp;nbsp;name=&amp;quot;Headplane&amp;quot;&amp;nbsp;stopProcessing=&amp;quot;true&amp;quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;match&amp;nbsp;url=&amp;quot;^(.*)$&amp;quot;&amp;nbsp;/&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;action&amp;nbsp;type=&amp;quot;Rewrite&amp;quot;&amp;nbsp;url=&amp;quot;http://localhost:3003/{R:1}&amp;quot;&amp;nbsp;/&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;serverVariables&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;set&amp;nbsp;name=&amp;quot;HTTP_X_FORWARDED_PROTO&amp;quot;&amp;nbsp;value=&amp;quot;https&amp;quot;&amp;nbsp;/&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/serverVariables&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/rule&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/rules&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/rewrite&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/system.webServer&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Sat, 23 May 2026 20:52:25 +0800</pubDate></item><item><title>Windows Server服务器上原生部署私有Tailscale服务端</title><link>https://blog.fairysoft.net/post/70.html</link><description>&lt;p&gt;Tailscale 是一款基于 WireGuard 协议的虚拟组网工具，它能将不同网络环境下的设备连接至同一个加密的虚拟局域网，无需公网 IP，也免去了复杂的端口映射和防火墙配置。&amp;nbsp;&lt;/p&gt;&lt;p&gt;WireGuard 要求连接的其中一端必须有公网 IP 或者在路由器上做端口映射。若两台设备都在严格的 NAT后方，通常无法直接建立连接。由于是点对点的连接，所以它的设置比较复杂，需要在每一台设备上单独手动配置密钥、IP 地址和路由等信息。一旦设备过多，管理会非常繁琐。&lt;/p&gt;&lt;p&gt;Tailscale 由于引入了中心协调服务器，所有设备连接到同一个虚拟网络中，由中心服务器给每个设备分配固定 IP 地址，通过图形化界面进行统一管理，它还能自动识别网络环境并实现 NAT 穿透。如果直连失败，它会通过加密中转服务器（DERP）转发流量，保证连接成功率。&amp;nbsp;&lt;/p&gt;&lt;p&gt;Tailscale 除了可以使用官方的服务器之外，还允许用户自建私有服务器。Headscale 是一个开源、自托管的 Tailscale 服务器。在 Windows Server 上搭建 Headscale 最便捷且推荐的方法是利用 WSL (Windows Subsystem for Linux) 安装 Linux 版本的 Headscale，或直接使用 Docker 部署，因为 Headscale 原生主要是针对 Linux 环境设计的。但是我的云服务器配置太低，我不想安装 Docker 服务。&lt;/p&gt;&lt;p&gt;打开Headscale 的 Github 主页看了一下，这个项目是用 Go 语言编写的，Go 语言是跨平台的，所以理论上应该可以直接在 Windows 上编译运行。&lt;/p&gt;&lt;p&gt;于是我就安装了 Go 语言的 Windows 版本，下载 Headscale 的源代码，然后输入以下指令来进行编译：&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;$env:GOOS=&amp;quot;windows&amp;quot;
$env:GOARCH=&amp;quot;amd64&amp;quot;
$env:GOPROXY&amp;nbsp;=&amp;nbsp;&amp;quot;https://goproxy.cn,direct&amp;quot;
go&amp;nbsp;build&amp;nbsp;-o&amp;nbsp;headscale.exe&amp;nbsp;./cmd/headscale&lt;/pre&gt;&lt;p&gt;编译完成后在当前目录下生成了 headscale.exe 文件，说明编译已经成功了。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;接着按照官网的范例创建一个配置文件 config.yaml：&lt;/p&gt;&lt;pre class=&quot;brush:bash;toolbar:false&quot;&gt;#&amp;nbsp;基础访问配置
server_url:&amp;nbsp;https://myhost:8080
listen_addr:&amp;nbsp;0.0.0.0:8080
metrics_listen_addr:&amp;nbsp;127.0.0.1:9090
grpc_listen_addr:&amp;nbsp;0.0.0.0:50443
grpc_allow_insecure:&amp;nbsp;false
#&amp;nbsp;密钥与协议
noise:
&amp;nbsp;&amp;nbsp;private_key_path:&amp;nbsp;./data/noise_private.key
#&amp;nbsp;IP&amp;nbsp;分配
prefixes:
&amp;nbsp;&amp;nbsp;v4:&amp;nbsp;100.100.64.0/24
&amp;nbsp;&amp;nbsp;v6:&amp;nbsp;fd7a:115c:a1e0::/48
&amp;nbsp;&amp;nbsp;allocation:&amp;nbsp;sequential
#&amp;nbsp;DERP&amp;nbsp;核心配置
derp:
&amp;nbsp;&amp;nbsp;server:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled:&amp;nbsp;true&amp;nbsp;#&amp;nbsp;开启内置&amp;nbsp;DERP
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;region_id:&amp;nbsp;999
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;region_code:&amp;nbsp;&amp;quot;headscale&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;region_name:&amp;nbsp;&amp;quot;Headscale&amp;nbsp;Embedded&amp;nbsp;DERP&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verify_clients:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stun_listen_addr:&amp;nbsp;&amp;quot;0.0.0.0:3478&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ipv4:&amp;nbsp;129.28.179.56
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private_key_path:&amp;nbsp;./data/derp_server_private.key
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;automatically_add_embedded_derp_region:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;#&amp;nbsp;清空官方&amp;nbsp;URL，不使用外部路径
&amp;nbsp;&amp;nbsp;urls:&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;paths:&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;auto_update_enabled:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;update_frequency:&amp;nbsp;3h
disable_check_updates:&amp;nbsp;false
#&amp;nbsp;其他设置
node:
&amp;nbsp;&amp;nbsp;expiry:&amp;nbsp;0
&amp;nbsp;&amp;nbsp;ephemeral:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inactivity_timeout:&amp;nbsp;30m
&amp;nbsp;&amp;nbsp;routes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ha:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;probe_interval:&amp;nbsp;10s
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;probe_timeout:&amp;nbsp;5s
#&amp;nbsp;数据库配置
database:
&amp;nbsp;&amp;nbsp;type:&amp;nbsp;sqlite
&amp;nbsp;&amp;nbsp;sqlite:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;path:&amp;nbsp;./data/db.sqlite
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;write_ahead_log:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;wal_autocheckpoint:&amp;nbsp;1000
#&amp;nbsp;证书配置
tls_cert_path:&amp;nbsp;./data/fairysoft.net-chain.pem
tls_key_path:&amp;nbsp;./data/fairysoft.net-key.pem
log:
&amp;nbsp;&amp;nbsp;#&amp;nbsp;Valid&amp;nbsp;log&amp;nbsp;levels:&amp;nbsp;panic,&amp;nbsp;fatal,&amp;nbsp;error,&amp;nbsp;warn,&amp;nbsp;info,&amp;nbsp;debug,&amp;nbsp;trace
&amp;nbsp;&amp;nbsp;level:&amp;nbsp;info
&amp;nbsp;&amp;nbsp;#&amp;nbsp;Output&amp;nbsp;formatting&amp;nbsp;for&amp;nbsp;logs:&amp;nbsp;text&amp;nbsp;or&amp;nbsp;json
&amp;nbsp;&amp;nbsp;format:&amp;nbsp;text
policy:
&amp;nbsp;&amp;nbsp;#&amp;nbsp;The&amp;nbsp;mode&amp;nbsp;can&amp;nbsp;be&amp;nbsp;&amp;quot;file&amp;quot;&amp;nbsp;or&amp;nbsp;&amp;quot;database&amp;quot;&amp;nbsp;that&amp;nbsp;defines
&amp;nbsp;&amp;nbsp;#&amp;nbsp;where&amp;nbsp;the&amp;nbsp;policies&amp;nbsp;are&amp;nbsp;stored&amp;nbsp;and&amp;nbsp;read&amp;nbsp;from.
&amp;nbsp;&amp;nbsp;mode:&amp;nbsp;file
&amp;nbsp;&amp;nbsp;#&amp;nbsp;If&amp;nbsp;the&amp;nbsp;mode&amp;nbsp;is&amp;nbsp;set&amp;nbsp;to&amp;nbsp;&amp;quot;file&amp;quot;,&amp;nbsp;the&amp;nbsp;path&amp;nbsp;to&amp;nbsp;a&amp;nbsp;HuJSON&amp;nbsp;file&amp;nbsp;containing&amp;nbsp;policies.
&amp;nbsp;&amp;nbsp;path:&amp;nbsp;&amp;quot;&amp;quot;
#&amp;nbsp;DNS&amp;nbsp;配置
dns:
&amp;nbsp;&amp;nbsp;magic_dns:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;base_domain:&amp;nbsp;home
&amp;nbsp;&amp;nbsp;override_local_dns:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;nameservers:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;global:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;223.5.5.5
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;114.114.114.114&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;span style=&quot;text-wrap-mode: nowrap;&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;测试一下看能不能运行&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;.\headscale.exe&amp;nbsp;serve&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;测试通过之后就可以使用 nssm.exe 将 Headscale 注册为服务，这样就不需要每次都手动运行了。&lt;/p&gt;&lt;p&gt;先下载 nssm.exe，然后输入以下指令注册服务：&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;.\nssm.exe&amp;nbsp;install&amp;nbsp;headscale&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;在弹出来的界面中做以下设置：&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;Path:&amp;nbsp;C:\headscale\headscale.exe
Startup&amp;nbsp;directory:&amp;nbsp;C:\headscale
Arguments:&amp;nbsp;serve&amp;nbsp;-c&amp;nbsp;C:\headscale\config\config.yam&lt;/pre&gt;&lt;p&gt;设置好之后点击安装，安装完成后就可以在服务管理器里面看到 headscale 服务了，右键点击启动即可。&lt;/p&gt;&lt;p&gt;到现在为止，Headscale 服务器的编译安装工作就全部完成了。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;然后我们进行配置。首先，添加一个用户，后面的操作会用到，用户名随便取：&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;.\headscale.exe&amp;nbsp;users&amp;nbsp;create&amp;nbsp;Admin&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;部署好了服务器，接下来就是将客户端加入到这个私有的虚拟局域网，第一种方法是先在客户端上发起入网认证申请，我第一个测试的设备是威联通：&lt;/p&gt;&lt;pre class=&quot;brush:bash;toolbar:false&quot;&gt;/share/CACHEDEV1_DATA/.qpkg/Tailscale/tailscale&amp;nbsp;up&amp;nbsp;--login-server&amp;nbsp;https://myhost:8080&amp;nbsp;--reset&amp;nbsp;--force-reauth&lt;/pre&gt;&lt;p&gt;输入这条指令按回车之后终端会输出一个 URL，将这个 URL 复制粘贴到浏览器打开就会向服务器提交入网申请，服务器会在浏览器上返回一行包含了临时密钥的审批指令，按照提示将这条指令在服务器上执行就可以通过审批：&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;.\headscale.exe&amp;nbsp;auth&amp;nbsp;register&amp;nbsp;--auth-id&amp;nbsp;hskey-authreq-xxxxxx&amp;nbsp;--user&amp;nbsp;Admin&lt;/pre&gt;&lt;p&gt;审批通过之后，这台设备就成功加入了这个虚拟局域网，有图形界面的客户端这时就可以看到自己的登录状态以及这个局域网内其他所有设备了。&lt;/p&gt;&lt;p&gt;这种登录方式主要适用于 Linux 这种使用命令行的客户端。对于有操作界面的 Tailscale 客户端就不用输入指令这么麻烦，直接在菜单中直接输入自定义的登录网址之后会自动弹出系统浏览器并打开入网审批的 URL。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;对于 Windows、安卓、苹果等有操作界面的客户端，更方便的入网申请方法是先在服务器上创建一个预认证密钥(authkey)，然后在客户端上输入服务器的登录网址和这个密钥来加入网络。在服务器上创建预认证密钥的指令如下：&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;.\headscale.exe&amp;nbsp;auth&amp;nbsp;create-api-key&amp;nbsp;--user&amp;nbsp;Admin&lt;/pre&gt;&lt;p&gt;这种入网认证方法同样适用于纯命令行的 Linux 客户端：&lt;/p&gt;&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;tailscale&amp;nbsp;up&amp;nbsp;--login-server=https://myhost:8080&amp;nbsp;--authkey&amp;nbsp;hskey-auth-xxxxxxxx&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;如果需要通过威联通访问整个家庭局域网，可以在威联通上声明子网路由(--advertise-routes)，如果需要将威联通作为出口进行互联网访问，可以声明出口节点(--advertise-exit-node)：&lt;/p&gt;&lt;pre class=&quot;brush:bash;toolbar:false&quot;&gt;/share/CACHEDEV1_DATA/.qpkg/Tailscale/tailscale&amp;nbsp;up&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--login-server&amp;nbsp;https://myhost:8080&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--advertise-routes=192.168.1.0/24&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--advertise-exit-node&amp;nbsp;\
&amp;nbsp;&amp;nbsp;--reset&lt;/pre&gt;&lt;p&gt;客户端申明了路由之后并不会自动生效，而是需要在 Headscale 服务端进行审批，因为出于安全考虑，服务端默认不信任客户端声明的路由，需要手动进行确认。&lt;/p&gt;&lt;pre class=&quot;brush:ps;toolbar:false&quot;&gt;#查看威联通节点的&amp;nbsp;ID
.\headscale.exe&amp;nbsp;nodes&amp;nbsp;list
#查看节点&amp;nbsp;2&amp;nbsp;的所有路由请求
.\headscale.exe&amp;nbsp;nodes&amp;nbsp;list-routes&amp;nbsp;-i&amp;nbsp;2
#同意审批
.\headscale.exe&amp;nbsp;nodes&amp;nbsp;approve-routes&amp;nbsp;-i&amp;nbsp;2&amp;nbsp;-r&amp;nbsp;&amp;quot;192.168.1.0/24,0.0.0.0/0,::/0&amp;quot;&lt;/pre&gt;&lt;p&gt;服务器端完成确认之后，其他客户端就能通过威联通访问整个家庭局域网了。其他设备上还可以选择是否通过威联通进行互联网访问。如果想要让家庭局域网中的其他没有安装 Tailscale 的设备也能访问整个虚拟局域网，可以在出口路由器上添加一条静态路由，把 100.100.64.0/24 这个网段路由到威联通的局域网 IP 地址上。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;其实以上这些创建用户、审批客户端、创建预登录密钥、审批路由等操作都可以在图形化的管理界面（Web UI）中完成。下一篇文章我会介绍如何部署 Headscale 的 Web UI 管理界面。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Sat, 23 May 2026 20:13:12 +0800</pubDate></item><item><title>威联通的防火墙把自己干死了，多亏了Tailscale</title><link>https://blog.fairysoft.net/post/69.html</link><description>&lt;p&gt;
    今天在家里电脑上打开威联通的 QTS 系统，结果发现页面打不开。
&lt;/p&gt;
&lt;p&gt;
    我还以为是 QTS 系统崩溃了，于是我想通过 SSH 连接到威联通上，结果发现 SSH 也连不上。
&lt;/p&gt;
&lt;p&gt;
    然后我输入 ping 指令测试，居然连 ping 都不通！
&lt;/p&gt;
&lt;p&gt;
    打开机柜检查发现网口的指示灯正常闪亮，硬件连接是好的。
&lt;/p&gt;
&lt;p&gt;
    我想起来之前部署过 Tailscale 的服务，于是在手机上打开 Tailscale 看了一下，结果惊奇的发现威联通竟然在线。
&lt;/p&gt;
&lt;p&gt;
    于是我试着用 Tailscale 的虚拟IP地址连接，居然成功连上了。
&lt;/p&gt;
&lt;p&gt;
    这就有点诡异了，物理网卡无法连接但是虚拟网卡却能连接，这太不正常。
&lt;/p&gt;
&lt;p&gt;
    仔细分析了一下，怀疑是防火墙的问题。
&lt;/p&gt;
&lt;p&gt;
    于是开始排查，先试着在 QTS 里面关闭防火墙，结果 QuFirewall 的窗口一直转圈，根本无法加载。
&lt;/p&gt;
&lt;p&gt;
    登录 SSH，输入 /etc/init.d/QuFirewall.sh stop，执行成功。
&lt;/p&gt;
&lt;p&gt;
    再测试 QTS，现在能通过局域网 IP 打开了，再输入 /etc/init.d/QuFirewall.sh start
&lt;/p&gt;
&lt;p&gt;
    出现一堆错误，内容基本看不懂，大概意思就是启动失败。测试局域网访问，失败。
&lt;/p&gt;
&lt;p&gt;
    现在终于真相大白了，看来确实是防火墙的问题。
&lt;/p&gt;
&lt;p&gt;
    分析原因，我昨天访问 QTS 的时候系统提示启用安全中心，于是我就点了启用。进入安全中心之后，显示防火墙没有开，于是我就点了一下防火墙的启动按钮。
&lt;/p&gt;
&lt;p&gt;
    可能是因为我很长时间没有开启 QuFirewall，期间又升级过 QTS 系统或者 QuFirewall 应用程序。之前的防火墙配置文件可能不兼容了，导致防火墙无法正常启动。而当时我访问 NAS 的设备是通过 Tailscale 接入的，所以当时并没有发现异常。
&lt;/p&gt;
&lt;p&gt;
    防火墙正常启动时，第一步就是先封锁住所有的网络，然后再按照配置文件里面的规则逐条放行，结果现在防火墙在完成第一步之后就卡死了，所以整个网络就永久被封住了。
&lt;/p&gt;
&lt;p&gt;
    那为什么 Tailscale 能突破这个封锁呢，我想可能是因为防火墙封锁的是入站连接，而 Tailscale 是一个从内部发起的出站连接，所以能够突破防火墙的封锁。
&lt;/p&gt;
&lt;p&gt;
    于是卸载掉 QuFirewall，重新安装了一下，安装完成后重新设置规则，一切正常。
&lt;/p&gt;
&lt;p&gt;
    这次多亏了 Tailscale，留下了自救的通道。看来这东西不仅可以用来做内网穿透，关键时刻还能救砖。
&lt;/p&gt;
&lt;p&gt;
    下次我来讲一讲如何在 Windows Server 服务器上部署 Tailscale 的私有服务端。
&lt;/p&gt;
&lt;p&gt;
    &lt;br/&gt;
&lt;/p&gt;</description><pubDate>Sat, 23 May 2026 16:56:17 +0800</pubDate></item><item><title>在不同Linux系统下用脚本实现SSL证书的自动更新</title><link>https://blog.fairysoft.net/post/68.html</link><description>&lt;p&gt;
    从去年开始，各大ssl证书服务商统一把免费证书的有效期从 1 年缩短到了 90 天，以前一年更新一次，手动操作还可以忍，现在每年要操作四到五次，就有点麻烦了。于是我在 Windows Server 云服务器上部署了 Win-acme，解决了证书更新的问题。但是我还有几台其他的 Linux 设备，也需要同步更新，于是我想到了通过计划任务定时执行脚本来实现证书自动更新。
&lt;/p&gt;
&lt;p&gt;
    首先，把 Win-acme 自动下载的证书挂到 ftp 下，直接用 IIS 内置的的 ftp 服务就行，注意开启 SSL。
&lt;/p&gt;
&lt;p&gt;
    原理很简单，写一个 sh 脚本检查证书的有效期，如果临近过期，则连接到远程 ftp 服务器，下载最新的证书并替换原来快过期的证书，然后重启 Web 服务器。最后把这个脚本添加到系统的计划任务中，每天定时执行就行了。
&lt;/p&gt;
&lt;p&gt;
    这一切在 Ubuntu 和 Debian 系统下都非常容易实现，直接贴代码，/etc/ssl/update_ssl.sh：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;#!/usr/bin/bash
# 自动获取当前脚本所在的绝对路径
SCRIPT_DIR=$(cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;; pwd)

# 如果不是在终端运行（说明是 crontab 触发），则启用静默和错误日志模式
if [ ! -t 1 ]; then
    exec 2&amp;gt;&amp;gt; &amp;quot;${SCRIPT_DIR}/update_ssl_err.log&amp;quot; &amp;gt;/dev/null
fi

# ==================== 配置区域 ====================
FTP_HOST=&amp;quot;ftp.your_host&amp;quot;
FTP_USER=&amp;quot;ftp_user&amp;quot;
FTP_PASS=&amp;quot;password&amp;quot;
FTP_DIR=&amp;quot;/ssl&amp;quot;
FTP_URL=&amp;quot;ftp://${FTP_HOST}${FTP_DIR}&amp;quot;
CURL_OPTS=&amp;quot;--ssl-reqd -k -sS -u ${FTP_USER}:${FTP_PASS}&amp;quot;

REMOTE_CERT_FILE=&amp;quot;fairysoft.net-chain.pem&amp;quot; 
REMOTE_KEY_FILE=&amp;quot;fairysoft.net-key.pem&amp;quot;  

LOCAL_CERT_FILE=&amp;quot;/etc/ssl/fairysoft.net-chain.pem&amp;quot;
LOCAL_KEY_FILE=&amp;quot;/etc/ssl/fairysoft.net-key.pem&amp;quot;

NGINX_RELOAD_CMD=&amp;quot;systemctl reload nginx&amp;quot;
EXPIRE_DAYS=28
# =================================================

# 检查本地证书文件是否存在
if [ ! -f &amp;quot;$LOCAL_CERT_FILE&amp;quot; ]; then
    echo &amp;quot;本地证书文件不存在，将直接下载新证书。&amp;quot;
else
    # 获取当前证书的过期时间并计算剩余天数
    EXPIRE_DATE=$(openssl x509 -in &amp;quot;$LOCAL_CERT_FILE&amp;quot; -enddate -noout | cut -d= -f2)
    EXPIRE_SECS=$(date -d &amp;quot;$EXPIRE_DATE&amp;quot; +%s)
    CURRENT_SECS=$(date +%s)
    DAYS_LEFT=$(( ($EXPIRE_SECS - $CURRENT_SECS) / 86400 ))
    EXPIRE_DATE=$(date -d &amp;quot;$EXPIRE_DATE&amp;quot; &amp;#39;+%Y-%m-%d&amp;#39;)
    # 判断是否需要更新
    if [ &amp;quot;$DAYS_LEFT&amp;quot; -ge &amp;quot;$EXPIRE_DAYS&amp;quot; ]; then
        echo &amp;quot;当前证书有效期($EXPIRE_DATE)，将在 $DAYS_LEFT 天后过期 ，无需更新。&amp;quot;
        exit 0
    else
        echo &amp;quot;当前证书有效期($EXPIRE_DATE)，将在 $DAYS_LEFT 天后过期 ，需要更新。&amp;quot;
    fi
fi

echo &amp;quot;开始从远程 FTP 服务器下载新证书...&amp;quot;

# 通过 curl 从 FTP 服务器下载最新证书
echo &amp;quot;正在下载证书...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_CERT_FILE}&amp;quot; -o &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot;

echo &amp;quot;正在下载私钥...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_KEY_FILE}&amp;quot; -o &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot;

# 检查下载是否成功
if [ -s &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot; ] &amp;amp;&amp;amp; [ -s &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot; ]; then
    echo &amp;quot;下载成功！正在替换旧证书...&amp;quot;
    
    # 备份旧证书
    [ -f &amp;quot;$LOCAL_CERT_FILE&amp;quot; ] &amp;amp;&amp;amp; mv &amp;quot;$LOCAL_CERT_FILE&amp;quot; &amp;quot;${LOCAL_CERT_FILE}.bak&amp;quot;
    [ -f &amp;quot;$LOCAL_KEY_FILE&amp;quot; ] &amp;amp;&amp;amp; mv &amp;quot;$LOCAL_KEY_FILE&amp;quot; &amp;quot;${LOCAL_KEY_FILE}.bak&amp;quot;
    
    # 移动新证书到正式位置
    mv &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot; &amp;quot;$LOCAL_CERT_FILE&amp;quot;
    mv &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot; &amp;quot;$LOCAL_KEY_FILE&amp;quot;
    
    # 设置正确的权限
    chmod 644 &amp;quot;$LOCAL_CERT_FILE&amp;quot;
    chmod 600 &amp;quot;$LOCAL_KEY_FILE&amp;quot;
    
    # 重载 Nginx 使新证书生效
    echo &amp;quot;正在重载 Nginx 服务...&amp;quot;
    if eval $NGINX_RELOAD_CMD; then
        echo &amp;quot;SSL 证书更新并重载 Nginx 成功！&amp;quot;
    else
        echo &amp;quot;Nginx 重载失败，请检查配置文件！&amp;quot;
    fi
else
    echo &amp;quot;错误：从 FTP 下载证书失败或文件为空，更新终止。&amp;quot;
    rm -f &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot; &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot;
    exit 1
fi

echo &amp;quot;SSL 证书自动更新成功并已生效！&amp;quot;&lt;/pre&gt;
&lt;p&gt;
    然后用 crontab -e 编辑计划任务，添加：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;0 2 * * * /usr/bin/bash /etc/ssl/update-ssl.sh&lt;/pre&gt;
&lt;p&gt;
    完成！就这么简单
&lt;/p&gt;
&lt;p&gt;
    在搞定了 Debian 服务器之后，我又开始研究威联通的 NAS，威联通的 QNAP 系统是深度定制的 Linux 系统，跟常见的 Ubuntu/Debian 系统区别很大。最大的区别，也是我踩过的最大的坑，就是证书的格式。
&lt;/p&gt;
&lt;p&gt;
    QNAP 虽然使用的是 Apache 作为 Web 服务器，但是证书管理却使用了 stunnel，配置文件如下：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;SSLCertificateFile      &amp;quot;/etc/stunnel/stunnel.pem&amp;quot;  # 这里面同时包含私钥和网站证书
SSLCertificateChainFile &amp;quot;/etc/stunnel/uca.pem&amp;quot;      # 中继证书&lt;/pre&gt;
&lt;p&gt;
    从配置文件可以看出来，威联通使用的证书格式跟 Nginx 和 Apache 都不一样，私钥和网站证书合并成一个证书链，而中继证书却单独放。
&lt;/p&gt;
&lt;p&gt;
    经过几天时间的折腾，最后终于搞定了，/mnt/HDA_ROOT/.config/stunnel/update_ssl.sh：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;#!/bin/bash
# 自动获取当前脚本所在的绝对路径
SCRIPT_DIR=$(cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;; pwd)

# 如果不是在终端运行（说明是 crontab 触发），则启用静默和错误日志模式
if [ ! -t 1 ]; then
    exec 2&amp;gt;&amp;gt; &amp;quot;${SCRIPT_DIR}/update_ssl_err.log&amp;quot; &amp;gt;/dev/null
fi

# ==================== 配置区域 ====================
FTP_HOST=&amp;quot;ftp.your_host&amp;quot;
FTP_USER=&amp;quot;ftp_user&amp;quot;
FTP_PASS=&amp;quot;password&amp;quot;
FTP_DIR=&amp;quot;/ssl&amp;quot;
FTP_URL=&amp;quot;ftp://${FTP_HOST}${FTP_DIR}&amp;quot;
CURL_OPTS=&amp;quot;--ssl-reqd -k -sS -u ${FTP_USER}:${FTP_PASS}&amp;quot;

REMOTE_CRT_FILE=&amp;quot;fairysoft.net-crt.pem&amp;quot;
REMOTE_KEY_FILE=&amp;quot;fairysoft.net-key.pem&amp;quot;
REMOTE_CHAIN_FILE=&amp;quot;fairysoft.net-chain-only.pem&amp;quot;

TMP_DIR=&amp;quot;/tmp/ssl_download&amp;quot;
STUNNEL_DIR=&amp;quot;/mnt/HDA_ROOT/.config/stunnel&amp;quot;
LOCAL_CERT_FILE=&amp;quot;${STUNNEL_DIR}/stunnel.pem&amp;quot;
LOCAL_UCA_FILE=&amp;quot;${STUNNEL_DIR}/uca.pem&amp;quot;
EXPIRE_DAYS=28
# =================================================

# 检查本地证书文件是否存在
if [ ! -f &amp;quot;$LOCAL_CERT_FILE&amp;quot; ]; then
    echo &amp;quot;本地证书文件不存在，将直接下载新证书。&amp;quot;
else
    # 获取当前证书的过期时间并计算剩余天数
    EXPIRE_DATE=$(openssl x509 -in &amp;quot;$LOCAL_CERT_FILE&amp;quot; -enddate -noout | cut -d= -f2)
    EXPIRE_SECS=$(date -d &amp;quot;$EXPIRE_DATE&amp;quot; +%s)
    CURRENT_SECS=$(date +%s)
    DAYS_LEFT=$(( ($EXPIRE_SECS - $CURRENT_SECS) / 86400 ))
    EXPIRE_DATE=$(date -d &amp;quot;$EXPIRE_DATE&amp;quot; &amp;#39;+%Y-%m-%d&amp;#39;)
    # 判断是否需要更新
    if [ &amp;quot;$DAYS_LEFT&amp;quot; -ge &amp;quot;$EXPIRE_DAYS&amp;quot; ]; then
        echo &amp;quot;当前证书有效期($EXPIRE_DATE)，将在 $DAYS_LEFT 天后过期 ，无需更新。&amp;quot;
        exit 0
    else
        echo &amp;quot;当前证书有效期($EXPIRE_DATE)，将在 $DAYS_LEFT 天后过期 ，需要更新。&amp;quot;
    fi
fi

echo &amp;quot;开始从远程 FTP 服务器下载新证书...&amp;quot;

# 创建并清理临时下载目录
mkdir -p &amp;quot;$TMP_DIR&amp;quot;

# 通过 curl 从 FTP 服务器下载最新证书
echo &amp;quot;正在下载 crt 文件...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_CRT_FILE}&amp;quot; -o &amp;quot;${TMP_DIR}/${REMOTE_CRT_FILE}&amp;quot;

echo &amp;quot;正在下载 key 文件...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_KEY_FILE}&amp;quot; -o &amp;quot;${TMP_DIR}/${REMOTE_KEY_FILE}&amp;quot;

echo &amp;quot;正在下载 chain 文件...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_CHAIN_FILE}&amp;quot; -o &amp;quot;${TMP_DIR}/${REMOTE_CHAIN_FILE}&amp;quot;

# 校验下载的文件大小，防止下载了空文件导致服务瘫痪
if [ ! -s &amp;quot;${TMP_DIR}/${REMOTE_CRT_FILE}&amp;quot; ] || [ ! -s &amp;quot;${TMP_DIR}/${REMOTE_KEY_FILE}&amp;quot; ]; then
    echo &amp;quot;错误：从 FTP 下载证书失败或文件为空！更新终止。&amp;quot;
    rm -rf &amp;quot;$TMP_DIR&amp;quot;
    exit 1
fi

echo &amp;quot;下载完成！开始更新旧证书...&amp;quot;
# 备份旧证书
[ -f &amp;quot;$LOCAL_CERT_FILE&amp;quot; ] &amp;amp;&amp;amp; mv &amp;quot;$LOCAL_CERT_FILE&amp;quot; &amp;quot;${LOCAL_CERT_FILE}.bak&amp;quot;
[ -f &amp;quot;$LOCAL_UCA_FILE&amp;quot; ] &amp;amp;&amp;amp; mv &amp;quot;$LOCAL_UCA_FILE&amp;quot; &amp;quot;${LOCAL_UCA_FILE}.bak&amp;quot;

# 合并私钥与域名证书到 stunnel.pem，私钥在前，证书在后 
cat &amp;quot;${TMP_DIR}/${REMOTE_KEY_FILE}&amp;quot; &amp;quot;${TMP_DIR}/${REMOTE_CRT_FILE}&amp;quot; &amp;gt; &amp;quot;${LOCAL_CERT_FILE}&amp;quot;

# 写入中间证书链
if [ -s &amp;quot;${TMP_DIR}/${REMOTE_CHAIN_FILE}&amp;quot; ]; then
    cat &amp;quot;${TMP_DIR}/${REMOTE_CHAIN_FILE}&amp;quot; &amp;gt; &amp;quot;${LOCAL_UCA_FILE}&amp;quot;
fi

# 覆盖两个 backup 文件
cat &amp;quot;${TMP_DIR}/${REMOTE_CRT_FILE}&amp;quot; &amp;gt; &amp;quot;${STUNNEL_DIR}/backup.cert&amp;quot;
cat &amp;quot;${TMP_DIR}/${REMOTE_KEY_FILE}&amp;quot; &amp;gt; &amp;quot;${STUNNEL_DIR}/backup.key&amp;quot;

chmod 600 &amp;quot;${LOCAL_CERT_FILE}&amp;quot; &amp;quot;${LOCAL_UCA_FILE}&amp;quot;

# 清理临时文件
rm -rf &amp;quot;$TMP_DIR&amp;quot;

echo &amp;quot;正在重启系统服务使证书生效...&amp;quot;

# 重启核心 stunnle、web 与反向代理服务
/etc/init.d/stunnel.sh stop
sleep 3
/etc/init.d/Qthttpd.sh restart 2&amp;gt;&amp;amp;1 | grep -v &amp;quot;php_ext.ini&amp;quot;
sleep 3
/etc/init.d/thttpd.sh restart
sleep 3
/etc/init.d/stunnel.sh start
sleep 3
if [ -f &amp;quot;/etc/init.d/reverse_proxy.sh&amp;quot; ]; then
    /etc/init.d/reverse_proxy.sh reload  2&amp;gt;&amp;amp;1 | grep -v &amp;quot;AH00558&amp;quot;
fi

echo &amp;quot;SSL 证书自动更新成功并已生效！&amp;quot;&lt;/pre&gt;
&lt;p&gt;
    脚本跑通之后就是添加计划任务，威联通系统添加计划任务的方法跟其他 Linux 系统不一样，网上有很多教程，很容易就能搜到。需要先编辑 /etc/config/crontab，可以使用 vi 或者 nano，然后再执行指令：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;crontab /etc/config/crontab &amp;amp;&amp;amp; /etc/init.d/crond.sh restart&lt;/pre&gt;
&lt;p&gt;
    在搞定了威联通之后，我又把目光转到了我家里的一台 Home Assistant 服务器，HAOS 系统更加特殊，虽然底层是 Linux，但是默认并没有开启 SSH，需要先下载一个 SSH工具。
&lt;/p&gt;
&lt;p&gt;
    在 HA 插件商店里，搜索并安装 Advanced SSH &amp;amp; Web Terminal，开启&amp;nbsp;SSH，编写脚本测试，运行正常，/ssl/update_ssl.sh：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;#!/bin/bash
# 自动获取当前脚本所在的绝对路径
SCRIPT_DIR=$(cd &amp;quot;$(dirname &amp;quot;$0&amp;quot;)&amp;quot;; pwd)

# 如果不是在终端运行（说明是 crontab 触发），则启用静默和错误日志模式
if [ ! -t 1 ]; then
    exec 2&amp;gt;&amp;gt; &amp;quot;${SCRIPT_DIR}/update_ssl_err.log&amp;quot; &amp;gt;/dev/null
fi
# ==================== HAOS 专用配置区域 ====================
FTP_HOST=&amp;quot;ftp.your_host&amp;quot;
FTP_USER=&amp;quot;ftp_user&amp;quot;
FTP_PASS=&amp;quot;password&amp;quot;
FTP_DIR=&amp;quot;/ssl&amp;quot;
FTP_URL=&amp;quot;ftp://${FTP_HOST}${FTP_DIR}&amp;quot;
CURL_OPTS=&amp;quot;--ssl-reqd -k -sS -u ${FTP_USER}:${FTP_PASS}&amp;quot;

REMOTE_CERT_FILE=&amp;quot;fairysoft.net-chain.pem&amp;quot; 
REMOTE_KEY_FILE=&amp;quot;fairysoft.net-key.pem&amp;quot;  

LOCAL_CERT_FILE=&amp;quot;/ssl/fairysoft.net-chain.pem&amp;quot;
LOCAL_KEY_FILE=&amp;quot;/ssl/fairysoft.net-key.pem&amp;quot;

NGINX_RELOAD_CMD=&amp;quot;ha apps restart core_nginx_proxy&amp;quot;
EXPIRE_DAYS=28
# =========================================================


# 检查本地证书文件是否存在
if [ ! -f &amp;quot;$LOCAL_CERT_FILE&amp;quot; ]; then
    echo &amp;quot;本地证书文件不存在，将直接下载新证书。&amp;quot;
else
    # 获取 OpenSSL 的原始英文时间
    RAW_DATE=$(openssl x509 -in &amp;quot;$LOCAL_CERT_FILE&amp;quot; -enddate -noout | cut -d= -f2)
    # 示例格式: May 30 23:59:59 2026 GMT
    
    # 针对 HAOS (Alpine) 环境，用 awk 将英文月份转换为纯数字格式 (YYYY-MM-DD HH:MM:SS)
    CLEAN_DATE=$(echo &amp;quot;$RAW_DATE&amp;quot; | awk &amp;#39;
    BEGIN {
        split(&amp;quot;Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec&amp;quot;, months, &amp;quot;|&amp;quot;);
        for (i=1; i&amp;lt;=12; i++) m_num[months[i]] = sprintf(&amp;quot;%02d&amp;quot;, i);
    }
    {
        print $4 &amp;quot;-&amp;quot; m_num[$1] &amp;quot;-&amp;quot; sprintf(&amp;quot;%02d&amp;quot;, $2) &amp;quot; &amp;quot; $3
    }&amp;#39;)

    EXPIRE_DATE=$(date -d &amp;quot;$CLEAN_DATE&amp;quot; &amp;#39;+%Y-%m-%d&amp;#39;)

    # 计算具体的剩余天数 $DAYS_LEFT
    EXPIRE_SECS=$(date -d &amp;quot;$CLEAN_DATE&amp;quot; +%s)
    CURRENT_SECS=$(date +%s)
    DAYS_LEFT=$(( ($EXPIRE_SECS - $CURRENT_SECS) / 86400 ))

    # 检查证书是否在指定的 $EXPIRE_DAYS 天数内过期
    CHECK_SECS=$(( EXPIRE_DAYS * 86400 ))
    if openssl x509 -in &amp;quot;$LOCAL_CERT_FILE&amp;quot; -checkend $CHECK_SECS &amp;gt; /dev/null; then
        echo &amp;quot;当前证书有效期($EXPIRE_DATE)，将在 $DAYS_LEFT 天后过期 ，无需更新。&amp;quot;
        exit 0
    else
        echo &amp;quot;当前证书有效期($EXPIRE_DATE)，将在 $DAYS_LEFT 天后过期 ，需要更新。&amp;quot;
    fi
fi

echo &amp;quot;开始从远程 FTP 服务器下载新证书...&amp;quot;

# 通过 curl 从 FTP 服务器下载最新证书
echo &amp;quot;正在下载证书...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_CERT_FILE}&amp;quot; -o &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot;

echo &amp;quot;正在下载私钥...&amp;quot;
curl $CURL_OPTS &amp;quot;${FTP_URL}/${REMOTE_KEY_FILE}&amp;quot; -o &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot;

# 检查下载是否成功
if [ -s &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot; ] &amp;amp;&amp;amp; [ -s &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot; ] &amp;amp;&amp;amp; ! grep -q -E &amp;quot;html|Error&amp;quot; &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot;; then
    echo &amp;quot;下载成功！正在替换旧证书...&amp;quot;
    
    # 备份旧证书
    [ -f &amp;quot;$LOCAL_CERT_FILE&amp;quot; ] &amp;amp;&amp;amp; mv &amp;quot;$LOCAL_CERT_FILE&amp;quot; &amp;quot;${LOCAL_CERT_FILE}.bak&amp;quot;
    [ -f &amp;quot;$LOCAL_KEY_FILE&amp;quot; ] &amp;amp;&amp;amp; mv &amp;quot;$LOCAL_KEY_FILE&amp;quot; &amp;quot;${LOCAL_KEY_FILE}.bak&amp;quot;
    
    # 移动新证书到正式位置
    mv &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot; &amp;quot;$LOCAL_CERT_FILE&amp;quot;
    mv &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot; &amp;quot;$LOCAL_KEY_FILE&amp;quot;
    
    # 设置正确的权限
    chmod 644 &amp;quot;$LOCAL_CERT_FILE&amp;quot;
    chmod 600 &amp;quot;$LOCAL_KEY_FILE&amp;quot;
    
    # 重载 Nginx
    echo &amp;quot;正在重载 Nginx 服务...&amp;quot;
    if eval $NGINX_RELOAD_CMD; then
        echo &amp;quot;SSL 证书更新并重启 Nginx 成功！&amp;quot;
    else
        echo &amp;quot;Nginx 重启失败，请检查命令行！&amp;quot;
    fi
else
    echo &amp;quot;错误：从 FTP 下载证书失败，更新终止。&amp;quot;
    rm -f &amp;quot;${LOCAL_CERT_FILE}.new&amp;quot; &amp;quot;${LOCAL_KEY_FILE}.new&amp;quot;
    exit 1
fi

echo &amp;quot;SSL 证书自动更新成功并已生效！&amp;quot;&lt;/pre&gt;
&lt;p&gt;
    脚本跑通之后，下一步就是添加计划任务了，但这时候问题来了，计划任务却怎么都跑不起来。
&lt;/p&gt;
&lt;p&gt;
    问 AI，告诉我说 HAOS 不支持计划任务，让我使用系统自带的自动化功能配合 Shell Command 来实现，在 AI 的引导下，一顿操作猛如虎，折腾了一上午，脚本硬是没有执行起来。
&lt;/p&gt;
&lt;p&gt;
    只好自己想办法排查问题，经过一番排查，发现 Shell Command 根本没有 /ssl 目录的写入权限，然后我改目录，证书倒是能下载了，最后却又没有 ha apps restart core_nginx_proxy&amp;nbsp;的执行权限。
&lt;/p&gt;
&lt;p&gt;
    被一堆权限问题搞得焦头烂额之后，我只好换了一家 AI，请出 Gemini 大哥出场，他让我直接在&amp;nbsp;Advanced SSH &amp;amp; Web Terminal 的配置页面里编辑YAML：
&lt;/p&gt;
&lt;pre class=&quot;prism-highlight prism-language-bash&quot;&gt;init_commands:
  - echo &amp;quot;00 03 * * * /bin/bash /ssl/update_ssl.sh&amp;quot; &amp;gt; /etc/crontabs/root
  - echo &amp;quot;&amp;quot; &amp;gt;&amp;gt; /etc/crontabs/root
  - chmod 600 /etc/crontabs/root
  - crond&lt;/pre&gt;
&lt;p&gt;
    改完之后点保存，系统会自动重启 Advanced SSH &amp;amp; Web Terminal，测试正常，还得是Gemini。
&lt;/p&gt;</description><pubDate>Thu, 21 May 2026 13:18:00 +0800</pubDate></item><item><title>腾讯云 SSL 证书文件格式踩坑笔记</title><link>https://blog.fairysoft.net/post/67.html</link><description>&lt;p&gt;最近在给网站部署 SSL 证书，由于我的设备跨越了 Windows Server、Debian、QNAP 等多个平台，所以证书格式也要用到很多种，以下是我经过几天时间踩坑之后总结出来的干货。&lt;/p&gt;

&lt;p&gt;先说证书，IIS 的就不说了，只说说 Nginx 和 Apache 证书格式的区别，以及威联通 QNAP 系统独特的证书格式。&lt;/p&gt;

&lt;p&gt;腾讯云的 Nginx 证书包解压之后有四个文件，文件名分别为：&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
fairysoft.net_bundle.crt 网站证书跟中继证书合并的证书链
fairysoft.net_bundle.crt 这个文件跟上面那个文件内容完全一样，只是扩展名不同
fairysoft.net.csr        申请证书时生成的请求文件，部署时用不到，可删
fairysoft.net.key        私钥文件&lt;/pre&gt;

&lt;p&gt;腾讯云的 Apache 证书包解压之后也有四个文件，文件名分别是：&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
fairsoft.net.crt 网站证书
fairsoft.net.key 私钥
fairsoft.net.csr 申请证书时生成的请求文件，部署时用不到，可删
root_bundle.crt  中继证书&lt;/pre&gt;

&lt;p&gt;Nginx 的配置文件如下，需要指定两个文件，一个是网站证书跟中继证书合并成的证书链，另外一个是单独的私钥：&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
ssl_certificate     /etc/ssl/fairysoft.net_bundle.pem;
ssl_certificate_key /etc/ssl/fairysoft.net.key;&lt;/pre&gt;

&lt;p&gt;旧版 Apache（2.4.8 之前）配置文件中需要指定三个文件：&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
SSLCertificateFile      /etc/ssl/fairsoft.net.crt
SSLCertificateChainFile /etc/ssl/root_bundle.crt
SSLCertificateKeyFile   /etc/ssl/fairsoft.net.key&lt;/pre&gt;

&lt;p&gt;新版 Apache（2.4.8 及以后）配置文件中废弃了 SSLCertificateChainFile，强制要求把域名证书跟中继证书顺序拼接成一个证书链，私钥依旧单独文件，格式跟 Nginx 一致了。&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
SSLCertificateFile    /etc/ssl/fairysoft.net_bundle.pem
SSLCertificateKeyFile /etc/ssl/fairsoft.net.key&lt;/pre&gt;

&lt;p&gt;&lt;br /&gt;
总结一下，腾讯云 Apache 证书包是拆分文件：域名证书.crt + 中继.crt + 私钥.key，旧版专用，新版不能直接用。新版 Apache 2.4.8+ 如果要用旧版的 Apache 证书包，必须手动将：域名证书.crt + 中继.crt &amp;rarr; 合并成证书链文件，和 Nginx 格式一致，其实直接下载 Nginx 的证书包就可以直接用了，不需要手动合并。&lt;/p&gt;

&lt;p&gt;威联通 QNAP 系统则有些特殊，QNAP 虽然使用的是 Apache 作为 Web 服务器，但是证书管理却使用了&amp;nbsp;stunnel，配置文件如下：&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
SSLCertificateFile      &amp;quot;/etc/stunnel/stunnel.pem&amp;quot;  # 这里面同时包含私钥和网站证书
SSLCertificateChainFile &amp;quot;/etc/stunnel/uca.pem&amp;quot;      # 中继证书&lt;/pre&gt;

&lt;p&gt;从配置文件可以看出来，威联通使用的证书格式跟 Nginx 和 Apache 都不一样，私钥和网站证书合并成一个证书链，而中继证书却单独放。&lt;/p&gt;

&lt;p&gt;最近我还在 Windows Server 服务器上部署了 Win‑acme ，下载的证书文件格式如下：&lt;/p&gt;

&lt;pre class=&quot;prettyprint&quot;&gt;
fairsoft.net‑key.pem        私钥文件
fairsoft.net‑crt.pem        网站证书
fairsoft.net‑chain‑only.pem 仅中继证书
fairsoft.net‑chain.pem      网站证书和中级证书合并的证书链
&lt;/pre&gt;

&lt;p&gt;对比一下就会发现 Win‑acme 下载的证书跟 Nginx 格式的证书包完全互通，可直接通用于 Nginx 和新版 Apache。&lt;/p&gt;

&lt;p&gt;下一期我来讲一下如何在各种 Linux 系统上部署脚本，自动更新快过期的证书。&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;
</description><pubDate>Wed, 20 May 2026 23:15:53 +0800</pubDate></item><item><title>当飞机遇到GPS干扰</title><link>https://blog.fairysoft.net/post/66.html</link><description>&lt;p&gt;两年前我就想写一篇关于飞机GPS干扰的文章，一直没找到机会写，直到昨天我自己遭遇了一次严重的GPS干扰事件。&lt;/p&gt;

&lt;p&gt;昨天飞晚班回重庆，着陆前10分钟，在我们飞过沙坪坝上空时，飞机弹出一个&amp;ldquo;ADS-B故障&amp;rdquo;的信息，我们明白这是遇到了GPS干扰。但这时候我们还以为这只是一次普通的干扰，对这种由于GPS干扰导致的ADS-B故障我们最近几年经常遇到，早就习以为常了，一般这种干扰只会持续几秒钟就会消失，因为通常干扰范围是局部的，以我们飞机的速度很快就能脱离这个干扰区域，而且就算是持续受到干扰，对我们飞机影响也不大，因为我们飞机有三套高精度的惯性导航系统，它们可以完全不依赖外部的GPS信号来工作。&lt;/p&gt;

&lt;p&gt;可是随后我在无线电频率里听到一架在我前面的山东航空的飞机报告管制员说他们遭遇GPS干扰中止进近拉升了，这时候我才意识到这次的干扰可能跟之前不一样，于是我们开始评估风险，我们重点检查了飞机的导航系统，确认飞机的位置没有出现偏移，但我们还是通告了管制员要求帮我们通过地面雷达监控我们飞机的位置和高度，因为空管的一次雷达是使用无线电波反射原理来主动测量飞机的位置，而不依赖飞机的位置报告系统。&lt;/p&gt;

&lt;p&gt;没过多久，飞机又弹出一个&amp;ldquo;FMS/GPS位置不一致&amp;rdquo;的信息，这个信息意味着GPS受到的不是普通的干扰&amp;mdash;&amp;mdash;普通的干扰属于&amp;ldquo;压制式信号干扰&amp;rdquo;，只是用一个杂乱无章的无线电噪声淹没掉正常的GPS卫星信号，使得GPS接收机无法从这一堆噪声中提取出有用的信息，也就无法计算出经纬度位置数据。这种情况下，GPS只是暂时罢工但并不会添乱，失去了GPS定位，飞机的位置精度会略微下降一点点，但还是能满足除了RNP进近之外的大多数飞行程序的要求。但是这个&amp;ldquo;FMS/GPS位置不一致&amp;rdquo;的告警信息意味着GPS接收机接收到了一组精心伪造的无线电信号并解码计算出了一个错误的经纬度位置信息，这种升级版的干扰被称为&amp;ldquo;GPS欺骗&amp;rdquo;，这种情况下，GPS不再是罢工这么简单了，它会直接给你添乱。虽然GPS欺骗很难对飞机的导航系统产生严重的影响，因为飞机的FMGS计算机会识别出明显不合理的GPS位置信息并将其舍弃掉，但是错误的位置信息会对飞机的增强型近地警告系统（EGPWS）造成极大的影响，因为EGPWS系统的位置信号是直接来源于GPS接收机而不是FMGS计算机，EGPWS系统在发出告警之前并不会对GPS接收机提供的位置信息做有效性校验。&lt;/p&gt;

&lt;p&gt;果然这个信息弹出来没多久，驾驶舱里PULL UP警告就开始响了起来，当时我的内心也是慌得一批，倒不是担心会撞山&amp;mdash;&amp;mdash;因为我很清楚我飞机当时的位置根本没有任何撞山的可能。我在重庆飞了近20年，这条航路我太熟悉了：当时我们正在左转五边准备建立盲降，我左前方是鹅公岩长江大桥，江对岸是南岸区，虽然正前方是南山，但是南山海拔最高的地方也只有600米，而我们飞机当时的高度在1500米以上。&lt;/p&gt;

&lt;p&gt;那我慌啥呢？非业内人士可能不知道&amp;ldquo;PULL UP&amp;rdquo;警告有多严重，可以这么说，这个警告是飞机上最严重的警告，没有之一！99%的飞行员一辈子都不会在真飞机上听到这个警告，如果你触发了这个警告，在现在的处罚制度下，基本上就可以宣告你的职业生涯终结了。不过好在这是个假警告，所以我们三个人的饭碗暂时是保住了，算是虚惊一场。随后的重点是如何决策，当时我们有两个选择，一是直接中止进近拉升，二是忽略警告继续进近。&lt;/p&gt;

&lt;p&gt;原则上讲，PULL UP警告是最高优先级、立即动作级警告，根本没时间给你去思考判断和决策，你要做的只能是立即执行地形避让的记忆项目：迅速前推油门杆到底同时断开自动驾驶并向后拉杆到底。这个原则曾经是不容置疑的，关键时刻能保命。但问题是在GPS干扰越来越频发的当今，这种地形避让操作带来的衍生风险也不容忽视：首先，迅速带杆到底的操作对飞机的飞控系统是一个很大的挑战，虽说空客的正常法则会保护飞机不进入失速，但是飞行数据上必然会出现很多超限，每一个超限都够飞行员喝一壶了，如果造成旅客受伤你更是吃不了兜着走了；其次，警告持续响你就得持续爬升，势必会导致穿越多个高度层，在繁忙的机场很容易跟空中其他飞机造成飞行冲突，任何一个冲突都可能会造成事故征候级别的后果，何况受到干扰的飞机不止你一架，大家全都满油门大角度爬升，估计空管都要疯掉了。所以空客公司在最近的一个&amp;ldquo;关于应对虚假TAWS告警&amp;rdquo;的技术通告里面很委婉的提示飞行员，首先，如果在安全高度之上&amp;ldquo;不应当机械的执行TAWS记忆项目&amp;rdquo;；其次，在明确GPS受到干扰并执行完地形意识简令之后可以考虑关闭飞机的TERR告警功能。不过，可能是为了免责，空客在通告中还是强调触发TAWS告警之后处置原则依然是立即执行地形避让的记忆项目。&lt;/p&gt;

&lt;p&gt;在犹豫了0.01秒之后我选择了违反原则，忽略警告并抑制TERR告警功能，继续进近。虽然从结果来看我的选择是对的，如果我选择拉升避让，可能后果会非常严重。但是直到现在我一直在反复思考这个问题：飞行员一直以来被要求原则上应当无条件服从机器的告警指示，是因为我们一直以来都认为机器比人更可靠，人会犯错，机器不会，但是当这个机器明确被证实存在某个Bug，在某些情况下会给出错误的指示，当这种情况发生时，我们是否还应该遵循之前的原则。&lt;/p&gt;

&lt;p&gt;这个问题就不多写了，大家有兴趣可以讨论一下。最后留点科普时间给大家粗略的讲解一下飞机FMGS计算位置的方法。&lt;/p&gt;

&lt;p&gt;首先讲一下惯性导航系统（INS），惯导是一种完全自主的导航设备，它不依赖任何外部信号，仅通过飞机自身，就能实时计算出自身的位置、速度、姿态信息。惯导的核心部件有两个，一个是激光陀螺：负责测量飞机的俯仰、倾斜、转弯角度等，跟位置计算没啥关系，这里就不细说了。另一个核心部件是加速度计：负责测量飞机在空间坐标系中xyz三个方向的加速度。根据牛顿运动定律，加速度对时间积分得到速度，速度对时间积分得到位移，初始位置加位移就是飞机的当前位置。惯导的短时精度非常高，但惯导有一个无法避免的缺陷&amp;mdash;&amp;mdash;累计误差。因为积分运算会不断累积测量过程中的微小偏差，随着飞行时间延长、飞行距离增加，这些误差会一点点累积，导致位置精度逐渐下降。飞行时间越久，惯导算出的位置与真实位置偏差就越大，单纯依靠惯导，无法满足长途飞行的高精度定位需求。&lt;/p&gt;

&lt;p&gt;再说一下GPS定位，GPS是我们最熟悉的定位方式，飞机通过接收卫星发射的信号，实时测算与多颗卫星的距离，通过计算得出自身的位置。它的优势十分突出：定位精度高，且误差不会随飞行时间累积，无论飞多久，只要能稳定接收卫星信号，就能保持较高的位置准确性。但GPS的短板也很明显：信号极易受干扰。就像此前提到的GPS干扰问题，一旦GPS失效，飞机就会失去这一精准定位来源。&lt;/p&gt;

&lt;p&gt;飞机上的FMGS计算机融合惯导与GPS各自的优点，将二者数据融合，取长补短，打造出稳定又精准的定位最优解。工作原理简单来说就是利用惯导短时精度高的优点来对接收到的GPS位置进行校验，采纳可信GPS位置，舍弃不可信GPS位置，不断修正惯导的累积位置偏差。&lt;/p&gt;

&lt;p&gt;所以大家大可不必担心GPS干扰甚至GPS欺骗会对飞机FMGS的定位导航造成严重的影响，就算是受到严重的GPS干扰，飞机位置也不会突然发生剧烈的变化，最多不过是随着时间的累积，慢慢的发生微小漂移。再说了，失去GPS辅助以后FMGS还会降级到无线电位置更新，利用地面导航台的方位距离信息来计算自身的位置，并修正惯导的漂移。就算是无线电位置更新也不可用，惯导仅依靠自身的精度也能维持一段时间的导航精度。这就是为什么没有GPS的飞机如果执行RNAV10的跨洋航线会限制纯惯导的飞行时间不能大于6小时，因为据说惯导漂移的适航认证要求每小时漂移距离不得大于2海里，纯惯导飞行6小时基本能满足RNAV10的要求。实际飞行时我观察过惯导的漂移量，一般每小时只有零点几海里的误差，远低于适航认证的上限。RNP AR(0.3)进离港的GPS空洞期最大允许5分钟也跟这个有关，其逻辑就是在GPS信号丢失之后，惯导仅靠自身的精度能保证在5分钟之内位置偏差不会超过0.3海里。&lt;/p&gt;

&lt;p&gt;最后再说一下受到GPS干扰之后，如果两部GPS都失效的情况下到底能不能使用RNAV进离港程序的问题。我查阅了RNAV1的设计规范，发现设计规范并不强制机载设备必须具备GNSS，可使用 GNSS、DME/DME 或DME/DME/IRU中的任意一种，只有RNP进近和RNP AR进近才强制必须具备GNSS，普通RNP进近要求1部GPS，RNP AR要求两部GPS。但是实际运行中我发现国内几乎所有机场的RNAV进离港程序都要求GNSS，所以在这些机场运行时，一旦GPS失效，就只能申请飞传统进离港程序。不过也有例外，比如成都双流机场的进离港程序就没有要求必须要有GNSS，理论上，双GPS失效的情况下只要DME/DME/IRU可用，在成都双流机场一样能飞RNAV进离港程序，只不过最后进近只能接盲降或者VOR程序，不能飞RNP进近。&lt;/p&gt;
</description><pubDate>Wed, 08 Apr 2026 22:23:17 +0800</pubDate></item><item><title>飞行员专用座舱高度监控报警器</title><link>https://blog.fairysoft.net/post/65.html</link><description>&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208110662916291.jpg&quot; style=&quot;height:270px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;空客A319飞机在飞高原的时候，座舱压力控制计算机有一个BUG，偶尔小概率会触发一个假警告，空客公司认为只是个假警告而已，不理他就行了，所以一直不去修复。但CAAC太卷，领导说了哪怕是假警告也坚决不能响，所以制定了一套补充程序要求机组在座舱高度达到8500ft时执行，以避免这个假警告。但是机组有时候事太多，经常忘了去做这个程序，如果运气不好碰巧这个警告响了的话，回来就会被收拾。所以我做了这个物理外挂，在需要执行程序的时候来提醒机组。使用ESP32处理器，MS5611高精度气压传感器，1.3寸低功耗OLED显示屏，三个物理按键，内置500mAh电池。&lt;/p&gt;

&lt;p&gt;以下是产品简要说明：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;简介：&lt;/strong&gt;&lt;/p&gt;

&lt;p style=&quot;margin-left:40px&quot;&gt;一个带有高度报警和升降率报警功能的气压高度表。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;基本操作：&lt;/strong&gt;&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;1、&amp;nbsp;在主界面下，左键开关显示屏，关屏状态下高度提醒功能仍然在后台工作。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;2、&amp;nbsp;主界面下短按中键进入设置模式，设置气压基准和高度提醒值。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;3、&amp;nbsp;主界面下长按中键设置升降率报警值。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;4、&amp;nbsp;主界面下短按右键切换公英制。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;5、&amp;nbsp;主界面下长按右键将气压基准恢复为1013hPa，高度提醒值恢复为8300ft。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;6、&amp;nbsp;设置模式下，左右键操作数值的减和加。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;四种提醒：&lt;/strong&gt;&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;1、&amp;nbsp;高度超过8300ft，提醒CPC转换，持续滴滴约20秒，按任意键停止。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;2、&amp;nbsp;巡航时首次升降率超过500ft/m，滴滴两声，提醒主控CPC已进入下降模式。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;3、&amp;nbsp;座舱高度超过13000ft，滴滴5声，提醒检查高高度电门是否接通。&lt;/p&gt;

&lt;p style=&quot;margin-left:21pt&quot;&gt;4、&amp;nbsp;升降率超过设定值提醒，只要升降率超过设定值就一直滴滴，无法通过按键停止。本提醒只在亮屏模式下才工作，因为需要高采样频率的支持。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;自动熄屏：&lt;/strong&gt;&lt;/p&gt;

&lt;p style=&quot;margin-left:40px&quot;&gt;正常开机后默认5分钟无按键操作自动熄屏进入待机模式。&lt;/p&gt;

&lt;p style=&quot;margin-left:40px&quot;&gt;按住右键再打开电源开关则不启用自动熄屏功能。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;气压传感器校准：&lt;/strong&gt;&lt;/p&gt;

&lt;p style=&quot;margin-left:40px&quot;&gt;按住左键再打开电源开关进入气压传感器校准页面，左右键调整校准值，中键保存。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;记录一下开发过程：&lt;/p&gt;

&lt;p&gt;突然有一天有了这个想法，于是画了这张手稿&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/2024120810420196196.jpg&quot; style=&quot;height:640px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;说干就干，用ESP32开发板搭建试验平台&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208104379977997.jpg&quot; style=&quot;height:270px; opacity:0.9; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;学习单片机编程，第一步当然是写&amp;ldquo;Hello World！&amp;rdquo;&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208104772177217.jpg&quot; style=&quot;height:203px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;带上飞机调试，精度非常满意&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/2024120810490568568.jpg&quot; style=&quot;height:480px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;开始学习画电路板&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105093029302.jpg&quot; style=&quot;height:411px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;学习手工焊接CPU&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105163256325.jpg&quot; style=&quot;height:270px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;第一台试验样机组装成功&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105280448044.jpg&quot; style=&quot;height:480px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;为了提高续航，研究ESP32的睡眠模式，经过一番努力终于把待机电流控制到了2毫安以下，算下来待机时间可以达到200小时以上。&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105392809280.jpg&quot; style=&quot;height:480px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最终实现了量产。&lt;/p&gt;

&lt;p&gt;经济版用的3D打印塑料外壳，定价319元。&lt;/p&gt;

&lt;p&gt;豪华版使用铝合金CNC外壳，定价499元。&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105688668866.jpg&quot; style=&quot;height:270px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105762646264.jpg&quot; style=&quot;height:480px; width:360px&quot; /&gt;&lt;/p&gt;

&lt;p&gt;有需要的可以联系我购买&lt;/p&gt;

&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241208105981938193.jpg&quot; style=&quot;height:480px; width:360px&quot; /&gt;&lt;/p&gt;
</description><pubDate>Sun, 08 Dec 2024 10:37:56 +0800</pubDate></item><item><title>当前高高原中止进近之后的CPC转换程序存在的问题</title><link>https://blog.fairysoft.net/post/64.html</link><description>&lt;p&gt;先插入一个我自己开发的CPC转换提醒器的广告，详情点击：&lt;a href=&quot;https://blog.fairysoft.net/post/65.html&quot; target=&quot;_self&quot; textvalue=&quot;“飞行员专用座舱高度监控报警器”&quot;&gt;“飞行员专用座舱高度监控报警器”&lt;/a&gt;&lt;/p&gt;&lt;p&gt;进入主题之前我先说一个很多人对于CPC转换的误解。可能很多人都认为CPC转换的作用是强制让备用CPC跟主用CPC“同步”，其实这个理解是不正确的。转换CPC并不会立刻让两部CPC的工作模式同步，每一部CPC原本处于什么模式，转换之后还是什么模式。既然这样，那为什么在下降之后当座舱高度达到8500ft的时候进行一次CPC转换能有效避免虚假的“座舱高度过高”警告呢？我来仔细分析一下这个CPC转换程序背后的逻辑：&lt;/p&gt;&lt;p&gt;飞机正常下降之后当座舱高度开始上升，说明这时候主用CPC已经进入了下降模式。但这时候备用CPC却不一定也进入了下降模式，也就是所谓的“不同步”或者叫“延迟”，此时有两种可能性：&lt;br/&gt;A.备用CPC也进入了下降模式&lt;br/&gt;B.备用CPC还停留在巡航模式&lt;/p&gt;&lt;p&gt;平时飞行时大多数情况都是A，在这种情况下就算不转换也不会触发警告，不过转换一下也没坏处。&lt;/p&gt;&lt;p&gt;如果是情况B，只要飞机持续保持下降，备用CPC还是会探测到飞机已下降，从而进入下降模式，转换时机虽有延迟但大多数时候也不会延迟到座舱高度达到9550ft的报警高度，所以也不会出现警告。这就是为什么在以前，大家虽然都没有做8500转换CPC的程序，也很少出现这个假警告。&lt;/p&gt;&lt;p&gt;只有在极少数情况下，当主用CPC进入到了下降模式而备用CPC还停留在巡航模式正巧又遇到飞机改平，那么备用CPC就会一直停留在巡航模式，主用CPC因为在下降模式，会控制座舱高度一直上升，当上升到9550ft的时候，备用CPC就会发出警告。&lt;/p&gt;&lt;p&gt;在这种情况下，只要在座舱高度达到9550ft之前进行一次CPC的转换，就能使原本卡在巡航模式的CPC变成主用，转换之后处于巡航模式的主用CPC会控制座舱高度从上升转为下降，座舱高度不会达到9550ft，于是避免了触发警告。随后当飞机转入下降，这部停留在巡航模式的CPC迟早也会探测到下降，所以座舱高度又会从8000ft再次上升，这时候也不用担心到了9550ft会触发警告了，因为两部CPC都已经是下降模式了。&lt;/p&gt;&lt;p&gt;CPC转换这个动作并不能直接使两部CPC立刻同步。只是在某些特定的阶段，比如在飞机从巡航开始下降之后当主用CPC已经进入了下降模式的时候，CPC转换程序能确保两部CPC都进入下降模式，从结果上看，两部CPC确实“同步”了，所以才会给大家造成误解。大家一定要知道，转换CPC这个操作并不是在所有情况下都能让两部CPC同步，比如说中止进近之后的转换，后面会详细分析。&lt;/p&gt;&lt;p&gt;写到这里忍不住吐槽一下公司的手册，连猜带蒙才大致看懂写的什么意思：&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241207181588278827.jpg&quot; style=&quot;height:412px; width:400px&quot;/&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;了解了CPC转换并不一定能使两部CPC同步这个原理，我们再看一下中止进近之后的CPC转换程序：&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/2024120718190631631.jpg&quot; style=&quot;height:306px; width:400px&quot;/&gt;&lt;/p&gt;&lt;p&gt;为什么我说这个程序存在BUG？下面我会通过两起典型的案例分析来解释这个问题。&lt;/p&gt;&lt;p&gt;首先看一下空客A320飞机的CPC转换逻辑图&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/12/20241207193879867986.jpg&quot; style=&quot;height:349px; width:400px&quot;/&gt;&lt;/p&gt;&lt;p&gt;从这个图表中可以看到CPC从下降模式进入上升模式需要探测到持续60秒的飞机爬升，所以中止进近之后如果只上升60秒左右改平，就会导致两部CPC有可能其中一部进入了上升模式而另一部却停留在下降模式。一共有四种可能性：&lt;/p&gt;&lt;p&gt;A.两部都进入了上升模式&lt;br/&gt;B.两部都停留在下降模式&lt;br/&gt;C.主用进入了上升模式而备用停留在下降模式&lt;br/&gt;D.主用停留在下降模式而备用进入了上升模式&lt;/p&gt;&lt;p&gt;其中A和B两种情况下两部CPC处于“同步”状态，所以不管是否转换CPC都不会触发假警告。只有当C或者D两种情况出现时，才有可能触发这个警告。&lt;/p&gt;&lt;p&gt;先说情况D，这个案例是我亲自遇到的，2022年2月在邦达中止进近，改平之后未进行CPC转换（当时手册中还没有中止进近之后的CPC转换程序）。当时是从6300米上升到6600米，只上升了1000英尺就改平（爬升过程大概60秒左右），刚中止进近的时候两部CPC都处于下降模式，座舱高度13800ft。飞机上升一段时间之后备用CPC探测到了超过60秒的持续爬升，进入了爬升模式。但是主用CPC却由于未探测到持续60秒的爬升，还停留在下降模式，所以座舱高度也一直保持在13800ft没有下降。飞机在6600米改平之后不久，备用CPC进入了巡航模式，触发了“座舱高度过高”的警告。如果当时我们改平之后立即（备用CPC进入巡航模式之前）进行一次CPC的转换，就能避免这个假警告。&lt;/p&gt;&lt;p&gt;再说情况C，这个案例是2024年2月在拉萨中止进近，改平之后机组进行了CPC转换，在转换完CPC后不久触发了警告。当时是从6900米爬升到7200米，同样也是只上升了1000英尺就改平（爬升过程大概60秒左右），刚中止进近的时候两部CPC都处于下降模式，座舱高度11300ft，CPC2为主控，CPC1为备用。随后CPC2探测到爬升时间超过了60秒，进入到了上升模式，于是CPC2控制座舱高度下降到目标高度8000英尺。CPC1直到飞机改平都没有探测到60秒的持续爬升，所以未能进入上升模式，仍然停留在下降模式。改平之后不久CPC2从上升模式转为巡航模式，但是CPC1仍然停留在下降模式（因为下降模式无法直接跳到巡航模式）。当座舱高度下降到8500ft时，机组转换CPC，导致处于下降模式的CPC1成为了主控，处于巡航模式的CPC2被转换到了备用，成为主控的CPC1（下降模式）控制座舱高度从8500ft再次转为上升，当座舱高度上升到9550英尺时，处于备用状态的CPC2（巡航模式）触发了警告。&lt;/p&gt;&lt;p&gt;通过上面的案例分析我发现：情况C如果不转换CPC不会出现警告，转换之后反而会触发警告。情况D不转换CPC会出现假警告，如果转换则能避免这个警告。但是目前的FCOM手册的程序并没有区分这两种不同的情况，统一要求机组在改平之后进行CPC转换。也就是说机组现在所执行的这个程序，在某些情况下能避免假警告，但是在某些情况下不仅不能避免警告，反而还会导致假警告。&lt;/p&gt;&lt;p&gt;以上分析都是我个人的理解，不一定正确，因为缺乏更多的技术资料和案例可查阅，但是根据目前我所知的这几起案例，以上整个分析过程从逻辑上是能自洽的。如果大家有不同看法，请留言指出，不吝赐教！&amp;nbsp;&lt;/p&gt;</description><pubDate>Sat, 07 Dec 2024 17:49:12 +0800</pubDate></item><item><title>RNP进近的温度限制</title><link>https://blog.fairysoft.net/post/60.html</link><description>&lt;p&gt;我们先来看一个某公司最近的事件报告中的片段：&lt;/p&gt;&lt;p&gt;“机组在做进近准备收到签派电话：告知西双版纳/嘎洒机场盲降不工作，因机场温度过高不能使用 RNP LNAV/VNAV进近程序，可能使用 LNAV 或 VOR/DME 进近。机组收到消息后主计划按 RNP LNAV 程序准备，二计划按 VOR/DME 程序准备……。13:56 机组首次联系嘎洒进近（后简称进近），被告知使用 16 号 RNP 程序进近，机组考虑温度超限制，提出使用 LNAV 进近，进近告知机组：不能使用 LNAV 程序，更改指令使用 16 号跑道 VOR/DME 进近……”&lt;/p&gt;&lt;p&gt;看完这段报告，我们来思考一个问题：版纳机场16号RNP进近航图上标注了这样一个限制：“无温度补偿的航空器，运行温度范围为0℃-29℃(Baro-VNAV)”。现在机场气温30℃，能否运行RNP进近程序？&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/7/2024072801340481481.jpeg&quot; style=&quot;height:120px; width:338px&quot;/&gt;&lt;/p&gt;&lt;p&gt;要回答这个问题，首先要弄清楚RNP进近中的“LNAV/VNAV”与“LNAV”是什么。我们都知道LNAV代表水平导航、VNAV代表垂直导航，这是航空器的两个导航功能。但是在RNP进近程序中，这两者却另有所指。我查阅了民航局飞标司的咨询通告《在终端区和进近中实施RNP的运行批准指南》(AC-91-FS-2010-01R1)。通告中是这么写的：“RNP 进近一般包括 LNAV 和 LNAV/VNAV 两类运行最低标准。”&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/7/20240728014747214721.jpeg&quot; style=&quot;height:132px; width:480px&quot;/&gt;&lt;/p&gt;&lt;p&gt;按照这个咨询通告的描述，“LNAV/VNAV”和“LNAV”在RNP进近中是两类最低标准，也可以理解为RNP进近程序的两个子类，前者是类精密进近，使用DA，后者是非精密进近，使用MDA。这两类进近由于垂直导航源使用的是气压式高度表，所以机场的温度会对飞机的飞行高度产生影响，简单来说就是温度高的时候飞机飞行轨迹会比预期高，温度低的时候飞机飞行轨迹会比预期低。于是程序设计上会对没有温度补偿功能的飞机（比如说常见的A320和B737）做出限制，当温度超出限制范围，就不能使用LNAV/VNAV的最低标准，只能使用LNAV的最低标准，也就是降级为非精密进近。使用LNAV最低标准的RNP进近和其他非精密进近程序一样是没有温度限制的，比如丽江20号的RNP进近只有LNAV标准，航图上就没有标注温度限制。&lt;/p&gt;&lt;p&gt;现在又有了一个新问题：同样是使用气压高度表作为垂直导航源，为什么类精密进近有温度限制而非精密进近没有温度限制呢？对这个问题，我翻了一下书，没找到标准答案，所以只能自己揣测一下：这大概是因为类精密进近在设计飞行程序的时候没有给飞行员提供低温修正的程序。而传统的非精密进近由于可以做低温修正，所以不需要设置运行温度的下限。类精密进近把垂直引导完全交给计算机控制，不允许飞行员对计算机程序数据进行修改，这其实是一种很先进的设计理念。只是目前受限于技术的原因使用了气压高度表来作为垂直导航数据源，所以才不得不加入温度限制。等到使用LPV技术的RNP进近在国内普及之后，温度限制的问题就不复存在了。&lt;/p&gt;&lt;p&gt;好了，总结一下：温度超出范围并不意味着RNP程序不可用，只是对于大部分不具备温度补偿功能的飞机来说不能执行LNAV/VNAV的最低标准，只能使用LNAV的最低标准，也就是降级为非精密进近，降级的目的是为了让你能做低温修正程序。但是温度高于最高限制值为什么也要降级为非精密进近，这一点我始终没有想明白。虽说温度高会带来下滑角过大的问题，但是降级为非精密进近之后并不能解决这个问题，因为我们只有低温修正程序而没有高温修正程序。&lt;/p&gt;&lt;p&gt;了解了以上这些知识，我们再来看一下具体操作上问题：RNP进近使用LNAV/VNAV最低标准和使用LNAV最低标准分别该怎么飞？很多人对此都有一个误区，认为LNAV不能使用FINAL APP模式来引导。为此我专门查阅了几个航司的飞行手册（空客系列），基本上大同小异：对于使用LNAV/VNAV最低标准的RNP进近，只能使用FINAL APP这一种模式来引导；对于使用LNAV最低标准的RNP进近，可以使用FINAL APP模式也可以使用NAV FPV模式，而且公司更推荐的是FINAL APP模式，NAV FPA模式大致是为了用于低温修正程序。&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/7/20240729025375537553.jpeg&quot; style=&quot;height:456px; width:480px&quot;/&gt;&lt;/p&gt;&lt;p&gt;最后我们再回过头来分析一下文章开头那个事件报告中的几个问题，首先，机组和签派混淆了概念，把LNAV/VNAV当成了进近程序，认为温度超出限制范围就&amp;quot;不能使用LNAV/VNAV进近程序&amp;quot;，于是向管制员申请&amp;quot;使用LNAV进近&amp;quot;，管制员也许是没明白机组的意图，以为机组无法执行RNP进近程序，于是指挥机组使用VOR进近，而版纳16号VOR进近的下滑角是3.5度，比RNP还大，30多度的高温让梯度变得更陡，飞行难度更大，从而导致后面发生了一些其他不安全事件。机组当时其实不需要向管制员申请，直接执行LNAV的最低标准就可以了。至于在进近引导方式上，机组依然可以使用FINAL APP的模式来引导。我查阅了飞行操作手册，对于垂直管理引导模式，手册上只对低温做了限制，没有对高温做出使用限制。&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;&quot; src=&quot;https://www.fairysoft.net/zb_users/upload/2024/7/20240729030444854485.jpeg&quot; style=&quot;height:121px; width:480px&quot;/&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;在文章最后我申明一下：文章开头写的“某公司的事件”是我瞎编的，如有雷同，纯属巧合。&lt;/em&gt;&lt;/p&gt;</description><pubDate>Sun, 28 Jul 2024 03:19:42 +0800</pubDate></item><item><title>安卓应用的https抓包简明教程</title><link>https://blog.fairysoft.net/post/59.html</link><description>&lt;p&gt;基本原理：在电脑上运行抓包工具Fiddler，安卓手机跟电脑在同一个局域网内，Fiddler会启动一个网络代理，在安卓手机的WIFI设置里面手动指定这个代理，手机所有的流量都会通过Fiddler来转发，于是Fiddler就能抓取手机的http数据包了。但是https的包是经过加密的，Fiddler抓到的包无法显示包的内容，这时我们就需要在手机上安装Fiddler的证书，并信任这个证书。在安卓手机上安装和信任证书需要Root，由于手机Root不太方便，所以我使用的是夜神安卓模拟器。&lt;/p&gt;

&lt;p&gt;方法很简单，教程如下：&lt;/p&gt;

&lt;p&gt;1.下载安装抓包工具Fiddler和夜神模拟器，下载地址和安装方法本文略过&lt;/p&gt;

&lt;p&gt;2.下载安装openssl，下载地址：http://slproweb.com/products/Win32OpenSSL.html&lt;/p&gt;

&lt;p&gt;3.安装完openssl之后，在环境变量PATH中添加openssl的可执行文件路径，比如说： C:\Program Files\OpenSSL-Win64\bin，然后打开命令提示符或者Power Shell输入命令：openssl version 查看openssl版本（验证是否安装成功）&lt;/p&gt;

&lt;p&gt;4.打开Fiddler、选项、HTTPS、勾上&amp;ldquo;抓取HTTPS&amp;rdquo;，然后点右边的Actions按钮，在下拉菜单中点击&amp;ldquo;导出证书到桌面&amp;rdquo;，你会发现桌面上有了一个名为&amp;ldquo;FiddlerRoot.cer&amp;rdquo;的证书文件&lt;/p&gt;

&lt;p&gt;4.用openssl将证书转换为pem格式：openssl x509 -inform DER -in FiddlerRoot.cer -out cacert.pem，执行完成之后桌面上会出现一个名为&amp;ldquo;cacert.pem&amp;rdquo;的pem格式证书&lt;/p&gt;

&lt;p&gt;5.获取cacert.pem的hash信息：openssl x509 -inform PEM -subject_hash_old -in cacert.pem，比如说hash信息为&amp;ldquo;269953fb&amp;rdquo;&lt;/p&gt;

&lt;p&gt;6.用这个hash信息来重命名cacert.pem文件为269953fb.0，然后将269953fb.0复制到安卓设备/system/etc/security/cacerts/这个目录下（文件管理器要支持root才行，我用的是ES文件浏览器）&lt;/p&gt;

&lt;p&gt;7.在手机设置---&amp;gt;安全--&amp;gt;加密凭证--&amp;gt;信任的凭证中找到名为&amp;ldquo;DO_NOT_TRUST&amp;rdquo;的证书，看一下有没有启用，如果没启用的话就启用。&lt;/p&gt;

&lt;p&gt;完成！&lt;/p&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;
</description><pubDate>Mon, 16 Oct 2023 15:44:18 +0800</pubDate></item></channel></rss>