diff --git a/README.md b/README.md index 9e8d478f..f98e3b41 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Have as many scripts as you want to extend the power of `nnn`! Pick from the ava - [Navigate-as-you-type](#navigate-as-you-type) - [File indicators](#file-indicators) - [Configuration](#configuration) + - [SSHFS mounts](#sshfs-mounts) - [Help](#help) - [Plugins](#plugins) - [Troubleshooting](#troubleshooting) @@ -103,7 +104,7 @@ Have as many scripts as you want to extend the power of `nnn`! Pick from the ava - FreeDesktop compliant trash (needs trash-cli) - Show copy, move progress on Linux (needs avdcpmv) - Plugin repository - - Transfer files using lftp + - SSHFS mounts (needs sshfs) - Batch rename (needs vidir) - Per-context directory color (default: blue) - Spawn a shell in the current directory @@ -140,6 +141,8 @@ Have as many scripts as you want to extend the power of `nnn`! Pick from the ava | vidir (from moreutils) | batch rename dir entries | | vlock (Linux), bashlock (macOS), lock(1) (BSD) | terminal locker | | advcpmv (Linux) ([integration](https://github.com/jarun/nnn/wiki/hacking-nnn#show-cp-mv-progress)) | copy, move progress | +| sshfs | mount remote over SSHFS | +| fusermount(3) | SSHFS unmount | | $EDITOR (overridden by $VISUAL, if defined) | edit files (fallback vi) | | $PAGER (less, most) | page through files (fallback less) | | $SHELL | spawn a shell, run some commands (fallback sh) | @@ -264,6 +267,7 @@ Press ? in `nnn` to see the list anytime. MISC ! ^] Spawn SHELL C Execute entry R ^V Pick plugin L Lock terminal + c SSHFS mount u Unmount ^P Prompt ^N Note = Launcher ``` @@ -380,8 +384,9 @@ The following indicators are used in the detail view: | `NNN_IDLE_TIMEOUT=300` | idle seconds before locking terminal [default: disabled] | | `NNN_COPIER='/absolute/path/to/copier'` | system clipboard copier script [default: none] | | `NNN_PLUGIN_DIR=/home/user/nnn-plugins` | absolute path to plugins dir | -| `NNN_NOTE=/home/user/Dropbox/Public/notes` | path to note file [default: none] | +| `NNN_NOTE=/home/user/Dropbox/notes` | path to note file [default: none] | | `NNN_TMPFILE=/tmp/nnn` | file to write current open dir path to for cd on quit | +| `NNN_SSHFS_MNT_ROOT=/home/user/.netmnt` | absolute path to SSHFS mount point root | | `NNN_USE_EDITOR=1` | Open text files in `$EDITOR` (`$VISUAL`, if defined; fallback vi) | | `NNN_NO_AUTOSELECT=1` | do not auto-select matching dir in _nav-as-you-type_ mode | | `NNN_RESTRICT_NAV_OPEN=1` | open files on , not or l | @@ -389,6 +394,33 @@ The following indicators are used in the detail view: | `NNN_TRASH=1` | trash files to the desktop Trash [default: delete] | | `NNN_OPS_PROG=1` | show copy, move progress on Linux | +#### SSHFS mounts + +To connect to and mount remote shares using SSHFS, `nnn` requires the following: + +1. ssh configuration file `~/.ssh/config` should have the host entries. sshfs reads this file. +2. `NNN_SSHFS_MNT_ROOT` should be set to the **absolute path** to the directory under which `nnn` creates the mount point for a host. The mount point is the same as the host name. + +Example host entry for a Termux environment on Android device: + +``` +Host phone + HostName 192.168.0.102 + User u0_a117 + Port 8022 +``` + +If `NNN_SSHFS_MNT_ROOT` is set to `/home/user/remotes`, the above host `phone` will be mounted at `/home/user/remotes/phone`. `nnn` creates the directory `phone` if it doesn't exist. + +To unmount a mount point highlight it in `nnn` (so that it's the current entry) and press the relevant keybind to unmount. It might be a good idea to bookmark `NNN_SSHFS_MNT_ROOT`. + +Notes: + +1. `nnn` places you inside the mount point after both mount and unmount. This is done so you can ensure the operation completed successfully. To jump back to the last directory, press the usual -. +2. `nnn` doesn't delete the mount point on unmount. This is to prevent accidental data loss. + +More information on [SSHFS](https://wiki.archlinux.org/index.php/SSHFS) + #### Help $ nnn -h diff --git a/nnn.1 b/nnn.1 index 8621776a..f21ea14a 100644 --- a/nnn.1 +++ b/nnn.1 @@ -189,6 +189,11 @@ when dealing with the !, e and p commands respectively. A single combination to export NNN_TMPFILE=/tmp/nnn .Ed .Pp +\fBNNN_SSHFS_MNT_ROOT:\fR absolute path to SSHFS mount point root. Mount points are created at this location. +.Bd -literal + export NNN_SSHFS_MNT_ROOT=/home/user/.netmnt` +.Ed +.Pp \fBNNN_USE_EDITOR:\fR use EDITOR (VISUAL takes preference, preferably CLI, fallback vi) to handle text files. .Bd -literal diff --git a/src/nnn.c b/src/nnn.c index 05d1a266..2f10bb22 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -279,6 +279,7 @@ static char *editor; static char *pager; static char *shell; static char *home; +static char *sshfsmnt; static blkcnt_t ent_blocks; static blkcnt_t dir_blocks; static ulong num_files; @@ -388,15 +389,16 @@ static const char * const messages[] = { #define NNN_PLUGIN_DIR 5 #define NNN_NOTE 6 #define NNN_TMPFILE 7 -#define NNNLVL 8 /* strings end here */ -#define NNN_USE_EDITOR 9 /* flags begin here */ -#define NNN_NO_AUTOSELECT 10 -#define NNN_RESTRICT_NAV_OPEN 11 -#define NNN_RESTRICT_0B 12 -#define NNN_OPENER_DETACH 13 -#define NNN_TRASH 14 +#define NNN_SSHFS_MNT_ROOT 8 +#define NNNLVL 9 /* strings end here */ +#define NNN_USE_EDITOR 10 /* flags begin here */ +#define NNN_NO_AUTOSELECT 11 +#define NNN_RESTRICT_NAV_OPEN 12 +#define NNN_RESTRICT_0B 13 +#define NNN_OPENER_DETACH 14 +#define NNN_TRASH 15 #ifdef __linux__ -#define NNN_OPS_PROG 15 +#define NNN_OPS_PROG 16 #endif static const char * const env_cfg[] = { @@ -408,6 +410,7 @@ static const char * const env_cfg[] = { "NNN_PLUGIN_DIR", "NNN_NOTE", "NNN_TMPFILE", + "NNN_SSHFS_MNT_ROOT", "NNNLVL", "NNN_USE_EDITOR", "NNN_NO_AUTOSELECT", @@ -2387,6 +2390,7 @@ static bool show_help(const char *path) "1MISC\n" "9! ^] Spawn SHELL C Execute entry\n" "9R ^V Pick plugin L Lock terminal\n" + "cc SSHFS mount u Unmount\n" "b^P Prompt ^N Note = Launcher\n"}; if (g_tmpfpath[0]) @@ -2888,10 +2892,8 @@ begin: /* Can fail when permissions change while browsing. * It's assumed that path IS a directory when we are here. */ - if (access(path, R_OK) == -1) { + if (access(path, R_OK) == -1) printwarn(); - goto nochange; - } populate(path, lastname); if (interrupted) { @@ -3862,6 +3864,73 @@ nochange: /* Repopulate as directory content may have changed */ goto begin; + case SEL_SSHFS: + if (!sshfsmnt) { + printwait("set NNN_SSHFS_MNT_ROOT", &presel); + goto nochange; + } + + tmp = xreadline(NULL, "Host: "); + if (!tmp[0]) + goto nochange; + + /* Create the mount point */ + mkpath(sshfsmnt, tmp, newpath); + r = mkdir(newpath, 0777); + if (r == -1 && errno != EEXIST) { + printwait(strerror(errno), &presel); + goto nochange; + } + + /* Check if directory can be accessed */ + if (!xdiraccess(newpath)) { + presel = MSGWAIT; + goto nochange; + } + + if (!getutil("sshfs")) { + printwait("sshfs missing", &presel); + goto nochange; + } + + /* Convert "Host" to "Host:" */ + r = strlen(tmp); + tmp[r] = ':'; + tmp[r + 1] = '\0'; + + /* Connect to remote */ + spawn("sshfs", tmp, newpath, NULL, F_NORMAL); // fallthrough + case SEL_UMOUNT: + if (sel == SEL_UMOUNT) { + static char cmd[] = "fusermount3"; /* Arch Linux utility */ + static bool found = FALSE; + + /* On Ubuntu it's fusermount */ + if (!found && !getutil(cmd)) + cmd[10] = '\0'; + + if (!ndents) + goto nochange; + + mkpath(path, dents[cur].name, newpath); + if (!xdiraccess(newpath)) { + presel = MSGWAIT; + goto nochange; + } + + spawn(cmd, "-u", newpath, NULL, F_NORMAL); + } + + lastname[0] = '\0'; + + /* Save last working directory */ + xstrlcpy(lastdir, path, PATH_MAX); + + /* Switch to mount point */ + xstrlcpy(path, newpath, PATH_MAX); + + setdirwatch(); + goto begin; case SEL_QUITCD: // fallthrough case SEL_QUIT: for (r = 0; r < CTX_MAX; ++r) @@ -4169,6 +4238,9 @@ int main(int argc, char *argv[]) xstrlcpy(g_cppath + g_tmpfplen - 1, "/.nnncp", PATH_MAX - g_tmpfplen); } + /* Get SSHFS mountpoint */ + sshfsmnt = getenv("NNN_SSHFS_MNT_ROOT"); + /* Get the clipboard copier, if set */ copier = getenv(env_cfg[NNN_COPIER]); diff --git a/src/nnn.h b/src/nnn.h index dd21ef5b..d47080b9 100644 --- a/src/nnn.h +++ b/src/nnn.h @@ -85,6 +85,8 @@ enum action { SEL_NEW, SEL_RENAME, SEL_RENAMEALL, + SEL_SSHFS, + SEL_UMOUNT, SEL_HELP, SEL_EXEC, SEL_SHELL, @@ -218,6 +220,10 @@ static struct key bindings[] = { { CONTROL('R'), SEL_RENAME }, /* Rename contents of current dir */ { 'r', SEL_RENAMEALL }, + /* Connect to server over SSHFS */ + { 'c', SEL_SSHFS }, + /* Disconnect a SSHFS mount point */ + { 'u', SEL_UMOUNT }, /* Show help */ { '?', SEL_HELP }, /* Execute file */