Incredibly useful SSH flag

So at work we have a lot of different django environments, scattered across varies servers. All of this information is kept in a central resource. We have the pythonpath, settings file, and the remote server that the client is on. So every time that I want to go do work on a different site, I have to ssh into that server, set the PYTHONPATH and DJANGO_SETTINGS_MODULE environmental variable, and then do what I want to do. This is not a huge deal, but it is annoying when you're doing it 10-20 times a day.

So I went along and tried to figure out the best way to handle this situation. One of the popular ways to achieve this type of functionality is a simple shell script on the remote client, that sets the environment. You ssh into the client, change into the clients directory, and source it into bash to get into your environment. This didn't really solve my problem, because it's basically what I was already doing.

It would be really nice to be able to run an ssh command, to the remote server, and have it executed. SSH has this ability, simply by passing a command after your connect string.

$ ssh ericholscher.com uptime
19:11:28 up 69 days, 21:29,  1 user,  load average: 0.00, 0.00, 0.00

However, this simply executes and returns. I want to be able to set up my environment on the remote side. With the -t command to SSH, this allows you to do exactly that. It sets up a psuedo-tty on the remote side, which then lets you execute commands! So you can do something like this:

$ ssh ericholscher.com -t DJANGO_SETTINGS_MODULE=test.settings bash
eric@Odin:~$ env |grep DJ
DJANGO_SETTINGS_MODULE=test.settings

$ ssh ericholscher.com -t PYTHONPATH='$HOME/Python' django-admin.py dbshell
SQLite version 3.5.9
Enter ".help" for instructions
sqlite>

$ ssh ericholscher.com -t PYTHONPATH='$HOME/Python' django-admin.py shell
[..ipython startup truncated..]
In [1]: import django

In [2]: django.VERSION
Out[2]: (1, 1, 0, 'alpha', 0)

Note that the PYTHONPATH variable is quoted. This is because I am using the $HOME variable, if you don't quote it, it will be evaluated on the client side. If you quote it, it gets passed to the server to be set.

I have written a datastore backend that keeps all of my sites settings and pythonpaths (along with other information), and then I wrote a simple wrapper script around that. I will hopefully be releasing this code in the near future, but it allows me to do things along these lines.

$ ./assume.py production my_site shell
$ ./assume.py production test_project shell
$ ./assume.py staging my_site dbshell
$ ./assume.py local my_site syncdb

and end up in the correct place.

A really neat part of this is that when you do something like dbshell, you are sent to the remote site, and you can perform an action. However, when you leave that command, you are brought back to the local environment that you were already working in. This makes it really easy to be able to do simple one off actions on a remote site (like fixing a bug). This comes up a lot at work, eg: someone is reporting X on server Y, can you please check the database over there really quick.

This gives you a lot of value because you are connected to the database with the credentials in the settings. It really allows you to abstract the knowledge that is in your settings files and build commands around them. Any custom management commands that are defined on the remote server are also really easy to activate as well. This allows you to think "I need to reindex the search on client Z", you simply do something like ./assume.py Z reindex, and then go on about your previously scheduled activity. You don't have to think what server the client is running on, what version of django, where the code lives, or anything like that.

I'd love to hear other ideas that people have for this kind of system. I think that a simple reusable app that keeps track of your different servers and commands would be really neat and useful. Perhaps integrating it into Fabric or something would be possible as well.




Comments

1 codekoala says...

Thanks for this tip!! I've never considered using this part of SSH, but I'm sure I'll start doing it all the time now.

Posted at 4:06 a.m. on February 16, 2009

2 Julian says...

Why don't you just add export PYTHONPATH=$HOME/Python to your .bashrc file on the server?

Posted at 6:15 a.m. on February 16, 2009

3 Eric Holscher says...

@Julian: The whole point is to be able to dynamically add these settings on the remote host. You may have two seperate projects on that server that have different PYTHONPATH and DJANGO_SETTINGS_MODULE variables. The above was merely a trivial example, showing the functionality.

Posted at 6:24 a.m. on February 16, 2009

4 Tracy Reed says...

I have been using this trick for a long time. But I use it to automatically install my ssh key in ~/.ssh/authorized_keys for every account I log into. This is very handy for my sysadmin work. The only problem is that I have a shell script which is 10 lines long with each line separated by a ; instead of a n which has my public key embedded in it which I execute on the remote end using the -t trick. This whole script shows up in the ps output on the remote end which makes a mess of the ps output. It's only aesthetic but I'm annoyed that it is there. I would love to hear if anyone knows how to correct this.

Posted at 7:14 a.m. on February 16, 2009

5 Eric Holscher says...

@Tracy: I would checkout ssh-copy-id. It sounds like what you want, and isn't as hacky :)

Posted at 8:44 a.m. on February 16, 2009

6 lamby says...

You don't actually need -t to solve this - just execute 'env' on the remote server. e.g:

$ ssh ericholscher.com env DJANGO_SETTINGS_MODULE=test.settings bash

Posted at 10:02 a.m. on February 16, 2009

7 Eric Holscher says...

@Iamby: I don't know if you tried this, but on my end it just ends up with a stuck ssh client. That is the point of -t, to allow you to interact on the other end like it is a teminal. Perhaps you have your ssh configured differently?

Posted at 3:50 p.m. on February 16, 2009

8 Michal Migurski says...

The reliance on environment variables seems like a bit of a minefield to me, a design problem in the framework - do you ever forget which site you're working on because you've confused your terminals? Neat trick though.

Posted at 7:52 p.m. on February 16, 2009

9 travis parker says...

i solved the same issue at my job by creating .bashrc files in my home directory on each server I might ssh into. at the end of them, I even use 'cd' so that I just say 'ssh servername' and my environment gets set up and i start in the right directory.

Posted at 9:21 p.m. on February 16, 2009

10 Michal Migurski says...

Second thought: what if you used entirely separate user accounts for each site, with .bashrc or .cshrc files for the environment vars as others have suggested? It's a bit more upfront overhead, but serverside permissions would keep you from clobbering the wrong client file.

Posted at 9:24 p.m. on February 16, 2009

11 macdet says...

@Fabric or something would be possible as well.

that was my first idea :) before read the line!

great post anyway!

i`ll like to see the code

Posted at 12:30 p.m. on March 16, 2009

Comments support markdown

Comments are closed.

Comments have been close for this post.