<section xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="sec-sshfs-file-systems">
  <title>SSHFS File Systems</title>
  <para>
    <link xlink:href="https://github.com/libfuse/sshfs">SSHFS</link> is
    a
    <link xlink:href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</link>
    filesystem that allows easy access to directories on a remote
    machine using the SSH File Transfer Protocol (SFTP). It means that
    if you have SSH access to a machine, no additional setup is needed
    to mount a directory.
  </para>
  <section xml:id="sec-sshfs-interactive">
    <title>Interactive mounting</title>
    <para>
      In NixOS, SSHFS is packaged as <package>sshfs</package>. Once
      installed, mounting a directory interactively is simple as
      running:
    </para>
    <programlisting>
$ sshfs my-user@example.com:/my-dir /mnt/my-dir
</programlisting>
    <para>
      Like any other FUSE file system, the directory is unmounted using:
    </para>
    <programlisting>
$ fusermount -u /mnt/my-dir
</programlisting>
  </section>
  <section xml:id="sec-sshfs-non-interactive">
    <title>Non-interactive mounting</title>
    <para>
      Mounting non-interactively requires some precautions because
      <literal>sshfs</literal> will run at boot and under a different
      user (root). For obvious reason, you can’t input a password, so
      public key authentication using an unencrypted key is needed. To
      create a new key without a passphrase you can do:
    </para>
    <programlisting>
$ ssh-keygen -t ed25519 -P '' -f example-key
Generating public/private ed25519 key pair.
Your identification has been saved in test-key
Your public key has been saved in test-key.pub
The key fingerprint is:
SHA256:yjxl3UbTn31fLWeyLYTAKYJPRmzknjQZoyG8gSNEoIE my-user@workstation
</programlisting>
    <para>
      To keep the key safe, change the ownership to
      <literal>root:root</literal> and make sure the permissions are
      <literal>600</literal>: OpenSSH normally refuses to use the key if
      it’s not well-protected.
    </para>
    <para>
      The file system can be configured in NixOS via the usual
      <link xlink:href="options.html#opt-fileSystems">fileSystems</link>
      option. Here’s a typical setup:
    </para>
    <programlisting language="bash">
{
  system.fsPackages = [ pkgs.sshfs ];

  fileSystems.&quot;/mnt/my-dir&quot; = {
    device = &quot;my-user@example.com:/my-dir/&quot;;
    fsType = &quot;sshfs&quot;;
    options =
      [ # Filesystem options
        &quot;allow_other&quot;          # for non-root access
        &quot;_netdev&quot;              # this is a network fs
        &quot;x-systemd.automount&quot;  # mount on demand

        # SSH options
        &quot;reconnect&quot;              # handle connection drops
        &quot;ServerAliveInterval=15&quot; # keep connections alive
        &quot;IdentityFile=/var/secrets/example-key&quot;
      ];
  };
}
</programlisting>
    <para>
      More options from <literal>ssh_config(5)</literal> can be given as
      well, for example you can change the default SSH port or specify a
      jump proxy:
    </para>
    <programlisting language="bash">
{
  options =
    [ &quot;ProxyJump=bastion@example.com&quot;
      &quot;Port=22&quot;
    ];
}
</programlisting>
    <para>
      It’s also possible to change the <literal>ssh</literal> command
      used by SSHFS to connect to the server. For example:
    </para>
    <programlisting language="bash">
{
  options =
    [ (builtins.replaceStrings [&quot; &quot;] [&quot;\\040&quot;]
        &quot;ssh_command=${pkgs.openssh}/bin/ssh -v -L 8080:localhost:80&quot;)
    ];

}
</programlisting>
    <note>
      <para>
        The escaping of spaces is needed because every option is written
        to the <literal>/etc/fstab</literal> file, which is a
        space-separated table.
      </para>
    </note>
    <section xml:id="sec-sshfs-troubleshooting">
      <title>Troubleshooting</title>
      <para>
        If you’re having a hard time figuring out why mounting is
        failing, you can add the option
        <literal>&quot;debug&quot;</literal>. This enables a verbose log
        in SSHFS that you can access via:
      </para>
      <programlisting>
$ journalctl -u $(systemd-escape -p /mnt/my-dir/).mount
Jun 22 11:41:18 workstation mount[87790]: SSHFS version 3.7.1
Jun 22 11:41:18 workstation mount[87793]: executing &lt;ssh&gt; &lt;-x&gt; &lt;-a&gt; &lt;-oClearAllForwardings=yes&gt; &lt;-oServerAliveInterval=15&gt; &lt;-oIdentityFile=/var/secrets/wrong-key&gt; &lt;-2&gt; &lt;my-user@example.com&gt; &lt;-s&gt; &lt;sftp&gt;
Jun 22 11:41:19 workstation mount[87793]: my-user@example.com: Permission denied (publickey).
Jun 22 11:41:19 workstation mount[87790]: read: Connection reset by peer
Jun 22 11:41:19 workstation systemd[1]: mnt-my\x2ddir.mount: Mount process exited, code=exited, status=1/FAILURE
Jun 22 11:41:19 workstation systemd[1]: mnt-my\x2ddir.mount: Failed with result 'exit-code'.
Jun 22 11:41:19 workstation systemd[1]: Failed to mount /mnt/my-dir.
Jun 22 11:41:19 workstation systemd[1]: mnt-my\x2ddir.mount: Consumed 54ms CPU time, received 2.3K IP traffic, sent 2.7K IP traffic.
</programlisting>
      <note>
        <para>
          If the mount point contains special characters it needs to be
          escaped using <literal>systemd-escape</literal>. This is due
          to the way systemd converts paths into unit names.
        </para>
      </note>
    </section>
  </section>
</section>
