emacs-ssh-deploy
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.
DirectoryVariables
or File Variables
)eshell
and shell
terminals in base or relative directorydired
browsing in base or relative directoryediff
ediff
(make-thread
) or async.el
is installed. (You need to setup an automatic authorization for this, i.e. ~/.authinfo.gpg
and/or key-based password-less authorization)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).
Here is a list of other variables you can set globally or per directory:
ssh-deploy-root-local
The local root that should be under deployment (string)ssh-deploy-root-remote
The remote Tramp root that is used for deployment (string)ssh-deploy-debug
Enables debugging messages (integer)ssh-deploy-revision-folder
The folder used for storing local revisions (string)ssh-deploy-automatically-detect-remote-changes
Enables automatic detection of remote changes (integer)ssh-deploy-on-explicit-save
Enabled automatic uploads on save (integer)ssh-deploy-force-on-explicit-save
Enables forced uploads on explicit save actions (integer)ssh-deploy-exclude-list
A list defining what paths to exclude from deployment (list)ssh-deploy-async
Enables asynchronous transfers (you need to have (make-thread)
or async.el
installed as well) (integer)ssh-deploy-remote-sql-database
Default database when connecting to remote SQL database (string)ssh-deploy-remote-sql-password
Default password when connecting to remote SQL database (string)ssh-deploy-remote-sql-port
- Default port when connecting to remote SQL database (integer)ssh-deploy-remote-sql-server
Default server when connecting to remote SQL database (string)ssh-deploy-remote-sql-user
Default user when connecting to remote SQL database (string)ssh-deploy-remote-shell-executable
Default remote shell executable when launching shell on remote host (string)ssh-deploy-verbose
Show messages in message buffer when starting and ending actions (integer)ssh-deploy-script
- Your custom lambda function that will be called using (funcall) when running deploy script handler (function)ssh-deploy-async-with-threads
- Whether to use threads (make threads) instead of processes (async-start) for asynchronous operations (integer)When integers are used as booleans, above zero means true, zero means false and nil means unset and fallback to global settings.
~/.emacs.d/ssh-deploy/
or install via package.el
(M-x list-packages
or M-x package-install
+ ssh-deploy
) from the ELPA
or MELPA
repository./Users/username/Web/MySite/
to create this DirectoryVariables
file in your project root at /Users/username/Web/MySite/.dir-locals.el
.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.
((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")
)))
((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)
)))
((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:
((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"))))
)))
((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.
((nil . (
(ssh-deploy-root-local . "/Users/username/Web/MySite/")
(ssh-deploy-root-remote . "/ftp:myuser@myserver.com:/MySite/")
(ssh-deploy-on-explicit-save . 1)
)))
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.
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
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.
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.
;; 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
and hydra-script
I'm using: (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.
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"))))
)))
/Users/username/Web/MySite/
, the script will launch and deploy the file with the remote server.C-c C-z x
and the current buffer is a file, you will launch a ediff
session showing differences between local file and remote file via Tramp, or if current buffer is a directory it will open a buffer showing directory differences
w* If you press C-c C-z f
you will force upload local file or directory to remote host even if they have external changes.C-c C-z u
you will upload local file or directory to remote host.C-c C-z d
you will download the current file or directory from remote host and then reload current buffer.C-c C-z D
you will delete the current file or directory after a confirmation on local and remote host.C-c C-z t
you will open a terminal with remote host in base directory via eshell
.C-c C-z T
you will open a terminal with remote host in current directory via eshell
.C-c C-z h
you will open a terminal with remote host in base directory via shell
.C-c C-z H
you will open a terminal with remote host in current directory via shell
.C-c C-z b
you will browse base directory on remote host in dired
.C-c C-z B
you will browse current directory on remote host in dired
.C-c C-z R
you will rename current file or directory.C-c C-z e
you will check for remote changes to the current file.C-c C-z o
you will open remote file corresponding to local file.C-c C-z m
you will open remote sql-mysql session on remote host.C-c C-z s
you will run your custom deploy script.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.
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:
tnftp
inside the extracted archive in terminal./configure
then make
and then sudo make install
mv ./src/ftp /usr/local/bin/ftp
Ange-FTP defaults to ~/.netrc
so you need to add this to your init script:
(setq ange-ftp-netrc-filename "~/.authinfo.gpg")
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))))
Run make test
from plug-in folder to run tests
if you need to specify specific Emacs use export syntax i.e. export emacs="YOUR_PATH" && make tests
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