{ 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")
]
