I only really use laptops, and I use several throughout my various tasks. This leads to some synchronisation and authentication issues; maybe I need to develop on something that's on another machine, or I need to use SSH/GPG authentication to connect/sign to my version control system.

I ended up working out how to get JetBrains IDEs working for development on one of my servers so that I can synchronise my development while maintaining segmentation for my credentials.

SSH agent forwarding

I use SSH authentication to connect to my VCSes with a variety of different machines, each with their own set of SSH keypairs for connecting. To forward my SSH agent, and therefore allow the remote machine to connect on my behalf when I'm SSH'd in without storing the keys physically on the server, I added the following lines to my SSH config:

Host development-server
 Hostname xxx.xxx.xxx.xxx
 Port 22
 ForwardAgent yes

Now I can perform git cloning and pushing when connecting to development-server. This is a good start, but I also sign my commits.

GPG agent forwarding

This one's a bit more tricky. I use GPG keypairs to sign my GitHub commits, with different keypairs to indicate on which device the development was performed. In this way, it acts as public documentation of whether some work was done with respect to my work or on my own time. I need to continue signing my commits with these keys or GitHub will throw a fit about partial or failed verification.

To do this, I first performed the following command on the local machine:

local$ gpgconf --list-dir agent-extra-socket
/run/user/1000/gnupg/S.gpg-agent.extra

Then on the remote machine:

development-server$ gpgconf --list-dir agent-socket
/run/user/1000/gnupg/S.gpg-agent

After this, I added the following to my (local) SSH config:

Host development-server
 Hostname xxx.xxx.xxx.xxx
 Port 22
 ForwardAgent yes
 RemoteForward /run/user/1000/gnupg/S.gpg-agent /run/user/1000/gnupg/S.gpg-agent.extra

Note the use of the results of each command from earlier: server as the first arg, local as the second.

Then, on the remote machine, I added the following line to my /etc/ssh/sshd_config:

StreamLocalBindUnlink yes

And executed the following commands:

development-server$ sudo service sshd reload
development-server$ sudo systemctl --global mask gpg-agent.service gpg-agent.socket gpg-agent-ssh.socket gpg-agent-extra.socket gpg-agent-browser.socket
development-server$ killall gpg-agent
development-server$ echo "gpgconf --create-socketdir" >> "$HOME/.bashrc"
development-server$ echo "gpgconf --create-socketdir" >> "$HOME/.profile"
development-server$ echo "use-agent" > "$HOME/.gnupg/gpg.conf"

To propagate and test my GPG key, I then did the following two commands on my local machine (replacing KEYID with the email associated with my key):

local$ gpg --export KEYID | ssh development-server gpg --import
local$ echo "test" | gpg --encrypt -r KEYID | ssh development-server gpg --decrypt

If all goes well, the last command outputs test, confirming that you can use the private half of the keypair on the remote machine.

Unfortunately, JetBrains does not yet support Unix tunnelling, so you'll need to commit using a normal terminal. They are already aware of this limitation, and it appears to be marked as a wont-fix. It is possible to work around this by simply opening a terminal and connecting to the remote server after the IDE has started, though this is admittedly less than optimal.

IDE configuration

As of the time of writing, JetBrains devices still default to using a custom implementation of the SSH config parser. We don't want that, because we've just enabled some pretty specific configuration settings.

In the IDE with which you'll be connecting (I'm using JetBrains Gateway here), you need to go to Settings > Advanced Settings > SSH > Configuration Files Parser and set it to OpenSSH. This will then use OpenSSH to connect to the remote server instead. I learned this particularly handy workaround from the YouTrack issue regarding the parsing of some options.

With all this completed, you should now be able to connect to the remote device and get to it! And, just to make sure everything is working as intended, I used my development server to write this page and upload it to GitLab.

Errata

My first commit was marked unverified because I hadn't added the key for this machine yet. Whoops.