cjohansson / emacs-ssh-deploy

A deployment plugin via Tramp for Emacs.
GNU General Public License v3.0
152 stars 6 forks source link

emacs-ssh-deploy

License GPL 3 MELPA MELPA Stable Build Status

The ssh-deploy plug-in for Emacs makes it possible to effortlessly deploy local files and directories to remote hosts via Tramp (including but not limited to SSH, SFTP, FTP). It tries to provide functions that can be easily used by custom scripts.

Features:

The idea for this plug-in was to mimic the behavior of PhpStorm deployment functionality.

This application is made by Christian Johansson christian@cvj.se 2016-2021 and is licensed under GNU General Public License 3 (GNU GPL 3).

Configuration

Here is a list of other variables you can set globally or per directory:

When integers are used as booleans, above zero means true, zero means false and nil means unset and fallback to global settings.

Deployment configuration examples

You really need to do a bit of research about how to connect via different protocols using Tramp on your operating system, I think Windows users should use plink for most protocols. Linux should work out of the box and macOS requires a bit of tweaking to get FTP support.

SSH, with automatic uploads and SQL

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/ssh:myuser@myserver.com:/var/www/MySite/")
  (ssh-deploy-on-explicit-save . 1)
  (ssh-deploy-remote-sql-database . "myuser")
  (ssh-deploy-remote-sql-password . "mypassword")
  (ssh-deploy-remote-sql-user . "myuser")
)))

SFTP, with forced automatic uploads

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/sftp:myuser@myserver.com:/var/www/MySite/")
  (ssh-deploy-on-explicit-save . 1)
  (ssh-deploy-force-on-explicit-save . 1)
)))

SSH, custom port 2120, not asynchronous and without automatic uploads

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/ssh:myuser@myserver.com#2120:/var/www/MySite/")
  (ssh-deploy-on-explicit-save . 0)
  (ssh-deploy-async . 0)
)))

You can pipe remote connections as well like this:

SSH, asynchronous using threads, with automatic uploads, piped to other user on remote server and with custom deployment script.

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/ssh:myuser@myserver.com|sudo:web@myserver.com:/var/www/MySite/")
  (ssh-deploy-async . 1)
  (ssh-deploy-async-with-threads . 1)
  (ssh-deploy-on-explicit-save . 1)
  (ssh-deploy-script . (lambda() (let ((default-directory ssh-deploy-root-remote)) (shell-command "bash compile.sh"))))
)))

SSH, asynchronous not using threads, without automatic uploads, piped to other user on remote server and with custom deployment script.

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/ssh:myuser@myserver.com|sudo:web@myserver.com:/var/www/MySite/")
  (ssh-deploy-async . 1)
  (ssh-deploy-async-with-threads . 0)
  (ssh-deploy-on-explicit-save . 0)
  (ssh-deploy-script . (lambda() (let ((default-directory ssh-deploy-root-local)) (shell-command "bash compile.sh") (ssh-deploy-upload-handler))))
)))

If you have a password-less sudo on your remote host you should be to do this asynchronously or if you have your sudo credentials in your ~/.authinfo.gpg file.

FTP, with automatic uploads

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/ftp:myuser@myserver.com:/MySite/")
  (ssh-deploy-on-explicit-save . 1)
)))

Interaction-free SSH setup using password-less public-key authorization

For automatic SSH connections you need to setup a password-less public-key authorization. You need to research how to setup this on your operating system.

Changing SSH port or public-key identify-file per host

If you have a SSH connection that is using a different identity-file than the default, or if it is using a different port than the default you just need to edit your local SSH-config ~/.ssh/config to make it work using this plug-in, like this:

## My special connection (replace remote-host, remote-port and identity-file with your values)
Host remote-host
    Port remote-port
    IdentityFile identity-file

Interaction-free password-based setup on *NIX systems

For automatic FTP connections you need to setup ~/.authinfo.gpg with your login credentials. An example of contents:

machine myserver.com login myuser port ftp password mypassword
machine myserver2.com login myuser2 port ssh password mypassword2
machine myserver3.com login myuser3 port sftp password mypassword3

Set your user and group as owner and file permissions to 600. Emacs should now be able to automatically connect to this server via FTP without any user interaction.

Interaction-free SSH setup using public-key password-based authorization

By combining a ~/.authinfo.gpg setup and a public-key setup you should be able to have a interaction-free public-key password-based authorization that can be used asynchronously.

Emacs configuration example

;; ssh-deploy - prefix = C-c C-z, f = forced upload, u = upload, d = download, x = diff, t = terminal, b = browse, h = shell
(add-to-list 'load-path "~/.emacs.d/ssh-deploy/")
(require 'ssh-deploy)
(ssh-deploy-line-mode) ;; If you want mode-line feature
(ssh-deploy-add-menu) ;; If you want menu-bar feature
(ssh-deploy-add-after-save-hook) ;; If you want automatic upload support
(ssh-deploy-add-find-file-hook) ;; If you want detecting remote changes support
(global-set-key (kbd "C-c C-z") 'ssh-deploy-prefix-map)

If you want to use the pre-defined hydra you can use this key-binding instead:

(ssh-deploy-hydra "C-c C-z")
      (use-package ssh-deploy
        :ensure t
        :demand
        :after hydra
        :hook ((after-save . ssh-deploy-after-save)
               (find-file . ssh-deploy-find-file))
        :config
        (ssh-deploy-line-mode) ;; If you want mode-line feature
        (ssh-deploy-add-menu) ;; If you want menu-bar feature
        (ssh-deploy-hydra "C-c C-z") ;; If you want the hydra feature
      )

(1) You can remove the (add-to-list) and (require) lines if you installed via ELPA or MELPA repository.

Example usage

File contents /Users/username/Web/MySite/.dir-locals.el:

((nil . (
  (ssh-deploy-root-local . "/Users/username/Web/MySite/")
  (ssh-deploy-root-remote . "/ssh:myuser@myserver.com|sudo:web@myserver.com:/var/www/MySite/")
  (ssh-deploy-async . 1)
  (ssh-deploy-on-explicit-save . 1)
  (ssh-deploy-script . (lambda() (let ((default-directory ssh-deploy-root-remote))(shell-command "bash compile.sh"))))
)))

The local path and local root is evaluated based on their truename so if you use different symbolic local paths it shouldn't affect the deployment procedure.

The above configuration example uses the Emacs plug-in use-package which I highly recommend.

Tramp FTP problem in macOS 10.13

macOS 10.13 removed the Darwin port of BSD ftp which is needed for ange-ftp, which is required by Tramp. You can get it back by doing this:

  1. Download https://opensource.apple.com/tarballs/lukemftp/lukemftp-16.tar.gz or some other version from https://opensource.apple.com/tarballs/lukemftp/
  2. Extract archive
  3. Visit folder for tnftp inside the extracted archive in terminal
  4. Type ./configure then make and then sudo make install
  5. Type mv ./src/ftp /usr/local/bin/ftp

Tramp FTP doesn't read my ~/.authinfo.gpg

Ange-FTP defaults to ~/.netrc so you need to add this to your init script:

(setq ange-ftp-netrc-filename "~/.authinfo.gpg")

DirectoryVariables not read in evil-mode / doom-emacs / spacemacs et. al

Thanks shrubbroom for poiting out that evil-mode has a upstream bug were the function hack-local-variables are not executed as expected and this results in that DirectoryVariables are not set, to fix this you can add this to your init-script:

(advice-add #'turn-on-evil-mode :before
              (lambda (&optional args)
                (when (eq major-mode 'fundamental-mode)
                  (hack-local-variables))))

Tests

Run make test from plug-in folder to run tests

From custom Emacs version

if you need to specify specific Emacs use export syntax i.e. export emacs="YOUR_PATH" && make tests

With tests for async.el integration

Make sure to load if before the unit tests, i.e. ~/Documents/emacs/src/emacs -Q -batch -L ../elpa/async-20180527.1730/ -L . -l ../elpa/async-20180527.1730/async.el -l ssh-deploy-test.el

Read more