欢迎来到我的博客

Hugo 使用指南

Hugo 使用指南 适用版本:Hugo v0.168.0(Extended)。命令以 macOS/Linux 的 shell 为例。根据 Hugo 官方文档整理,主题私有配置以实际主题仓库为准。 1. 安装与版本检查 macOS(Homebrew): brew install hugo hugo version hugo env Linux 可使用对应发行版软件包或 Hugo 官方发布的二进制文件。使用 SCSS/Sass 或某些主题资产管道时,需要确认安装版本包含 Extended 功能。 2. 创建项目 hugo new project myblog cd myblog git init 典型目录结构: myblog/ ├── archetypes/ # 新文章模板 ├── assets/ # 需通过资源管道处理的资源 ├── content/ # Markdown 内容 ├── data/ # 数据文件 ├── i18n/ # 主题界面翻译 ├── layouts/ # 自定义模板,可覆盖主题模板 ├── static/ # 原样复制到网站根路径 ├── themes/ # 本地主题或 Git 子模块 ├── hugo.toml # 网站配置(也可使用 YAML 等格式) └── public/ # 构建产物(运行 hugo 后生成) Hugo 本体不附带完整博客外观。若没有可用主题或自定义布局,可能出现 found no layout file for "html" 的警告。 ...

October 17, 2026 · 3 min

排查旁路由 DNS 超时:sing-box 上游查询走错网络接口

在一台用作旁路由的 Armbian 上同时运行 sing-box 和 WireGuard。旁路由本机可以解析内网域名,下游终端将其设为 DNS 服务器却始终超时。最终发现问题不在 DNS 匹配规则,而在 sing-box 向上游 DNS 发出的流量选错了出口接口。 本文示例地址均为重写后的示意值,不是原始网络配置;保留了私有网络的拓扑关系与协议行为。 网络拓扑与现象 角色 示例地址 作用 下游终端 192.168.10.20 DNS 客户端 旁路由 LAN 192.168.10.254 / eth0 sing-box DNS 监听地址 旁路由 WireGuard 10.77.0.11 / wg0 隧道客户端 内网 DNS 10.77.0.1:53 WireGuard 对端 DNS 拓扑如下: 终端 192.168.10.20 │ DNS 查询 ▼ 旁路由 192.168.10.254:53 (sing-box DNS 入站) │ 期望:由 wg0 向 10.77.0.1:53 查询 ▼ WireGuard 对端 DNS 10.77.0.1:53 内网测试域名采用 service.home.arpa(示例)。现象是: ...

October 23, 2026 · 2 min

Armbian 上 sing-box 开启 auto_redirect 后 IPv6 HTTPS 超时:一次回环地址缺失的排查

在 Armbian 上部署 sing-box TUN 后,我遇到了一个不太直观的问题:IPv6 ping 能通,但开启 auto_redirect 后,IPv6 HTTPS 连接超时。 最终发现,故障并不在 IPv6 公网路由或代理节点,而是 Linux 回环接口 lo 缺少 ::1/128 地址。本文记录定位过程和修复方法。 环境 Linux:Armbian,ARM64,6.18 系列内核 sing-box:1.14.x 网络管理:Netplan + NetworkManager 透明代理:TUN,auto_route: true,auto_redirect: true IPv6:上游自动配置,接口已有全局 IPv6 地址和 RA 默认路由 为保护隐私,本文省略了真实公网地址、MAC、路由器地址、域名及自定义内部路由表编号。以下示例中的 ULA 地址和测试目标用于演示。 故障现象 直接测试 IPv6 HTTPS: curl --noproxy '*' -6 -v --connect-timeout 5 \ --resolve 'cloudflare.com:443:[2606:4700::6810:85e5]' \ https://cloudflare.com/ 开启 sing-box 后: * Trying [2606:4700::6810:85e5]:443... * Connection timed out after 5002 milliseconds curl: (28) Connection timed out 对照实验: ...

October 23, 2026 · 3 min

UFW 与 sing-box TUN auto_redirect 冲突:动态端口被拦截的排查

在 Linux 旁路由上同时运行 UFW 和 sing-box TUN 时,可能出现一种容易误判的故障:sing-box 本机运行正常,但局域网设备通过旁路由访问互联网失败。即使放行了当时被拦截的 TCP 端口,重启 sing-box 后问题也可能再次出现。 本文记录一种实际观察到的原因和待回归验证的修复方式。示例均使用文档保留地址,不对应真实网络。 环境和症状 Linux 旁路由,LAN 接口为 eth0,地址 192.0.2.1(仅作示例)。 下游终端 192.0.2.20,使用旁路由作为 IPv4 网关。 sing-box 的 TUN 接口名为 tun0,开启 auto_route 和 auto_redirect。 UFW 默认拒绝未经允许的入站流量。 sing-box 的关键配置: { "type": "tun", "interface_name": "tun0", "auto_route": true, "auto_redirect": true } 故障期间日志类似: [UFW BLOCK] IN=eth0 OUT= SRC=192.0.2.20 DST=192.0.2.1 PROTO=TCP DPT=24461 SYN DPT=24461 只是一次运行时观察到的动态端口,不应该把它永久写死在 UFW 里。OUT= 为空,提示包正在进入本机的 INPUT 路径,而不只是穿过 FORWARD 路径。 追踪 NAT 重定向 检查 sing-box 监听: sudo ss -lntup | grep sing-box 检查 nftables: ...

October 23, 2026 · 2 min

使用 CrowdSec 为 Debian 服务器实现恶意 IP 自动封禁

使用 CrowdSec 为 Debian 服务器实现恶意 IP 自动封禁 公网服务器每天都会遇到各种自动化扫描。 即使没有主动公开服务地址,SSH 端口也经常会收到针对 root、admin、test 等用户名的登录尝试。Web 服务则可能遭遇针对 .env、wp-admin、phpinfo.php 等路径的探测。 这些请求大多来自自动化扫描程序。虽然不一定构成实际入侵,但会产生大量无意义的日志,同时增加服务暴露面。 单纯依靠防火墙静态规则,难以根据攻击行为动态调整封禁名单。 因此,我选择尝试 CrowdSec,通过服务器日志识别恶意行为,再联动 nftables 自动封禁攻击来源。 本文记录在 Debian 12 上部署 CrowdSec 的过程,以及它如何判断一个 IP 是否应该被封禁。 一、CrowdSec 是什么? CrowdSec 是一个开源的入侵检测与安全响应系统。 它通过分析 SSH、Web Server 等服务产生的日志,检测暴力破解、漏洞扫描、恶意爬虫等行为。 与传统的静态 IP 黑名单不同,CrowdSec 主要根据行为特征决定是否封禁,并且可以利用社区威胁情报。 CrowdSec 的工作可以分为两部分: Security Engine:读取日志,识别攻击,生成封禁决策。 Remediation Component(Bouncer):执行封禁决策,例如修改防火墙规则。 两者相互独立。 也就是说,仅安装 CrowdSec Security Engine,并不会自动阻止网络连接。 1.1 工作流程 SSH / Caddy / Nginx | v Application Logs | v CrowdSec Parser | v Detection Scenarios | v Security Alerts | v Decision Profiles | v Local API | v Firewall Bouncer | v nftables | v Block IP CrowdSec 将日志分析与防火墙执行解耦,意味着它既可以在单台服务器上使用,也能通过中心 API 向多台服务器下发封禁决策。 ...

October 19, 2026 · 6 min

中国秸秆禁烧政策的历史:从传统农耕到现代环境治理

中国秸秆禁烧政策的历史,实际上是中国农业现代化、农村能源转型、环境治理和基层行政管理方式演变的一个缩影。 最关键的历史结论是:中国并非自古存在大规模秸秆焚烧问题,也不是近年来才开始禁止烧秸秆。现代秸秆禁烧制度在20世纪90年代逐步建立,1999年形成全国性的管理规定,2008年前后明显强化,2013年以后进一步纳入大气污染防治体系。 更值得研究的,是政策治理目标与执行方式之间的变化: 最初的重点是公共安全。 尤其是机场、铁路、公路附近的焚烧风险,同时兼顾大气污染。 后来逐渐转向区域空气质量管理。 秸秆焚烧与城市雾霾、PM2.5治理建立了更紧密的政策联系。 再后来出现基层执行范围扩大和管理方式强化的问题。 一些地方采取全面禁烧、网格巡查和责任追究,导致政策成本与农民生产需求之间的矛盾。 政策争论的核心不是焚烧是否产生污染,而是应当禁止到什么程度,以及由谁承担处理秸秆的经济成本。 一、历史时间线:秸秆禁烧如何一步步形成 20世纪80年代以前 秸秆是有使用价值的农业资源 主要用于烧饭取暖、牲畜饲料、垫圈积肥、堆肥和建材。露天焚烧存在,但大量秸秆并非没有用途。 1980年代末—1990年代 大规模集中焚烧成为社会问题 农业机械化、农村生活燃料转型、粮食增产及农业劳动力转移,使部分地区秸秆集中剩余,焚烧逐渐成为快速清田的方式。 1999—2000年 全国性禁烧管理制度确立 1999年六部门发布《秸秆禁烧和综合利用管理办法》;2000年修订《大气污染防治法》,明确特定区域的秸秆露天焚烧禁令。 2008年 奥运空气质量保障推动强化禁烧 京津冀及周边多个省份实施重点时段严格禁烧,城市及重点区域管控范围扩大。 2013—2017年 与PM2.5及大气污染防治绑定 大气污染防治行动全面推进。2015年修订《大气污染防治法》,规定由省级人民政府划定禁烧区域,并明确相关法律责任。 2018年至今 基层精细化监管与政策争议并存 网格监管、卫星火点监测及秸秆综合利用持续推进,部分地区也探索分区、分时段的焚烧管理。 上面的阶段划分依据国家层面的重要政策节点,而非认为每一年全国都同步改变。 二、1949—1980年代:为什么过去农民很少需要大规模集中烧秸秆? 要解释今天的禁烧政策,必须先理解一个历史变化: 秸秆从过去的生产生活资源,逐渐变成了部分地区需要付费处理的农业副产物。 1. 传统农村社会有较完整的秸秆利用体系 过去的小农经济中,秸秆具有多种实际用途。 用途 农村的典型做法 生活燃料 使用麦秸、稻草、玉米秆烧饭取暖 牲畜饲料 秸秆切碎后喂牛、羊等反刍动物 农业肥料 堆肥、沤肥、垫圈积肥、直接还田 生活材料 编织草席、制作草绳、屋顶覆盖 农业辅助 覆盖地面、保墒、防止部分土壤侵蚀 这并不意味着过去不存在焚烧。农民同样会焚烧残茬、杂草,或通过草木灰补充部分矿质养分。 ...

October 17, 2026 · 2 min

一把火,三十年:中国秸秆禁烧为何从重点管控走向全域禁烧?(二)

2008年的奥运保障是全面禁烧的重要先例,江苏2009年已提出全域禁烧目标。2013年以后,卫星火点、绩效考核与财政奖补共同改变了政策的执行逻辑。

October 17, 2026 · 2 min

My First Post

March 22, 2025 · 0 min

Azure入门指南(一)

阅读前提:了解国内外云服务(如阿里云、Amazon Web Services),并有一定的使用经验 注册 注册一个微软账户即可,具体步骤可以参考知乎的这篇文章(如何白嫖微软Azure12个月及避坑指南 - 知乎 (zhihu.com)), 注意:这篇文章写了有很久了,azure的策略可能已经更改,由此可能产生费用,大家实践的时候一定要看清收费策略,或者提前咨询客服,避免不必要的损失。 Azure服务 以下是常用的azure服务: 以下表格是Azure、阿里云和AWS的云服务的对应关系 Azure 阿里云 AWS 备注 订阅(Subscriptions) 资源组(Resource groups) 资源组 虚拟机(Virtual machine) 云服务器ECS EC2 磁盘(Disk) 云盘 卷(Volume) 虚拟网络(Virtual network) 专有网络 VPC 网络接口(Network Interface) 交换机 网络接口(Network interface) 网络安全组(Network security group) 安全组 安全组(Security Group) 公共 IP 地址(Public IP address) 公网IP 公有 IPv4 /IPv6地址(Public IPv4/IPv6 address) SSH 密钥(SSH key) 密钥对 密钥对(Key Pair) 虚拟机 创建 基本 ...

June 18, 2023 · 2 min

ChatGPT接入微信

下载或打开 PDF ChatGPT接入微信 from flask import Flask, request, make_response import requests import hashlib import xml.etree.cElementTree as ET import time # from WXBizMsgCrypt import WXBizMsgCrypt import base64 import redis app = Flask(__name__) # 见下图 headers={'Authorization': 'Bearer **-********************'} appid = '***********' token = '*******************' encodingAESKey = '************************' r = redis.Redis() @app.get('/completions') def sig(): args = request.args print(args) signature = args['signature'] echostr = args['echostr'] timestamp = args['timestamp'] nonce = args['nonce'] arr = [token, timestamp, nonce] arr.sort() s = ''.join(arr) sha1 = hashlib.sha1(s.encode('utf-8')).hexdigest() if sha1 == signature: return echostr, 200 return 'error', 500 @app.post('/completions') def completions(): t0 = time.time() xmlStr = request.stream.read() print(f'xmlStr = {xmlStr}') xmlData = None try: xmlData = ET.fromstring(xmlStr) except Exception: return '', 200 msg_type = xmlData.find('MsgType').text if msg_type == 'text': ToUserName = xmlData.find('ToUserName').text FromUserName = xmlData.find('FromUserName').text Content = xmlData.find('Content').text json = { "model": "text-davinci-003", "prompt": Content.strip(), "max_tokens": 4000, "temperature": 1.0 } key = str(FromUserName + ':' + Content.strip()) print(f'question:{key}') reply = ''' <xml> <ToUserName><![CDATA[%s]]></ToUserName> <FromUserName><![CDATA[%s]]></FromUserName> <CreateTime>%s</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[%s]]></Content> </xml> ''' answer = '没听清,再说一遍' cache = r.get(key) if cache: answer = cache.decode('utf-8') r.delete(key) elif cache is None: r.set(key, '', ex=60) text = '' try: res = requests.post('https://api.openai.com/v1/completions', headers=headers, json=json).json() text = res['choices'][0]['text'] print(f'res = {res}') except Exception as e: print(f'error:{e}') r.set(key, None) return '', 555 t1 = time.time() if t1 - t0 > 4.5: r.set(key, text, ex=3600) return '', 555 else: answer = res['choices'][0]['text'] elif cache.decode('utf-8') == '': print('sleep') time.sleep(4.5) answer = f'思考中...\n过会儿再对我说一遍[{Content.strip()}]' else: return 'success' response = make_response(reply % (FromUserName, ToUserName, str(int(time.time())), answer.strip())) response.content_type = 'application/xml' return response else: return 'success' if __name__ == '__main__': app.run(port = 9999) appid:必填 token:必填 encodingAESKey:选填(当需要加密时必填) ...

February 8, 2023 · 2 min