Skip to content

Commit

Permalink
Add the ability to mount sites on Windows drive letters
Browse files Browse the repository at this point in the history
  • Loading branch information
uberhacker committed Jul 24, 2017
1 parent 22569e8 commit 0e30715
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 91 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ Terminus plugin to mount [Pantheon](https://pantheon.io/) site environments.

## Usage:
```
terminus [site:]mount|[site:]u(n)mount site-name.env [--dir=<directory>]
terminus [site:]mount|[site:]u(n)mount site-name.env [--dir=<directory> --drive=<drive letter>]
```

By default, the site environment will be mounted in `/tmp/site-name.env`.
By default, the site environment will be mounted in `$HOME/site-name.env` (Linux / Mac only).

If you want to mount in a different directory, use the `--dir=<directory>` option.

Keep in mind, if you mount in a different directory, you will also need to specify the same `--dir` option when unmounting.

The `--drive=<drive letter>` option is only necessary on Windows. The values can be 'first', 'last' or any available drive letter.

The default is `--drive=first` which means the first available drive letter.

## Examples:
Mount the site environment awesome-site.dev.
```
Expand All @@ -31,7 +35,7 @@ Learn more about [Terminus](https://pantheon.io/docs/terminus/) and [Terminus Pl

## Prerequisites:

Executable mount, umount and sshfs commands must exist.
Executable mount, umount and sshfs commands must exist (Linux / Mac only).

### Mac OSX:

Expand All @@ -43,6 +47,10 @@ Executable mount, umount and sshfs commands must exist.
- Debian-based (Debian, Linux Mint, Ubuntu and derivatives): `sudo apt-get update && sudo apt-get install sshfs`
- RedHat-based (RedHat, Fedora, CentOS and derivatives): `sudo yum install sshfs`

### Windows:

- Download and install [SFTP Net Drive 2017](http://www.sftpnetdrive.com/products/NDXCA/download).

## Installation:
For installation help, see [Extend with Plugins](https://pantheon.io/docs/terminus/plugins/).

Expand Down
232 changes: 144 additions & 88 deletions src/Commands/SiteMountCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,41 @@
// Determine if the environment is Windows.
define('TERMINUS_SITE_MOUNT_WINDOWS', (php_uname('s') == 'Windows NT'));

// Determine the default mount location.
$temp_dir = TERMINUS_SITE_MOUNT_WINDOWS ? '\\Temp' : '/tmp';
define('TERMINUS_SITE_MOUNT_TEMP_DIR', $temp_dir);
// Set the default mount location.
define('TERMINUS_SITE_MOUNT_DIR', TERMINUS_SITE_MOUNT_HOME);

// Set the default drive letter.
define('TERMINUS_SITE_MOUNT_DRIVE', 'first');

/**
* Class SiteMountCommand
* Mounts/unmounts the site environment via SSHFS.
* Mounts/unmounts the site environment via SSHFS/SFTP.
*/
class SiteMountCommand extends TerminusCommand implements SiteAwareInterface
{

use SiteAwareTrait;

/**
* Mounts the site environment via SSHFS.
* Mounts the site environment via SSHFS/SFTP.
*
* @authorize
*
* @command site:mount
* @aliases mount
*
* @param string $site_env Site & environment in the format `site-name.env`
* @option dir Directory to mount
* @option dir Directory to mount (Linux / Mac only)
* @option drive Drive letter to mount (Windows only)
*
* @usage terminus site:mount <site>.<env>
* @usage terminus site:mount|mount <site-name>.<env> [--dir=<directory> --drive=<drive letter>]
*/
public function mount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT_TEMP_DIR])
public function mount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT_DIR, 'drive' => TERMINUS_SITE_MOUNT_DRIVE])
{
$this->checkRequirements();
$sftp = $this->checkRequirements();

if (empty($site_env)) {
$message = "Usage: terminus site:mount|mount <site-name.env> --dir=<directory>";
$message = "Usage: terminus site:mount|mount <site-name.env> [--dir=<directory> --drive=<drive letter>]";
throw new TerminusNotFoundException($message);
}

Expand All @@ -56,34 +59,58 @@ public function mount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT_T
$host = $connection_info['host'];
$port = $connection_info['port'];

// Determine the mount location.
$mount = str_replace('~', TERMINUS_SITE_MOUNT_HOME, $options['dir']) . DIRECTORY_SEPARATOR . $site_env;

// Create the mount directory if it doesn't exist.
$command = "if [ ! -d \"{$mount}\" ]; then mkdir \"{$mount}\"; fi";
exec($command, $messages);
foreach ($messages as $message) {
$this->log()->notice($message);
// Execute the command(s).
if (TERMINUS_SITE_MOUNT_WINDOWS) {
// Set the profile configuration file.
$username = getenv('USERNAME');
$config = "/Users/{$username}/AppData/Roaming/SftpNetDrive/{$site_env}.cfg";

// Create the profile if it doesn't exist.
if (!file_exists("{$config}")) {
$command = "\"{$sftp}\" new /profile:{$site_env} /server:{$host} /port:{$port} /login:{$user} /letter:{$options['drive']} /open /access:private /reconnect:3 /keepalive:5 /compression:1 /timeout:60";
$this->execute($command);
}

// Set the drive letter.
if ($options['drive'] != TERMINUS_SITE_MOUNT_DRIVE) {
$command = "\"{$sftp}\" set /profile:{$site_env} /letter:{$options['drive']}";
$this->execute($command);
}

// Start the profile.
$command = "start \"\" /b \"{$sftp}\" start /profile:{$site_env} > nul 2> nul";

// Set the message.
$message = "Site mounted successfully. Unmount with 'terminus umount {$site_env}'.";
} else {
// Determine the mount location.
$mount = str_replace('~', TERMINUS_SITE_MOUNT_HOME, $options['dir']) . DIRECTORY_SEPARATOR . $site_env;

// Create the mount directory if it doesn't exist.
if (!file_exists("{$mount}")) {
$command = "mkdir \"{$mount}\"";
$this->execute($command);
}

// Abort if unable to create the mount directory.
if (!file_exists("{$mount}")) {
$message = "Unable to mount in '{$mount}'.";
throw new TerminusNotFoundException($message);
}

$command = "sudo sshfs -o Port={$port} -o allow_other {$user}@{$host}:. {$mount}";

// Set the message.
$unmount = 'terminus umount ';
$temp_mount_dir = TERMINUS_SITE_MOUNT_DIR . DIRECTORY_SEPARATOR . $site_env;
$unmount .= ($mount == $temp_mount_dir) ? $site_env : $site_env . ' --dir=' . $options['dir'];
$message = "Site mounted in '{$mount}' successfully. Unmount with '{$unmount}'.";
}

// Abort if unable to create the mount directory.
if (!file_exists("{$mount}")) {
$message = "Unable to mount in '{$mount}'.";
throw new TerminusNotFoundException($message);
}
// Execute the command to mount.
$this->execute($command);

// Execute the sshfs command.
$command = "sudo sshfs -o Port={$port} -o allow_other {$user}@{$host}:. {$mount}";
exec($command, $messages);
foreach ($messages as $message) {
$this->log()->notice($message);
}

// Output mounted files location message.
$unmount = 'terminus umount ';
$temp_mount_dir = TERMINUS_SITE_MOUNT_TEMP_DIR . DIRECTORY_SEPARATOR . $site_env;
$unmount .= ($mount == $temp_mount_dir) ? $site_env : $site_env . ' --dir=' . $options['dir'];
$message = "Site mounted in '{$mount}' successfully. Unmount with '{$unmount}'.";
// Output the final message.
$this->log()->notice($message);
}

Expand All @@ -98,47 +125,46 @@ public function mount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT_T
* @param string $site_env Site & environment in the format `site-name.env`
* @option dir Directory to unmount
*
* @usage terminus site:umount <site>.<env>
* @usage terminus site:umount|site:unmount|umount|unmount <site-name>.<env> [--dir=<directory>]
*/
public function unmount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT_TEMP_DIR])
public function unmount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT_DIR])
{
$this->checkRequirements();
$sftp = $this->checkRequirements();

if (empty($site_env)) {
$message = "Usage: terminus site:umount|site:unmount|umount|unmount <site-name.env> --dir=<directory>";
$message = "Usage: terminus site:umount|site:unmount|umount|unmount <site-name.env> [--dir=<directory>]";
throw new TerminusNotFoundException($message);
}

// Determine the mount location.
$mount = str_replace('~', TERMINUS_SITE_MOUNT_HOME, $options['dir']) . DIRECTORY_SEPARATOR . $site_env;

// Check if the directory exists.
if (!file_exists("{$mount}")) {
$message = "Site environment {$site_env} not mounted.";
$this->log()->notice($message);
return;
}

// Cannot unmount inside a mounted directory.
exec('pwd', $directory);
$pwd = array_pop($directory);
if (strpos($pwd, $mount) !== false) {
$message = 'Please change to another directory outside the mounted location and try again.';
throw new TerminusNotFoundException($message);
}

// Execute the umount command.
$command = "sudo umount {$mount}";
exec($command, $messages);
foreach ($messages as $message) {
$this->log()->notice($message);
}

// Remove mount directory.
$command = "rmdir {$mount}";
exec($command, $messages);
foreach ($messages as $message) {
$this->log()->notice($message);
if (TERMINUS_SITE_MOUNT_WINDOWS) {
// Execute the command to unmount.
$command = "\"{$sftp}\" stop /profile:{$site_env} /wait /timeout:30";
$this->execute($command);
} else {
// Determine the mount location.
$mount = str_replace('~', TERMINUS_SITE_MOUNT_HOME, $options['dir']) . DIRECTORY_SEPARATOR . $site_env;

// Check if the directory exists.
if (!file_exists("{$mount}")) {
$message = "Site environment {$site_env} not mounted.";
throw new TerminusNotFoundException($message);
}

// Cannot unmount inside a mounted directory.
exec('pwd', $directory);
$pwd = array_pop($directory);
if (strpos($pwd, $mount) !== false) {
$message = 'Please change to another directory outside the mounted location and try again.';
throw new TerminusNotFoundException($message);
}

// Execute the command to unmount.
$command = "sudo umount {$mount}";
$this->execute($command);

// Remove mount directory.
$command = "rmdir {$mount}";
$this->execute($command);
}
}

Expand All @@ -150,31 +176,61 @@ public function unmount($site_env = '', $options = ['dir' => TERMINUS_SITE_MOUNT
*/
protected function commandExists($command)
{
// @TODO: This could be a generic utility function used by other commands.

$test_command = TERMINUS_SITE_MOUNT_WINDOWS ? 'where' : 'command -v';
$file = popen("$test_command $command", 'r');
$result = fgets($file, 255);
return $windows ? !preg_match('#Could not find files#', $result) : !empty($result);
if (TERMINUS_SITE_MOUNT_WINDOWS) {
exec("cd / && dir /s /b {$command} 2> nul", $paths);
$path = array_pop($paths);
return file_exists("$path") ? $path : false;
} else {
$test_command = 'command -v';
$file = popen("{$test_command} {$command}", 'r');
$result = fgets($file, 255);
return !empty($result);
}
}

/**
* Check for plugin requirements.
*/
protected function checkRequirements()
{
if (!$this->commandExists('mount')) {
$message = 'Please install the mount command to enable site mounts.';
throw new TerminusNotFoundException($message);
}
if (!$this->commandExists('umount')) {
$message = 'Please install the umount command to enable site mounts.';
throw new TerminusNotFoundException($message);
}
if (!$this->commandExists('sshfs')) {
$release = TERMINUS_SITE_MOUNT_WINDOWS ? 'See https://linhost.info/2012/09/sshfs-in-windows/.' : 'See https://github.com/libfuse/sshfs/releases or install via your package manager.';
$message = "Please install sshfs to enable site mounts. {$release}";
throw new TerminusNotFoundException($message);
if (TERMINUS_SITE_MOUNT_WINDOWS) {
if (!$path = $this->commandExists('SftpNetDrive.exe')) {
$release = 'See http://www.sftpnetdrive.com/products/NDXCA/download';
$message = "Please install SftpNetDrive to enable site mounts. {$release}.";
throw new TerminusNotFoundException($message);
} else {
return $path;
}
} else {
if (!$this->commandExists('mount')) {
$message = 'Please install the mount command to enable site mounts.';
throw new TerminusNotFoundException($message);
}
if (!$this->commandExists('umount')) {
$message = 'Please install the umount command to enable site mounts.';
throw new TerminusNotFoundException($message);
}
if (!$this->commandExists('sshfs')) {
$release = 'See https://github.com/libfuse/sshfs/releases or install via your package manager';
$message = "Please install sshfs to enable site mounts. {$release}.";
throw new TerminusNotFoundException($message);
}
}
}
}

/**
* Executes the command.
*/
protected function execute($cmd) {
$process = proc_open(
$cmd,
[
0 => STDIN,
1 => STDOUT,
2 => STDERR,
],
$pipes
);
proc_close($process);
}
}

0 comments on commit 0e30715

Please sign in to comment.