前文《搞了个私人云盘,做点笔记》所实现的本地网盘已经运行了两年左右,一直很稳定,也为亲友提供了一个自主的资源分享中心。但随着最近NAS接入的互联网环境发生变化,才发现之前一些看似理所当然的东西如今已不复存在,于是必须做出些变动才行。
首先面临的问题是:
- 个人宽带没有公网IP地址。咨询客服后得知全省都已不再提供公网IP服务,除非架设专线;
- 没有公网IP,意味着公网无论通过DDNS的域名还是IP都无法再访问到内网NAS;
- 这时候唯一的解决方法是使用IPv6。
随着19年底工信部开始全国强制普及IPv6,从移动网络到光纤宽带都提供了IPv6地址,但网络设备可没那么快跟上。如今大部分路由器和猫也只是独立加了个IPv6选项,分配一下IP地址而已。而涉及安全的防火墙大多是默认全禁,少数是有个开关选项,而精细的配置往往在少数刷机软路由下才有。
还好,检查了一下自己的路由之后,发现关闭防火墙可以保证公网对内网IPv6设备的访问。第一个问题解决了,但不是完全解决,因为会有安全隐患。todo:更换能配置详细IPv6规则的路由器。
首先把原有云盘的功能整理出来,共四块:
- nextcloud:云盘应用的主体
- nginx:负责代理流量及SSL加密
- acme.sh:申请证书并定时更新
- ddns:绑定域名和本地IP
而之前这四部分功能使用一个docker-compose配置进行启动(在NAS里称为Container Station的APP),位于同一虚拟网络下的四个container中。
其中nextcloud与IP无关,可以不做改动;
acme.sh需要和nginx联动,以便更新证书后自动加载,不受IPv6影响;
到了nginx这里,问题来了。
docker默认不支持IPv6,而NAS自带的docker又是个魔改版,不光配置文件找不到,很多东西重启后还会自动复原。也就是没办法一劳永逸地开启IPv6。
同理,ddns脚本也没法在docker中运行。因为它的原理是先访问公网检测本机的IPv6地址,再调用cloudflare的API更新dns信息。在不支持IPv6的容器中无法获取地址。
经过反复实验,找到了可行的解决方案。
首先是nginx,由于要实现证书自动更新,不改动原有的容器化体系。而是在NAS主机运行一个socat端口映射,将它在IPv4地址下使用的端口映射到IPv6地址的端口。
而遇到的最大问题是:QNAP的NAS和正常linux完全不同,既没有socat命令,也没有后台运行服务的nohup命令。更不能通过正常的包管理器或本地编译来安装上述命令。
不过还有另一条路,就是用第三方包管理器Entware-std(网上搜到一些旧版教程可能会提到Entware-3x或 Entware-ng,但它们两者均已并入):
下载并手动安装到App Center后,Entware-std并没有图形界面可用,须登录SSH并手动运行。
opkg命令具体路径在/opt/bin,安装后的包也都位于此处。由于无法固定添加PATH所以所有命令执行时必须输入完整路径:
/opt/bin/opkg update
/opt/bin/opkg install nano socat coreutils-nohup
注:
nohup的包名是coreutils-nohup;
安装nano是个人习惯,编辑简单文本比vi顺手。
所需命令安装完成后,需要添加到启动脚本以保证重启后服务依然可用。
这里遇到另一个难题:QNAP自带的启动脚本功能在Entware管理器之前运行,此时nohup和socat尚未加载到指定路径,无法使用。
所以只能换个思路,在Entware自己启动后再执行我们想要的命令。
编辑/etc/init.d/Entware.sh,在”Enable Entware/opkg”一段的末尾添加端口映射命令:
/opt/bin/nohup /opt/bin/socat TCP6-LISTEN:4444,reuseaddr,fork TCP4:127.0.0.1:443 >/dev/null 2>&1 &
将nginx开放的443端口映射到本地IPv6的4444端口。
重启后,端口即可正常通过公网访问。
下一个问题是ddns,如前所述,docker容器中无法获取本机IPv6地址,最好的方法仍然是直接在主机定时运行。
这里使用了https://github.com/EngrZhou/CloudFlare-DDNS-Script的脚本,优点是无需过高权限(国产脚本作者特别爱用Global Key)。
此脚本需要jq命令解析API返回的json,使用opkg install jq即可。
修改脚本前端的配置项,包括:
SUB_DOMAIN=所用域名
ZONE_ID=主域名zone id
TOKEN_ID=所用api(需授权指定域名的操作,可直接用acme.sh的API,权限相同)
JQ_PATH="/opt/bin/jq"
由于本环境只需IPv6的ddns,所以可将脚本中判断IPv4的部分直接屏蔽,避免重复。
在cloudflare的控制台添加一条子域名的AAAA记录,地址设置为[::1]。
为脚本添加执行权限,运行,如果成功的话在cloudflare控制台里可以直接看到域名的IP地址已变化为实际地址。
测试成功后,即可使用crontab定时自动执行。
根据官方文档,不要直接使用crontab -e,而是编辑/etc/config/crontab文件。在末尾添加一条:
*/5 * * * * /path/to/ipv6-ddns.sh > /dev/null
然后运行命令
crontab /etc/config/crontab && /etc/init.d/crond.sh restart
即可。(qnap的crontab解释详见这里)
2022.4 发现新问题:ddns无法更新。经查是脚本中获取IP地址的curl -s https://api-ipv6.ip.sb/ip 命令返回403错误,改为curl -s -6 ip.sb即恢复正常。
经过上述改造,原先无法提供公网IP的环境即可通过原域名无缝转换到IPv6访问。