2023-06-15 10:08:54 +00:00
# docs
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
2024-02-23 02:29:12 +00:00
# - fuse options: `man mount.fuse`
2023-06-15 10:08:54 +00:00
2024-04-01 07:31:25 +00:00
{ config , lib , pkgs , sane-lib , utils , . . . }:
2022-06-02 10:40:14 +00:00
2023-08-20 06:20:04 +00:00
let
fsOpts = rec {
common = [
" _ n e t d e v "
" n o a t i m e "
2024-02-23 02:29:12 +00:00
# user: allow any user with access to the device to mount the fs.
# note that this requires a suid `mount` binary; see: <https://zameermanji.com/blog/2022/8/5/using-fuse-without-root-on-linux/>
" u s e r "
2023-08-20 06:20:04 +00:00
" x - s y s t e m d . r e q u i r e s = n e t w o r k - o n l i n e . t a r g e t "
" x - s y s t e m d . a f t e r = n e t w o r k - o n l i n e . t a r g e t "
" x - s y s t e m d . m o u n t - t i m e o u t = 1 0 s " # how long to wait for mount **and** how long to wait for unmount
] ;
2024-02-24 16:04:04 +00:00
# x-systemd.automount: mount the fs automatically *on first access*.
# creates a `path-to-mount.automount` systemd unit.
automount = [ " x - s y s t e m d . a u t o m o u n t " ] ;
# noauto: don't mount as part of remote-fs.target.
# N.B.: `remote-fs.target` is a dependency of multi-user.target, itself of graphical.target.
# hence, omitting `noauto` can slow down boots.
noauto = [ " n o a u t o " ] ;
2024-03-28 18:47:22 +00:00
# lazyMount: defer mounting until first access from userspace.
# see: `man systemd.automount`, `man automount`, `man autofs`
2024-02-24 16:04:04 +00:00
lazyMount = noauto ++ automount ;
2023-08-20 06:20:04 +00:00
wg = [
" x - s y s t e m d . r e q u i r e s = w i r e g u a r d - w g - h o m e . s e r v i c e "
" x - s y s t e m d . a f t e r = w i r e g u a r d - w g - h o m e . s e r v i c e "
] ;
2023-06-15 09:25:48 +00:00
2024-03-28 23:09:02 +00:00
fuse = [
2024-02-24 16:04:04 +00:00
" a l l o w _ o t h e r " # 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.
# "allow_root"
# default_permissions: enforce local permissions check. CRUCIAL if using `allow_other`.
# w/o this, permissions mode of sshfs is like:
# - sshfs runs all remote commands as the remote user.
# - if a local user has local permissions to the sshfs mount, then their file ops are sent blindly across the tunnel.
# - `allow_other` allows *any* local user to access the mount, and hence any local user can now freely become the remote mapped user.
# 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.
2023-08-20 06:20:04 +00:00
" d e f a u l t _ p e r m i s s i o n s "
] ;
2024-03-28 23:09:02 +00:00
fuseColin = fuse ++ [
" u i d = 1 0 0 0 "
" g i d = 1 0 0 "
] ;
ssh = common ++ fuse ++ [
" i d e n t i t y f i l e = / h o m e / c o l i n / . s s h / i d _ e d 2 5 5 1 9 "
# 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?
" i d m a p = u s e r "
] ;
sshColin = ssh ++ fuseColin ++ [
2024-02-24 16:04:04 +00:00
# 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`
" f o l l o w _ s y m l i n k s "
# 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.
2023-08-20 06:20:04 +00:00
" t r a n s f o r m _ s y m l i n k s "
] ;
2024-02-25 05:12:44 +00:00
# sshRoot = ssh ++ [
# # we don't transform_symlinks because that breaks the validity of remote /nix stores
# "sftp_server=/run/wrappers/bin/sudo\\040/run/current-system/sw/libexec/sftp-server"
# ];
2023-08-20 06:20:04 +00:00
# in the event of hunt NFS mounts, consider:
# - <https://unix.stackexchange.com/questions/31979/stop-broken-nfs-mounts-from-locking-a-directory>
2023-06-15 09:25:48 +00:00
2023-08-20 06:20:04 +00:00
# NFS options: <https://linux.die.net/man/5/nfs>
# actimeo=n = how long (in seconds) to cache file/dir attributes (default: 3-60s)
# bg = retry failed mounts in the background
# retry=n = for how many minutes `mount` will retry NFS mount operation
2024-03-28 18:47:22 +00:00
# intr = allow Ctrl+C to abort I/O (it will error with `EINTR`)
2023-08-20 06:20:04 +00:00
# soft = on "major timeout", report I/O error to userspace
2024-03-28 18:47:22 +00:00
# softreval = on "major timeout", service the request using known-stale cache results instead of erroring -- if such cache data exists
2023-08-20 06:20:04 +00:00
# retrans=n = how many times to retry a NFS request before giving userspace a "server not responding" error (default: 3)
# timeo=n = number of *deciseconds* to wait for a response before retrying it (default: 600)
# note: client uses a linear backup, so the second request will have double this timeout, then triple, etc.
2024-03-28 18:47:22 +00:00
# proto=udp = encapsulate protocol ops inside UDP packets instead of a TCP session.
# requires `nfsvers=3` and a kernel compiled with `NFS_DISABLE_UDP_SUPPORT=n`.
# UDP might be preferable to TCP because the latter is liable to hang for ~100s (kernel TCP timeout) after a link drop.
# however, even UDP has issues with `umount` hanging.
#
# N.B.: don't change these without first testing the behavior of sandboxed apps on a flaky network.
2023-08-20 06:20:04 +00:00
nfs = common ++ [
2024-03-28 18:47:22 +00:00
# "actimeo=5"
# "bg"
" r e t r a n s = 1 "
2023-08-20 06:20:04 +00:00
" r e t r y = 0 "
2024-03-28 18:47:22 +00:00
# "intr"
2023-08-20 06:20:04 +00:00
" s o f t "
2024-03-28 18:47:22 +00:00
" s o f t r e v a l "
" t i m e o = 3 0 "
2023-08-20 06:20:04 +00:00
" n o f a i l " # don't fail remote-fs.target when this mount fails (not an option for sshfs else would be common)
2024-03-28 18:47:22 +00:00
# "proto=udp" # default kernel config doesn't support NFS over UDP: <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1964093> (see comment 11).
# "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
2023-08-20 06:20:04 +00:00
] ;
2024-03-28 23:09:02 +00:00
# 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"
" u s e r = c o l i n : i p a u t h "
2024-05-13 07:52:43 +00:00
# connect_timeout=10: casting shows to T.V. fails partway through about half the time
" c o n n e c t _ t i m e o u t = 2 0 "
2024-03-28 23:09:02 +00:00
] ;
2023-08-20 06:20:04 +00:00
} ;
remoteHome = host : {
2024-04-01 02:10:12 +00:00
sane . programs . sshfs-fuse . enableFor . system = true ;
2024-02-06 05:48:11 +00:00
fileSystems . " / m n t / ${ host } / h o m e " = {
2023-08-20 06:20:04 +00:00
device = " c o l i n @ ${ host } : / h o m e / c o l i n " ;
fsType = " f u s e . s s h f s " ;
2024-02-24 16:04:04 +00:00
options = fsOpts . sshColin ++ fsOpts . lazyMount ;
2023-08-20 06:20:04 +00:00
noCheck = true ;
} ;
2024-02-23 02:29:12 +00:00
sane . fs . " / m n t / ${ host } / h o m e " = sane-lib . fs . wanted {
dir . acl . user = " c o l i n " ;
dir . acl . group = " u s e r s " ;
dir . acl . mode = " 0 7 0 0 " ;
} ;
2023-08-20 06:20:04 +00:00
} ;
2024-04-01 02:10:12 +00:00
remoteServo = subdir : {
sane . programs . curlftpfs . enableFor . system = true ;
2024-04-01 06:00:17 +00:00
sane . fs . " / m n t / s e r v o / ${ subdir } " = sane-lib . fs . wanted {
dir . acl . user = " c o l i n " ;
dir . acl . group = " u s e r s " ;
dir . acl . mode = " 0 7 5 0 " ;
} ;
2024-04-01 02:10:12 +00:00
fileSystems . " / m n t / s e r v o / ${ subdir } " = {
2024-04-01 07:31:25 +00:00
device = " f t p : / / s e r v o - h n : / ${ subdir } " ;
2024-04-01 02:10:12 +00:00
noCheck = true ;
fsType = " f u s e . c u r l f t p f s " ;
2024-04-01 06:00:17 +00:00
options = fsOpts . ftp ++ fsOpts . noauto ++ fsOpts . wg ;
# fsType = "nfs";
# options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
2024-04-01 02:10:12 +00:00
} ;
2024-04-01 07:31:25 +00:00
systemd . services . " a u t o m o u n t - s e r v o - ${ utils . escapeSystemdPath subdir } " = let
2024-04-01 06:00:17 +00:00
fs = config . fileSystems . " / m n t / s e r v o / ${ subdir } " ;
in {
# this is a *flaky* network mount, especially on moby.
# if done as a normal autofs mount, access will eternally block when network is dropped.
# notably, this would block *any* sandboxed app which allows media access, whether they actually try to use that media or not.
# a practical solution is this: mount as a service -- instead of autofs -- and unmount on timeout error, in a restart loop.
# until the ftp handshake succeeds, nothing is actually mounted to the vfs, so this doesn't slow down any I/O when network is down.
description = " a u t o m o u n t / m n t / s e r v o / ${ subdir } i n a f a u l t - t o l e r a n t a n d n o n - b l o c k i n g m a n n e r " ;
after = [ " n e t w o r k - o n l i n e . t a r g e t " ] ;
requires = [ " n e t w o r k - o n l i n e . t a r g e t " ] ;
wantedBy = [ " d e f a u l t . t a r g e t " ] ;
serviceConfig . Type = " s i m p l e " ;
serviceConfig . ExecStart = lib . escapeShellArgs [
" / u s r / b i n / e n v "
" P A T H = / r u n / c u r r e n t - s y s t e m / s w / b i n "
" m o u n t . ${ fs . fsType } "
" - f " # foreground (i.e. don't daemonize)
" - s " # single-threaded (TODO: it's probably ok to disable this?)
" - o "
( lib . concatStringsSep " , " ( lib . filter ( o : ! lib . hasPrefix " x - s y s t e m d . " o ) fs . options ) )
2024-04-01 07:31:25 +00:00
fs . device
2024-04-01 06:00:17 +00:00
" / m n t / s e r v o / ${ subdir } "
] ;
# not sure if this configures a linear, or exponential backoff.
# but the first restart will be after `RestartSec`, and the n'th restart (n = RestartSteps) will be RestartMaxDelaySec after the n-1'th exit.
serviceConfig . Restart = " a l w a y s " ;
serviceConfig . RestartSec = " 1 0 s " ;
serviceConfig . RestartMaxDelaySec = " 1 2 0 s " ;
serviceConfig . RestartSteps = " 5 " ;
2024-04-01 02:10:12 +00:00
} ;
} ;
2022-06-10 07:38:02 +00:00
in
2023-08-20 06:20:04 +00:00
lib . mkMerge [
{
# some services which use private directories error if the parent (/var/lib/private) isn't 700.
sane . fs . " / v a r / l i b / p r i v a t e " . dir . acl . mode = " 0 7 0 0 " ;
2023-07-13 23:37:30 +00:00
2023-08-20 06:20:04 +00:00
# in-memory compressed RAM
# defaults to compressing at most 50% size of RAM
# claimed compression ratio is about 2:1
# - but on moby w/ zstd default i see 4-7:1 (ratio lowers as it fills)
# note that idle overhead is about 0.05% of capacity (e.g. 2B per 4kB page)
# docs: <https://www.kernel.org/doc/Documentation/blockdev/zram.txt>
#
# to query effectiveness:
# `cat /sys/block/zram0/mm_stat`. whitespace separated fields:
# - *orig_data_size* (bytes)
# - *compr_data_size* (bytes)
# - mem_used_total (bytes)
# - mem_limit (bytes)
# - mem_used_max (bytes)
# - *same_pages* (pages which are e.g. all zeros (consumes no additional mem))
# - *pages_compacted* (pages which have been freed thanks to compression)
# - huge_pages (incompressible)
#
# see also:
# - `man zramctl`
zramSwap . enable = true ;
# how much ram can be swapped into the zram device.
# this shouldn't be higher than the observed compression ratio.
# the default is 50% (why?)
# 100% should be "guaranteed" safe so long as the data is even *slightly* compressible.
# 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 ;
2023-06-15 02:14:42 +00:00
2024-02-25 05:12:44 +00:00
# environment.pathsToLink = [
# # needed to achieve superuser access for user-mounted filesystems (see sshRoot above)
# # we can only link whole directories here, even though we're only interested in pkgs.openssh
# "/libexec"
# ];
2023-08-14 08:10:17 +00:00
2024-02-23 02:29:12 +00:00
programs . fuse . userAllowOther = true ; #< necessary for `allow_other` or `allow_root` options.
2023-08-20 06:20:04 +00:00
}
2023-06-15 10:08:54 +00:00
2024-06-11 00:36:18 +00:00
( remoteHome " c r a p p y " )
2023-08-20 06:20:04 +00:00
( remoteHome " d e s k o " )
2023-08-20 06:26:20 +00:00
( remoteHome " l a p p y " )
2023-08-20 06:20:04 +00:00
( remoteHome " m o b y " )
2024-04-01 07:31:25 +00:00
# this granularity of servo media mounts is necessary to support sandboxing:
# for flaky mounts, we can only bind the mountpoint itself into the sandbox,
# so it's either this or unconditionally bind all of media/.
( remoteServo " m e d i a / a r c h i v e " )
( remoteServo " m e d i a / B o o k s " )
( remoteServo " m e d i a / c o l l e c t i o n s " )
# (remoteServo "media/datasets")
( remoteServo " m e d i a / g a m e s " )
( remoteServo " m e d i a / M u s i c " )
( remoteServo " m e d i a / P i c t u r e s / m a c r o s " )
2024-04-23 01:25:57 +00:00
( remoteServo " m e d i a / t o r r e n t s " )
2024-04-01 07:31:25 +00:00
( remoteServo " m e d i a / V i d e o s " )
2024-04-01 02:10:12 +00:00
( remoteServo " p l a y g r o u n d " )
2023-08-20 06:20:04 +00:00
]
2022-06-02 10:40:14 +00:00