diff --git a/hosts/common/fs.nix b/hosts/common/fs.nix index 2a7ecb9c..5c474de7 100644 --- a/hosts/common/fs.nix +++ b/hosts/common/fs.nix @@ -5,6 +5,31 @@ { lib, pkgs, sane-lib, ... }: let + curlftpfs = pkgs.curlftpfs.overrideAttrs (upstream: { + # apply a timeout not just to the connect operation, but to every operation. + # without this, operations will hang for 2*, e.g. 4m20s. + # better would be to use CURLOPT_LOW_SPEED_TIME/CURLOPT_LOW_SPEED_LIMIT, + # but those options don't seem to apply. + postPatch = (upstream.postPatch or "") + '' + substituteInPlace ftpfs.c --replace-fail \ + 'curl_easy_setopt_or_die(easy, CURLOPT_CONNECTTIMEOUT, ftpfs.connect_timeout);' \ + 'curl_easy_setopt_or_die(easy, CURLOPT_CONNECTTIMEOUT, ftpfs.connect_timeout); curl_easy_setopt_or_die(easy, CURLOPT_TIMEOUT, ftpfs.connect_timeout);' + '' + # fuse mount programs need to ignore certain "meta" keys that could be in /etc/fstab. + # see: + + '' + substituteInPlace ftpfs.c --replace-fail \ + 'FUSE_OPT_END' \ + '/* These may come in from /etc/fstab - we just ignore them */ FUSE_OPT_KEY("auto", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("noauto", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("user", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("nouser", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("users", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("_netdev", FUSE_OPT_KEY_DISCARD), FUSE_OPT_END' + ''; + # `mount` clears PATH before calling the mount helper (see util-linux/lib/env.c), + # so the traditional /etc/fstab approach of fstype=fuse and device = curlftpfs#URI doesn't work. + # instead, install a `mount.curlftpfs` mount helper. this is what programs like `gocryptfs` do. + postInstall = (upstream.postInstall or "") + '' + ln -s curlftpfs $out/bin/mount.fuse.curlftpfs + ln -s curlftpfs $out/bin/mount.curlftpfs + ''; + }); fsOpts = rec { common = [ "_netdev" @@ -31,8 +56,7 @@ let "x-systemd.after=wireguard-wg-home.service" ]; - ssh = common ++ [ - "identityfile=/home/colin/.ssh/id_ed25519" + fuse = [ "allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root) # allow_root: allow root to access files on this fs (if mounted by non-root, else it can always access them). # N.B.: if both allow_root and allow_other are specified, then only allow_root takes effect. @@ -45,7 +69,18 @@ let # with default_permissions, sshfs doesn't tunnel file ops from users until checking that said user could perform said op on an equivalent local fs. "default_permissions" ]; - sshColin = ssh ++ [ + fuseColin = fuse ++ [ + "uid=1000" + "gid=100" + ]; + + ssh = common ++ fuse ++ [ + "identityfile=/home/colin/.ssh/id_ed25519" + # i *think* idmap=user means that `colin` on `localhost` and `colin` on the remote are actually treated as the same user, even if their uid/gid differs? + # i.e., local colin's id is translated to/from remote colin's id on every operation? + "idmap=user" + ]; + sshColin = ssh ++ fuseColin ++ [ # follow_symlinks: remote files which are symlinks are presented to the local system as ordinary files (as the target of the symlink). # if the symlink target does not exist, the presentation is unspecified. # symlinks which point outside the mount ARE followed. so this is more capable than `transform_symlinks` @@ -53,9 +88,6 @@ let # symlinks on the remote fs which are absolute paths are presented to the local system as relative symlinks pointing to the expected data on the remote fs. # only symlinks which would point inside the mountpoint are translated. "transform_symlinks" - "idmap=user" - "uid=1000" - "gid=100" ]; # sshRoot = ssh ++ [ # # we don't transform_symlinks because that breaks the validity of remote /nix stores @@ -94,6 +126,14 @@ let # "nfsvers=3" # NFSv4+ doesn't support UDP at *all*. it's ok to omit nfsvers -- server + client will negotiate v3 based on udp requirement. but omitting causes confusing mount errors when the server is *offline*, because the client defaults to v4 and thinks the udp option is a config error. # "x-systemd.idle-timeout=10" # auto-unmount after this much inactivity ]; + + # manually perform a ftp mount via e.g. + # curlftpfs -o ftpfs_debug=2,user=anonymous:anonymous,connect_timeout=10 -f -s ftp://servo-hn /mnt/my-ftp + ftp = common ++ fuseColin ++ [ + # "ftpfs_debug=2" + "user=colin:ipauth" + "connect_timeout=10" + ]; }; remoteHome = host: { fileSystems."/mnt/${host}/home" = { @@ -142,29 +182,35 @@ lib.mkMerge [ # but it decreases working memory under the heaviest of loads by however much space the compressed memory occupies (e.g. 50% if 2:1; 25% if 4:1) zramSwap.memoryPercent = 100; - # fileSystems."/mnt/servo-nfs" = { - # device = "servo-hn:/"; + # fileSystems."/mnt/servo/media" = { + # device = "servo-hn:/media"; # noCheck = true; # fsType = "nfs"; - # options = fsOpts.nfs ++ fsOpts.automount ++ fsOpts.wg; + # options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg; + # }; + # fileSystems."/mnt/servo/playground" = { + # device = "servo-hn:/playground"; + # noCheck = true; + # fsType = "nfs"; + # options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg; # }; fileSystems."/mnt/servo/media" = { device = "servo-hn:/media"; noCheck = true; - fsType = "nfs"; - options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg; + fsType = "fuse.curlftpfs"; + options = fsOpts.ftp ++ fsOpts.lazyMount ++ fsOpts.wg; + }; + fileSystems."/mnt/servo/playground" = { + device = "servo-hn:/playground"; + noCheck = true; + fsType = "fuse.curlftpfs"; + options = fsOpts.ftp ++ fsOpts.lazyMount ++ fsOpts.wg; }; sane.fs."/mnt/servo/media" = sane-lib.fs.wanted { dir.acl.user = "colin"; dir.acl.group = "users"; dir.acl.mode = "0750"; }; - fileSystems."/mnt/servo/playground" = { - device = "servo-hn:/playground"; - noCheck = true; - fsType = "nfs"; - options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg; - }; sane.fs."/mnt/servo/playground" = sane-lib.fs.wanted { dir.acl.user = "colin"; dir.acl.group = "users"; @@ -180,6 +226,7 @@ lib.mkMerge [ programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options. environment.systemPackages = [ pkgs.sshfs-fuse + curlftpfs ]; }