133 lines
5.1 KiB
Nix
133 lines
5.1 KiB
Nix
{ config, lib, utils, ... }:
|
|
let
|
|
fsOpts = import ./fs-opts.nix;
|
|
commonOptions = fsOpts.ftp ++ fsOpts.noauto;
|
|
mountpoint = "/mnt/.servo_ftp";
|
|
systemdName = utils.escapeSystemdPath mountpoint;
|
|
device = "curlftpfs#ftp://servo-hn:/";
|
|
fsType = "fuse3";
|
|
options = commonOptions ++ [
|
|
# systemd (or maybe fuse?) swallows stderr of mount units with no obvious fix.
|
|
# instead, use this flag to log the mount output to disk
|
|
"stderr_path=/var/log/curlftpfs/servo-hn.stderr"
|
|
];
|
|
|
|
remoteServo = subdir: {
|
|
# sane.fs."/mnt/servo/${subdir}".mount.bind = "/mnt/.servo_ftp/${subdir}";
|
|
systemd.mounts = [{
|
|
where = "/mnt/servo/${subdir}";
|
|
what = "/mnt/.servo_ftp/${subdir}";
|
|
options = "bind,nofail";
|
|
type = "auto";
|
|
|
|
after = [ "${systemdName}.mount" ];
|
|
upheldBy = [ "${systemdName}.mount" ]; #< start this mount whenever the underlying becomes available
|
|
bindsTo = [ "${systemdName}.mount" ]; #< stop this mount whenever the underlying disappears
|
|
}];
|
|
};
|
|
in
|
|
lib.mkMerge [
|
|
{
|
|
sane.programs.curlftpfs.enableFor.system = true;
|
|
system.fsPackages = [
|
|
config.sane.programs.curlftpfs.package
|
|
];
|
|
|
|
sane.fs."/var/log/curlftpfs".dir.acl.mode = "0777";
|
|
|
|
fileSystems."/mnt/.servo_ftp" = {
|
|
inherit device fsType options;
|
|
noCheck = true;
|
|
};
|
|
systemd.mounts = [{
|
|
where = mountpoint;
|
|
what = device;
|
|
type = fsType;
|
|
options = lib.concatStringsSep "," options;
|
|
wantedBy = [ "default.target" ];
|
|
after = [ "network-online.target" ];
|
|
requires = [ "network-online.target" ];
|
|
|
|
#VVV patch so that when the mount fails, we start a timer to remount it.
|
|
# and for a disconnection after a good mount (onSuccess), restart the timer to be more aggressive
|
|
unitConfig.OnFailure = [ "${systemdName}.timer" ];
|
|
unitConfig.OnSuccess = [ "${systemdName}-restart-timer.target" ];
|
|
|
|
mountConfig.TimeoutSec = "10s";
|
|
mountConfig.ExecSearchPath = [ "/run/current-system/sw/bin" ];
|
|
mountConfig.User = "colin";
|
|
mountConfig.AmbientCapabilities = "CAP_SETPCAP CAP_SYS_ADMIN";
|
|
# hardening (systemd-analyze security mnt-servo-playground.mount)
|
|
mountConfig.CapabilityBoundingSet = "CAP_SETPCAP CAP_SYS_ADMIN";
|
|
mountConfig.LockPersonality = true;
|
|
mountConfig.MemoryDenyWriteExecute = true;
|
|
mountConfig.NoNewPrivileges = true;
|
|
mountConfig.ProtectClock = true;
|
|
mountConfig.ProtectHostname = true;
|
|
mountConfig.RemoveIPC = true;
|
|
mountConfig.RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
|
|
#VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/...
|
|
# see `systemd-analyze filesystems` for a full list
|
|
mountConfig.RestrictFileSystems = "@common-block @basic-api fuse";
|
|
mountConfig.RestrictRealtime = true;
|
|
mountConfig.RestrictSUIDSGID = true;
|
|
mountConfig.SystemCallArchitectures = "native";
|
|
mountConfig.SystemCallFilter = [
|
|
"@system-service"
|
|
"@mount"
|
|
"~@chown"
|
|
"~@cpu-emulation"
|
|
"~@keyring"
|
|
# could remove almost all io calls, however one has to keep `open`, and `write`, to communicate with the fuse device.
|
|
# so that's pretty useless as a way to prevent write access
|
|
];
|
|
mountConfig.IPAddressDeny = "any";
|
|
mountConfig.IPAddressAllow = "10.0.10.5";
|
|
mountConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom}
|
|
mountConfig.DeviceAllow = "/dev/fuse";
|
|
# mountConfig.RestrictNamespaces = true;
|
|
}];
|
|
|
|
systemd.targets."${systemdName}-restart-timer" = {
|
|
# hack unit which, when started, stops the timer (if running), and then starts it again.
|
|
after = [ "${systemdName}.timer" ];
|
|
conflicts = [ "${systemdName}.timer" ];
|
|
upholds = [ "${systemdName}.timer" ];
|
|
unitConfig.StopWhenUnneeded = true;
|
|
};
|
|
systemd.timers."${systemdName}" = {
|
|
timerConfig.Unit = "${systemdName}.mount";
|
|
timerConfig.AccuracySec = "2s";
|
|
timerConfig.OnActiveSec = [
|
|
# try to remount at these timestamps, backing off gradually
|
|
# there seems to be an implicit mount attempt at t=0.
|
|
"10s"
|
|
"30s"
|
|
"60s"
|
|
"120s"
|
|
];
|
|
# cap the backoff to a fixed interval.
|
|
timerConfig.OnUnitActiveSec = [ "120s" ];
|
|
};
|
|
}
|
|
|
|
# this granularity of servo media mounts is necessary to support sandboxing. consider:
|
|
# 1. servo offline
|
|
# 2. launch a long-running app
|
|
# 3. servo comes online
|
|
# in order for the servo mount to be propagated into the app's namespace, we need to bind
|
|
# the root mountpoint into the app namespace. if we wish to only grant the app selective access
|
|
# to servo, we must create *multiple* mountpoints: /mnt/servo/FOO directories which always exist,
|
|
# and are individually bound to /mnt/.servo_ftp/FOO as the latter becomes available.
|
|
(remoteServo "media/archive")
|
|
(remoteServo "media/Books")
|
|
(remoteServo "media/collections")
|
|
# (remoteServo "media/datasets")
|
|
(remoteServo "media/games")
|
|
(remoteServo "media/Music")
|
|
(remoteServo "media/Pictures/macros")
|
|
(remoteServo "media/torrents")
|
|
(remoteServo "media/Videos")
|
|
(remoteServo "playground")
|
|
]
|