blog/self-hosted

目前用的自建 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";
    };
  };
};

certkey 是 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";
  };
};

密码文件使用 apacheHttpdhtpasswd 创建。

$: 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 了。

About Me