目前用的自建 NAS 方案
Why self host? 好用的公用云空间太小了!空间大的公用云不好用!
Why not NextCloud?
- PHP 写的,占用资源大。会将程序复制进数据盘,降低程序执行效率。
- 功能虽多但都很残缺,甚至做了个服务器面板进去。
- 无法方便地挂载,davfs2 效率很低并且会在本地长期缓存文件;官网说支持 Samba、NFS 等但并没有。
- 同步慢。
- 安装过程并不优雅,即使用了 Docker 还是很脏。
正好我前段时间将我的很多机器都换成了 NixOS,搭建云服务比较方便。下文我会展示目前用的一些 Nix 配置。
下文出现的
config.age.secrets.*.path
为用 agenix 加密的密码文件解密后所在的路径。我会附上密码文件的创建方式。
我将自己的自建 NAS 需求分为了三部分:
- 网盘:Samba、SSHFS、miniserve
- 同步:Syncthing、Radicale
- 备份:rsync
Samba
Samba 是 Windows 文件共享协议 SMB 的 Linux 实现。适合内网明文传输,速率很高。和 NFS 相比可以设置密码,不容易卡死内核,缺点是没有 UNIX 文件 mode。
# Also need to run this
#: smbpasswd -a chuang
services.samba = {
enable = true;
shares.nas_files = {
path = "/media/files";
"guest ok" = false;
"read only" = false;
};
};
客户端
fileSystems."/media/nas" = {
device = "//seaberry.lan/nas_files";
fsType = "cifs";
options = [
# Mount only on access
"x-systemd.automount,_netdev,noauto"
# This line prevents hanging on network split
"x-systemd.idle-timeout=60,x-systemd.device-timeout=5s,x-systemd.mount-timeout=5s"
"uid=${toString config.users.users.chuang.uid}"
"gid=${toString config.users.users.chuang.group}"
"credentials=${config.age.secrets.seaberry.path}"
];
};
密码文件:
username=chuang
password=correcthorsebatterystaple
Android 可以使用 Google 的 Android Samba Client,可以挂载到 Documents UI,使用系统的文件管理器访问。
SSHFS
SSHFS 是一个使用 SFTP 的 FUSE 文件系统,可以直接挂载默认配置的 SSH 远端。SSH 本身是安全的信道,适合在公网上使用。
$: sshfs chuang@cranberry.link.melty.land:/media/files /media/nas
自动挂载:
environment.systemPackages = with pkgs; [ sshfs ];
fileSystems."/media/cranberry" = {
device = "chuang@cranberry.link.melty.land:/media/files";
fsType = "fuse.sshfs";
options = [ "IdentityFile=/home/chuang/.ssh/id_ed25519" ];
};
programs.ssh.knownHosts.cranberry = {
hostNames = [ "cranberry.link.melty.land" ];
publicKey = (import ../../modules/keys.nix).cranberry;
};
Android 客户端可以用支持 SFTP 的 Material Files。
miniserve
有时候手上没有客户端,需要临时访问一下网盘。比如去机房做实验时想保存一份到 NAS 中,这时一个网页端网盘就十分好用了。目前我使用的是 miniserve,它使用起来十分简单,可以看作是 python -m http.server
on steroids。
systemd.services.miniserve = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
script = ''
${pkgs.miniserve}/bin/miniserve -a "$(< ${config.age.secrets.miniserve.path})" -i 127.0.0.1 -i ::1 -p 15428 -u /media/files
'';
serviceConfig = {
User = "chuang";
Group = config.users.users.chuang.group;
IPAccounting = "yes";
IPAddressAllow = "localhost";
IPAddressDeny = "any";
DynamicUser = "yes";
PrivateTmp = "yes";
PrivateUsers = "yes";
PrivateDevices = "yes";
NoNewPrivileges = "true";
ProtectSystem = "strict";
ProtectHome = "yes";
ProtectClock = "yes";
ProtectControlGroups = "yes";
ProtectKernelLogs = "yes";
ProtectKernelModules = "yes";
ProtectKernelTunables = "yes";
ProtectProc = "invisible";
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_DAC_READ_SEARCH" ];
};
};
services.nginx.enable = true;
services.nginx.virtualHosts."drive.melty.land" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:15428/";
};
};
密码文件创建方式:
$: hash=$(echo -n correcthorsebatterystaple | sha256sum | cut -d' ' -f1)
$: echo "chuang:sha256:$hash" > miniserve
Syncthing
Syncthing 是一个 P2P 文件同步程序,它不区分服务端和客户端。可以在你的任何设备上指定同步的对象,组成任意的拓补。并且能自动发现指定的设备,能直接访问的会直接传输,不能直接访问的会通过社区提供的 relay server 传输,即使设备都在 NAT 后面也能同步。整个过程通过 TLS 加密。它的同步速度很快,在一台设备上的更改只需要十多秒就可以同步到其他设备上。
Syncthing 自带 WebUI,可直接在 http://localhost:8384/ 配置。不过 NixOS 提供了对应的 Nix module,通过它可以在同步设备和文件夹较多时方便地管理。
服务端
services.syncthing = {
enable = true;
openDefaultPorts = true;
user = "chuang";
group = config.users.users.chuang.group;
# id = "YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY";
cert = "${./syncthing.pem}";
key = config.age.secrets.syncthing.path;
devices = {
hawthorn.id = "XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX";
};
folders."hawthorn/Pictures" = {
id = "aaaaa-aaaaa";
devices = [ "hawthorn" ];
path = "/media/files/gallery/hawthorn/Pictures";
versioning = {
type = "trashcan";
params.cleanoutDays = "365";
};
};
};
cert
和 key
是 Syncthing 自签的 TLS 证书和密钥 X.509 文件,证书的 hash 被称作这台机器的 id,devices 下填的是其他设备的 id。可以先不填 cert 和 key,Syncthing 会生成一个,把生成的证书和密钥复制过来,然后记下 id
填在其他设备的配置中就可以了。一个文件夹的 folders.<name>.id
应该在其同步的所有设备上相同。versioning
指定了当对方更改时,本地保留几个版本。trashcan
是保留所有版本,定时清理,这里清理时间是一年。
客户端
services.syncthing = {
enable = true;
openDefaultPorts = true;
user = "chuang";
group = config.users.users.chuang.group;
# id = "XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX-XXXXXXX";
cert = "${./syncthing.pem}";
key = config.age.secrets.syncthing.path;
devices = {
seaberry.id = "YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY-YYYYYYY";
};
folders."Pictures" = {
id = "aaaaa-aaaaa";
devices = [ "seaberry" ];
path = "/home/chuang/Pictures";
};
};
Syncthing 有 Android 版本。
Radicale
前面虽然吐槽了 WebDAV 传文件效率低下,但 WebDAV 的扩展 CalDAV 和 CardDAV,是目前使用最广泛的日历和联系人同步协议。Google 帐号、Apple ID 内部都采用这套协议。Radicale 是 CalDAV 和 CardDAV 的一个开源服务端实现。
services.nginx.virtualHosts."dav.melty.land" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:5232/";
extraConfig = ''
proxy_set_header X-Script-Name /;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass_header Authorization;
'';
};
};
services.radicale = {
enable = true;
settings = {
server.hosts = [ "127.0.0.1:5232" "[::1]:5232" ];
auth.type = "htpasswd";
auth.htpasswd_filename = config.age.secrets.radicale.path;
storage.filesystem_folder = "/var/lib/radicale/collections";
};
};
密码文件使用 apacheHttpd
的 htpasswd
创建。
$: htpasswd -c radicale chuang
用浏览器访问同步地址,会跳转到 WebGUI,在这里可以导入 .vcf
联系人列表和 .ics
日历。
这个 WebGUI 只支持单表 .ics
文件,如果你的日历有多个分类那么应当每个分类分别导出一个文件。
客户端
Kalendar:KDE 的一个新日历应用。界面很美观,可以与 Google Calendar 媲美。内建了 CalDAV 同步支持。
Android CalDAV/CardDAV 客户端:DAVx5。行为类似于 Google 帐号的同步功能,会在后台定时同步。
Android 的日历和联系人的逻辑是不同帐号的是不同的 profile,分开储存。如果要将原来的离线或 Google Account 上的日历和联系人同步到 Radicale,需要先导出、然后导入 Radicale、最后删掉原来的日历。如果原来用的是 Google Calendar 可以在网页版导出。联系人可以用这个应用导出。
设置好后就可以看到系统的日历里出现了 Radicale 的帐号,添加联系人时可以选择 DAVx5 了。