一切都要从前几天给手机刷新 ROM,导致数据丢失说起。

前些日子,我的 RedmiK40S MIUI13 突然给我自动更新至 MIUI14,这违背了我的意愿。但这还不至于让我动刷机的年头,毕竟年事已高,不再那么想折腾手机。可这次更新,不单单是 MIUI 版本的提升,更是 Android 12 到 13 的版本升级。这直接导致了我的钛备份闪退了,并且使用钛备份还原在 a12 上备份的应用,如果勾选还原应用数据,则必然导致对应应用闪退。应用备份出了问题我是无法接受的。于是,开始上 XDA 找 ROM,随便下了几个,准备动手。

我自觉这么多年刷机从未失手,再不济也能在 recovery 里面把数据迁移出来再进行格式化。不成想多年未刷机,出了个新名词"a/b slot". 一下子给我整懵了,在刷完 rom 之后的第一次启动卡 logo. 然后再进 recovery 直接给我把 data 分区锁上了。换了几个 recovery 还是无法解锁 data,最后只能无情 format data. 数据无价啊!

后来翻了几篇帖子,弄弄 a/b slot 到底是啥意思,放在下面供以后参考吧:

  1. How A/B Partitions and Seamless Updates Affect Custom Development on XDA
  2. How to fix unable to mount data internal storage 0mb in twrp permanently
  3. A/B slots flashing in TWRP

此外,这次踩坑在此处做了简单记录。折腾一宿加一天之后,终于用上了新 ROM,贴几张美图:


言归正传,新 ROM 装好之后,发现钛备份在 a12 创建的备份,如果还原应用数据,统统都会闪退。感概一波钛备份的过时之后,我寻到了两个替代品:

  1. Neo Backup
  2. Data Backup

并且这次迎来了新的需求:在本地备份 app 数据之后,即时在移动硬盘上做一个备份。于是我想起来,我的小米路由器 3 具有一个 USB 接口。有没有可能将移动硬盘插在路由器上,然后连接该路由器的设备都可以访问这个移动硬盘呢?

然后兴冲冲的打开小米路由器的管理界面,发现文件共享需要下载他的软件,过于辣鸡了。一怒之下,给小米路由器 3 刷了 OpenWrt1.

给路由器刷 OpenWrt

这个网上教程很多,列举一下:

  1. 小米路由 3 刷 OpenWRT
  2. 小米路由器 3 刷机记录
  3. [OpenWrt Wiki] Xiaomi Mi WiFi R3 (Mi Wifi Router 3 / MIR3 / MI3)
  4. 小米路由器 3 刷机潘多拉 (Openwrt) 以及刷回教程

看这几篇应该够了,其中需要注意的点:

  1. 用于开启 ssh 功能的 U 盘必须是 fat 文件系统;
  2. 先刷开发版固件,再降级到 miwifi_r3_all_55ac7_2.11.20.bin,2.11.20 版本更容易打开 ssh.

OpenWrt 路由器部署 WebDAV 服务器

刷好之后,打开网关地址进入管理界面,如下图所示:

LuCI of X-Wrt

LuCI(Lua Configuration Interface)是 OpenWrt 的前端管理界面,默认基于 uhttpd 运行,而它不支持 WebDAV,所以我们换一个支持的 – lighttpd.

下面是我安装的 lighttpd 的相关软件包,可以用 luci 前端安装,也可以 ssh 到路由器执行opkg install lighttpd-xxx.

1
2
3
4
5
6
7
root@X-WRT-27AD:~# opkg list-installed | grep lighttpd
lighttpd - 1.4.69-1
lighttpd-mod-auth - 1.4.69-1
lighttpd-mod-authn_file - 1.4.69-1
lighttpd-mod-cgi - 1.4.69-1
lighttpd-mod-openssl - 1.4.69-1
lighttpd-mod-webdav - 1.4.69-1

接下来就需要编辑配置文件了,lighttpd 的配置文件位于/etc/lighttpd下。经过配置的目录结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
root@X-WRT-27AD:/etc/lighttpd# ls -R
.:
certs          conf.d         lighttpd.conf  lighttpd.user  mime.conf

./certs:
lighttpd.pem

./conf.d:
20-auth.conf        30-cgi.conf         30-webdav.conf      99-luci.conf
20-authn_file.conf  30-openssl.conf     50-http.conf

Lighttpd 采用模块化的配置方式,主配置文件为lightttpd.conf,其会自动将conf.d文件夹中的配置加载出来。所以我们对单模块做配置,只需要在 conf.d 文件夹中新增相关配置文件即可。

例如,conf.d 中的 30-webdav.conf 就是 WebDAV 服务相关的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
server.modules += ( "mod_webdav" )

$HTTP["url"] =~ "^/dav($|/)" {
  webdav.activate = "enable"
  webdav.sqlite-db-name = home_dir + "/webdav.db"

  ## add by yychi, see: https://openwrt.org/docs/guide-user/services/nas/webdav
  server.document-root := "/mnt/sda4/"  # 用作 WebDAV 存储的根目录
  auth.backend = "plain"
  auth.backend.plain.userfile = conf_dir + "/lighttpd.user"  # 用户&密码配置
  auth.cache = ("max-age" => "3600")  # 输入密码后的过期时间

  ## 用户路径配置
  auth.require := (
    "/dav/yychi" => ("method" => "basic", "realm" => "disk for yychi", "require" => "user=yychi|user=admin,
    "/dav/yukynn" => ("method" => "basic", "realm" => "disk for yukynn", "require" => "user=yukynn|user=admin,
    "/dav/" => ("method" => "basic", "realm" => "WebDAV Server", "require" => "valid-user"),
  )
}

其中重要的地方我都加了注释,猛击 这里 详细了解配置语法。简单说明下第三行是个 条件配置,意思是 url 匹配后面这个正则表达式的话,scope 里面的配置才生效。其中,home_dir 和 conf_dir 这两个变量均来自于父文件夹的 lighttpd.conf:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@X-WRT-27AD:/etc/lighttpd# cat lighttpd.conf 
### Configuration Variables (potentially used in /etc/lighttpd/conf.d/*.conf)
var.log_root    = "/var/log/lighttpd/"
var.server_root = "/www/"
var.state_dir   = "/var/run/"
var.home_dir    = "/var/lib/lighttpd/"
var.conf_dir    = "/etc/lighttpd"
var.vhosts_dir  = server_root + "/vhosts"
var.cache_dir   = "/var/cache/lighttpd"
var.socket_dir  = home_dir + "/sockets"
...

接下来配置 WebDAV 的用户和密码,

1
echo yychi:123456 >> /etc/lighttpd/lighttpd.user

可以配置多个用户,以及简单的权限管理。比如我的配置/dav/yychi文件夹只能给用户 yychi 或 admin 访问,/dav/yukynn文件夹只能给 yukyyn 或 admin 访问。

这里认证方式是明文密码,其实还有其他方式,不过需要安装其他包,觉得太麻烦,而且是局域网比较安全所以就不管了。

配置到现在就差不多了,让我们启动一下:

1
2
3
4
service uhttpd stop  # 关闭 uhttpd
service uhttpd disable  # 取消 uhttpd 的自启动
service lighttpd start
service lighttpd enable

用同一局域网下的手机连接验证一下: xplore-webdav

Ok,现在我们已经在路由器上部署好了 WebDAV server.

LuCI 后端更改

如果你打开网关地址(X-Wrt 默认是 192.168.15.1),你会发现 LuCI 已经打不开了。当然了,因为我们关闭了 LuCI 的后端 uhttpd,现在我们要把 LuCI 后端改为 lighttpd,这一点 OpenWrt Wiki 上有很详细的教程,这里我们就简短概括一下。

主要使用 lighttpd 的 cgi 功能,使得当用浏览器访问网关地址时,重定向到 luci 启动的 cgi 脚本。

1
2
root@X-WRT-27AD:/www/cgi-bin# ls
cgi-backup    cgi-download  cgi-exec      cgi-upload    luci

这里的luci就是启动脚本。

具体配置参考这里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
root@X-WRT-27AD:/etc/lighttpd/conf.d# cat 99-luci.conf 
## Necessary LUCI configuration
## see: https://openwrt.org/docs/guide-user/luci/luci.on.lighttpd
cgi.assign += ( "/cgi-bin/luci" => "",
                "/cgi-bin/cgi-backup" => "",
                "/cgi-bin/cgi-download" => "",
                "/cgi-bin/cgi-exec" => "",
                "/cgi-bin/cgi-upload" => "" )

server.username := ""
server.groupname := ""

最后,

1
service lighttpd restart

重启 lighttpd 查看效果,现在访问网关地址应该可以打开 LuCI 了。

Lighttpd 启用 HTTPS

到这一步为止,我们已经无痛在 OpenWrt 上搭建了一个 WebDAV server,你可以插个移动硬盘上去,然后使用任意支持 webdav 协议的客户端去操作这块存储空间,LuCI 也能访问了,很好。

然而偏偏有些应用,他不支持 http,他强制让你使用 https,确实,够安全。但对于局域网来说,难免有些多次一举。我说的就是 FolderSync,这是个好应用,可以将本地文件夹与云端(OneDrive, DropBox, WebDAV 等)文件夹进行双向同步,详询酷安#foldersync 话题。但我确实恼他不支持 http 协议。

没办法,只好安排一下。由于我是局域网内使用,而 FolderSync 其实也开了个后门,他支持自签名证书。

那我们就给他安排一下,参考这篇 wiki,非常简单。

网页端访问

你是否想在浏览器中也能访问 WebDAV server?Lighttpd 自带一个简单的页面,可通过以下配置开启:

# Override the /dav/ folder configured in 30-webdav.conf
$HTTP["url"] =~ "^/dav($|/)" {
  # The root / is ovveriden by an alais in turris-root.conf so we must add another override
  alias.url = ( "/dav/" => "/srv/disk/dav/" )
  server.document-root := "/srv/disk/"
  auth.backend := "plain"
  auth.backend.plain.userfile := "/etc/lighttpd/webdav.shadow"
  auth.require := (""=>("method"=>"basic","realm"=>"webdav","require"=>"valid-user"))
  # (Optional) add a directory index to see files from a browser
  server.dir-listing := "enable"  # 这里
  dir-listing.encoding := "utf-8" # 和这里
  webdav.sqlite-db-name := "/etc/lighttpd/webdav_lock.db"
}

cf. https://gist.github.com/stokito/77c42f8aff2dade91621c1051f73e58c

加上上面两行配置,重启下 lighttpd,然后打开 192.168.15.1/dav 即可访问 webdav server:

这个界面只能读,不能写。

加载 webdav-js

OpenWrt 的 wiki 上提到一个方法可以让你在浏览器中访问 webdav server,是通过在根目录创建一个 index.html 文件实现的。这个文件里面引入了 webdav-js,作为一个精简的 web client,他支持对 webdav server 的写入操作(包括上传文件,创建文件夹,复制,移动,重命名等)。所以我们在根目录创建一个这样的文件,就可以看到如下界面:

原本这中方法有个问题,wiki 中也有提到:就是子文件夹中没有这个 index.html,因此访问子文件夹就无法展示了。但加了 lighttpd 本身的网页前端之后,每个子文件夹都是可访问的,我猜想应该是开启 dir-listing 选项之后,访问的时候会自动生成 index 文件。而因为 webdav-js 缓存的缘故,跳转到子文件夹,只有里面包含 index 文件,就可以进行访问,即便子文件夹中的 index 文件没有加载 webdav-js. 这样一来,就只需要在根目录添加一个加载了 webdav-js 的 index 文件,就能浏览任意子目录的效果。

Alist 文件共享服务

一路走来,我们在局域网搭建了一个 WebDAV server,实现了局域网内的文件共享。但是 WebDAV client 对于父母一辈的其实不太友好。而网页端也很简陋,如果他们想打开网页直接看视频什么的,还不够方便。

又忽然联想到家里还有一台旧笔记本躺在那里吃灰。于是翻将出来直接格式化,装了个 archlinux,不过没装图形界面。由于路由器的性能毕竟吃紧,把移动硬盘接在旧电脑上做文件服务器才是正理。我准备在这台机器上部署一个 Alist 服务器,archlinux 直接

1
yay -S alist

即可安装。也可以使用官方文档中的一键脚本进行安装,使用脚本安装之后的路径在:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
yychi@dell-inspiron /opt/alist> ls
alist*  data/

yychi@dell-inspiron /opt/alist> ifconfig
enp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.15.233  netmask 255.255.255.0  broadcast 192.168.15.255
        ...

# 运行 alist server
yychi@dell-inspiron /opt/alist> ./alist server
INFO[2023-03-15 13:38:01] reading config file: data/config.json        
INFO[2023-03-15 13:38:01] load config from env with prefix: ALIST_     
INFO[2023-03-15 13:38:01] init logrus...                               
INFO[2023-03-15 13:38:01] start server @ 0.0.0.0:5244                  
INFO[2023-03-15 13:38:01] success load storage: [/disk], driver: [Local] 
INFO[2023-03-15 13:38:01] success load storage: [/home/yychi], driver: [Local] 
INFO[2023-03-15 13:38:01] Aria2 not ready.                             
INFO[2023-03-15 13:38:01] qbittorrent not ready.                       
INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/来自分享], driver: [AliyundriveOpen] 
INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/电影], driver: [AliyundriveOpen] 
INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/动漫], driver: [AliyundriveOpen] 
INFO[2023-03-15 13:38:02] success load storage: [/Aliyunpan/电视剧], driver: [AliyundriveOpen] 

运行之后,就可以在浏览器输入 http://192.168.15.233:5244 打开 alist 界面了。

上面是我配置之后的样子2。更多关于 alist 的配置,网上多不胜数,我就不献丑了。

Alist 支持文件交互,就像云盘一样,可以上传/下载文件,在线播放音视频图片等,也可以用作 WebDAV server,更可以挂载阿里云盘,总之功能多多,值得一玩。

一点体验优化

上面说到,我将废弃的笔记本(dell-inspiron)用作文件服务器,它需要 7*24 小时开机,妥妥的就是一个正经的服务器。那万一停电了,路由器重启了,就是服务器 ip 变了怎么办?我还得去路由器上或者服务器本身上面去看看 ip,这显然很麻烦。所以必须给 dell-inspiron 一个固定的 ip. 使用 OpenWrt 很容易做到这一点。

甚至可以做个主机名映射,不用再记烦人的 ip.

this is figcaption

这样就可以通过域名来访问文件服务。

总结

至此,我们就搭建了一个家庭共享存储。只要连上指定局域网,就可以访问并操作共享存储,而且可以基于 Alist 在线观看影视,十分方便。不过还有个痛点就是这个局域网其实是楼下路由器的子网,如果连楼下 wifi,就无法访问了。本来可以通过配置静态路由实现,但楼下路由偏偏不支持配置,只能先搁置一下了。


  1. 说是 OpenWrt,其实是中文社区的 fork,X-Wrt. ↩︎

  2. 你可能看到了 https,其实也是我为了使用 FolderSync 给加的自签名证书,仅限局域网内部用用。 ↩︎